/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.tests.integration.paging; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.client.ClientConsumer; import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientProducer; import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; import org.apache.activemq.artemis.api.core.client.ServerLocator; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.MessageReference; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.utils.collections.LinkedListIterator; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class PagingSendTest extends ActiveMQTestBase { public static final SimpleString ADDRESS = new SimpleString("SimpleAddress"); private ServerLocator locator; private ActiveMQServer server; protected boolean isNetty() { return false; } @Override @Before public void setUp() throws Exception { super.setUp(); Configuration config = new ConfigurationImpl(); server = newActiveMQServer(); server.start(); waitForServerToStart(server); locator = createFactory(isNetty()); } private ActiveMQServer newActiveMQServer() throws Exception { ActiveMQServer server = createServer(true, isNetty()); AddressSettings defaultSetting = new AddressSettings().setPageSizeBytes(10 * 1024).setMaxSizeBytes(20 * 1024); server.getAddressSettingsRepository().addMatch("#", defaultSetting); return server; } @Test public void testSameMessageOverAndOverBlocking() throws Exception { dotestSameMessageOverAndOver(true); } @Test public void testSameMessageOverAndOverNonBlocking() throws Exception { dotestSameMessageOverAndOver(false); } public void dotestSameMessageOverAndOver(final boolean blocking) throws Exception { // Making it synchronous, just because we want to stop sending messages as soon as the // page-store becomes in // page mode // and we could only guarantee that by setting it to synchronous locator.setBlockOnNonDurableSend(blocking).setBlockOnDurableSend(blocking).setBlockOnAcknowledge(blocking); ClientSessionFactory sf = createSessionFactory(locator); ClientSession session = sf.createSession(null, null, false, true, true, false, 0); session.createQueue(PagingSendTest.ADDRESS, PagingSendTest.ADDRESS, null, true); ClientProducer producer = session.createProducer(PagingSendTest.ADDRESS); ClientMessage message = null; message = session.createMessage(true); message.getBodyBuffer().writeBytes(new byte[1024]); for (int i = 0; i < 200; i++) { producer.send(message); } session.close(); session = sf.createSession(null, null, false, true, true, false, 0); ClientConsumer consumer = session.createConsumer(PagingSendTest.ADDRESS); session.start(); for (int i = 0; i < 200; i++) { ClientMessage message2 = consumer.receive(10000); Assert.assertNotNull(message2); if (i == 100) { session.commit(); } message2.acknowledge(); } consumer.close(); session.close(); } @Test public void testOrderOverTX() throws Exception { ClientSessionFactory sf = createSessionFactory(locator); ClientSession sessionConsumer = sf.createSession(true, true, 0); sessionConsumer.createQueue(PagingSendTest.ADDRESS, PagingSendTest.ADDRESS, null, true); final ClientSession sessionProducer = sf.createSession(false, false); final ClientProducer producer = sessionProducer.createProducer(PagingSendTest.ADDRESS); final AtomicInteger errors = new AtomicInteger(0); final int TOTAL_MESSAGES = 1000; // Consumer will be ready after we have commits final CountDownLatch ready = new CountDownLatch(1); Thread tProducer = new Thread() { @Override public void run() { try { int commit = 0; for (int i = 0; i < TOTAL_MESSAGES; i++) { ClientMessage msg = sessionProducer.createMessage(true); msg.getBodyBuffer().writeBytes(new byte[1024]); msg.putIntProperty("count", i); producer.send(msg); if (i % 100 == 0 && i > 0) { sessionProducer.commit(); if (commit++ > 2) { ready.countDown(); } } } sessionProducer.commit(); } catch (Exception e) { e.printStackTrace(); errors.incrementAndGet(); } } }; ClientConsumer consumer = sessionConsumer.createConsumer(PagingSendTest.ADDRESS); sessionConsumer.start(); tProducer.start(); assertTrue(ready.await(10, TimeUnit.SECONDS)); for (int i = 0; i < TOTAL_MESSAGES; i++) { ClientMessage msg = consumer.receive(10000); Assert.assertNotNull(msg); assertEquals(i, msg.getIntProperty("count").intValue()); msg.acknowledge(); } tProducer.join(); sessionConsumer.close(); sessionProducer.close(); assertEquals(0, errors.get()); } @Test public void testPagingDoesNotDuplicateBatchMessages() throws Exception { int batchSize = 20; ClientSessionFactory sf = createSessionFactory(locator); ClientSession session = sf.createSession(false, false); // Create a queue SimpleString queueAddr = new SimpleString("testQueue"); session.createQueue(queueAddr, queueAddr, null, true); // Set up paging on the queue address AddressSettings addressSettings = new AddressSettings().setPageSizeBytes(10 * 1024) /** This actually causes the address to start paging messages after 10 x messages with 1024 payload is sent. Presumably due to additional meta-data, message headers etc... **/.setMaxSizeBytes(16 * 1024); server.getAddressSettingsRepository().addMatch("#", addressSettings); sendMessageBatch(batchSize, session, queueAddr); Queue queue = server.locateQueue(queueAddr); // Give time Queue.deliverAsync to deliver messages Assert.assertTrue("Messages were not propagated to internal structures.", waitForMessages(queue, batchSize, 3000)); checkBatchMessagesAreNotPagedTwice(queue); for (int i = 0; i < 10; i++) { // execute the same count a couple times. This is to make sure the iterators have no impact regardless // the number of times they are called assertEquals(batchSize, processCountThroughIterator(queue)); } } @Test public void testPagingDoesNotDuplicateBatchMessagesAfterPagingStarted() throws Exception { int batchSize = 20; ClientSessionFactory sf = createSessionFactory(locator); ClientSession session = sf.createSession(false, false); // Create a queue SimpleString queueAddr = new SimpleString("testQueue"); session.createQueue(queueAddr, queueAddr, null, true); // Set up paging on the queue address AddressSettings addressSettings = new AddressSettings().setPageSizeBytes(10 * 1024) /** This actually causes the address to start paging messages after 10 x messages with 1024 payload is sent. Presumably due to additional meta-data, message headers etc... **/.setMaxSizeBytes(16 * 1024); server.getAddressSettingsRepository().addMatch("#", addressSettings); int numberOfMessages = 0; // ensure the server is paging while (!server.getPagingManager().getPageStore(queueAddr).isPaging()) { sendMessageBatch(batchSize, session, queueAddr); numberOfMessages += batchSize; } sendMessageBatch(batchSize, session, queueAddr); numberOfMessages += batchSize; Queue queue = server.locateQueue(queueAddr); checkBatchMessagesAreNotPagedTwice(queue); for (int i = 0; i < 10; i++) { // execute the same count a couple times. This is to make sure the iterators have no impact regardless // the number of times they are called assertEquals(numberOfMessages, processCountThroughIterator(queue)); } } public List<String> sendMessageBatch(int batchSize, ClientSession session, SimpleString queueAddr) throws ActiveMQException { List<String> messageIds = new ArrayList<>(); ClientProducer producer = session.createProducer(queueAddr); for (int i = 0; i < batchSize; i++) { ClientMessage message = session.createMessage(true); message.getBodyBuffer().writeBytes(new byte[1024]); String id = UUID.randomUUID().toString(); message.putStringProperty("id", id); message.putIntProperty("seq", i); // this is to make the print-data easier to debug messageIds.add(id); producer.send(message); } session.commit(); return messageIds; } /** * checks that there are no message duplicates in the page. Any IDs found in the ignoreIds field will not be tested * this allows us to test only those messages that have been sent after the address has started paging (ignoring any * duplicates that may have happened before this point). */ public void checkBatchMessagesAreNotPagedTwice(Queue queue) throws Exception { LinkedListIterator<MessageReference> pageIterator = queue.browserIterator(); Set<String> messageOrderSet = new HashSet<>(); int duplicates = 0; while (pageIterator.hasNext()) { MessageReference reference = pageIterator.next(); String id = reference.getMessage().getStringProperty("id"); // If add(id) returns true it means that this id was already added to this set. Hence a duplicate is found. if (!messageOrderSet.add(id)) { duplicates++; } } assertTrue(duplicates == 0); } public boolean waitForMessages(Queue queue, int count, long timeout) throws Exception { long timeToWait = System.currentTimeMillis() + timeout; while (System.currentTimeMillis() < timeToWait) { if (queue.getMessageCount() >= count) { return true; } Thread.sleep(100); } return false; } /** * checks that there are no message duplicates in the page. Any IDs found in the ignoreIds field will not be tested * this allows us to test only those messages that have been sent after the address has started paging (ignoring any * duplicates that may have happened before this point). */ protected int processCountThroughIterator(Queue queue) throws Exception { LinkedListIterator<MessageReference> pageIterator = queue.browserIterator(); int count = 0; while (pageIterator.hasNext()) { MessageReference reference = pageIterator.next(); count++; } return count; } }