/* * 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.stress.paging; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.filter.Filter; import org.apache.activemq.artemis.core.message.impl.CoreMessage; import org.apache.activemq.artemis.core.paging.cursor.PageCache; import org.apache.activemq.artemis.core.paging.cursor.PageCursorProvider; import org.apache.activemq.artemis.core.paging.cursor.PageSubscription; import org.apache.activemq.artemis.core.paging.cursor.PagedReference; import org.apache.activemq.artemis.core.paging.cursor.impl.PageCursorProviderImpl; import org.apache.activemq.artemis.core.paging.impl.PagingStoreImpl; import org.apache.activemq.artemis.core.persistence.StorageManager; import org.apache.activemq.artemis.core.persistence.impl.journal.OperationContextImpl; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.server.RoutingContext; import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl; import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.core.transaction.Transaction; import org.apache.activemq.artemis.core.transaction.impl.TransactionImpl; import org.apache.activemq.artemis.tests.unit.core.postoffice.impl.FakeQueue; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.utils.RandomUtil; import org.apache.activemq.artemis.utils.collections.LinkedListIterator; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class PageCursorStressTest extends ActiveMQTestBase { // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- private final SimpleString ADDRESS = new SimpleString("test-add"); private ActiveMQServer server; private Queue queue; private List<Queue> queueList; private ReadLock lock; private static final int PAGE_MAX = -1; private static final int PAGE_SIZE = 10 * 1024 * 1024; // Read more cache than what would fit on the memory, and validate if the memory would be cleared through soft-caches @Test public void testReadCache() throws Exception { final int NUM_MESSAGES = 100; int numberOfPages = addMessages(NUM_MESSAGES, 1024 * 1024); System.out.println("NumberOfPages = " + numberOfPages); PageCursorProviderImpl cursorProvider = new PageCursorProviderImpl(lookupPageStore(ADDRESS), server.getStorageManager(), server.getExecutorFactory().getExecutor(), 5); for (int i = 0; i < numberOfPages; i++) { PageCache cache = cursorProvider.getPageCache(i + 1); System.out.println("Page " + i + " had " + cache.getNumberOfMessages() + " messages"); } forceGC(); assertTrue(cursorProvider.getCacheSize() < numberOfPages); System.out.println("Cache size = " + cursorProvider.getCacheSize()); } @Test public void testSimpleCursor() throws Exception { final int NUM_MESSAGES = 100; PageSubscription cursor = lookupPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); Iterator<PagedReference> iterEmpty = cursor.iterator(); int numberOfPages = addMessages(NUM_MESSAGES, 1024 * 1024); System.out.println("NumberOfPages = " + numberOfPages); PagedReference msg; LinkedListIterator<PagedReference> iterator = cursor.iterator(); int key = 0; while ((msg = iterator.next()) != null) { assertEquals(key++, msg.getMessage().getIntProperty("key").intValue()); cursor.confirmPosition(msg.getPosition()); } assertEquals(NUM_MESSAGES, key); server.getStorageManager().waitOnOperations(); waitCleanup(); assertFalse(lookupPageStore(ADDRESS).isPaging()); assertEquals(1, lookupPageStore(ADDRESS).getNumberOfPages()); forceGC(); server.stop(); createServer(); waitCleanup(); assertEquals(1, lookupPageStore(ADDRESS).getNumberOfPages()); } @Test public void testSimpleCursorWithFilter() throws Exception { final int NUM_MESSAGES = 100; PageSubscription cursorEven = createNonPersistentCursor(new Filter() { @Override public boolean match(Message message) { Boolean property = message.getBooleanProperty("even"); if (property == null) { return false; } else { return property.booleanValue(); } } @Override public SimpleString getFilterString() { return new SimpleString("even=true"); } }); PageSubscription cursorOdd = createNonPersistentCursor(new Filter() { @Override public boolean match(Message message) { Boolean property = message.getBooleanProperty("even"); if (property == null) { return false; } else { return !property.booleanValue(); } } @Override public SimpleString getFilterString() { return new SimpleString("even=true"); } }); int numberOfPages = addMessages(NUM_MESSAGES, 1024 * 1024); System.out.println("NumberOfPages = " + numberOfPages); queue.getPageSubscription().destroy(); PagedReference msg; LinkedListIterator<PagedReference> iteratorEven = cursorEven.iterator(); LinkedListIterator<PagedReference> iteratorOdd = cursorOdd.iterator(); int key = 0; while ((msg = iteratorEven.next()) != null) { System.out.println("Received" + msg); assertEquals(key, msg.getMessage().getIntProperty("key").intValue()); assertTrue(msg.getMessage().getBooleanProperty("even").booleanValue()); key += 2; cursorEven.confirmPosition(msg.getPosition()); } assertEquals(NUM_MESSAGES, key); key = 1; while ((msg = iteratorOdd.next()) != null) { assertEquals(key, msg.getMessage().getIntProperty("key").intValue()); assertFalse(msg.getMessage().getBooleanProperty("even").booleanValue()); key += 2; cursorOdd.confirmPosition(msg.getPosition()); } assertEquals(NUM_MESSAGES + 1, key); forceGC(); // assertTrue(lookupCursorProvider().getCacheSize() < numberOfPages); server.stop(); createServer(); waitCleanup(); assertEquals(1, lookupPageStore(ADDRESS).getNumberOfPages()); } @Test public void testReadNextPage() throws Exception { final int NUM_MESSAGES = 1; int numberOfPages = addMessages(NUM_MESSAGES, 1024); System.out.println("NumberOfPages = " + numberOfPages); PageCursorProvider cursorProvider = lookupCursorProvider(); PageCache cache = cursorProvider.getPageCache(2); assertNull(cache); } @Test public void testRestartWithHoleOnAck() throws Exception { final int NUM_MESSAGES = 1000; int numberOfPages = addMessages(NUM_MESSAGES, 10 * 1024); System.out.println("Number of pages = " + numberOfPages); PageCursorProvider cursorProvider = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider(); System.out.println("cursorProvider = " + cursorProvider); PageSubscription cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); System.out.println("Cursor: " + cursor); LinkedListIterator<PagedReference> iterator = cursor.iterator(); for (int i = 0; i < 100; i++) { PagedReference msg = iterator.next(); assertEquals(i, msg.getMessage().getIntProperty("key").intValue()); if (i < 10 || i > 20) { cursor.ack(msg); } } server.getStorageManager().waitOnOperations(); server.stop(); OperationContextImpl.clearContext(); server.start(); cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); iterator = cursor.iterator(); for (int i = 10; i <= 20; i++) { PagedReference msg = iterator.next(); assertEquals(i, msg.getMessage().getIntProperty("key").intValue()); cursor.ack(msg); } for (int i = 100; i < NUM_MESSAGES; i++) { PagedReference msg = iterator.next(); assertEquals(i, msg.getMessage().getIntProperty("key").intValue()); cursor.ack(msg); } server.stop(); createServer(); waitCleanup(); assertEquals(1, lookupPageStore(ADDRESS).getNumberOfPages()); } @Test public void testRestartWithHoleOnAckAndTransaction() throws Exception { final int NUM_MESSAGES = 1000; int numberOfPages = addMessages(NUM_MESSAGES, 10 * 1024); System.out.println("Number of pages = " + numberOfPages); PageCursorProvider cursorProvider = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider(); System.out.println("cursorProvider = " + cursorProvider); PageSubscription cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); System.out.println("Cursor: " + cursor); Transaction tx = new TransactionImpl(server.getStorageManager(), 60 * 1000); LinkedListIterator<PagedReference> iterator = cursor.iterator(); for (int i = 0; i < 100; i++) { PagedReference msg = iterator.next(); assertEquals(i, msg.getMessage().getIntProperty("key").intValue()); if (i < 10 || i > 20) { cursor.ackTx(tx, msg); } } tx.commit(); server.stop(); OperationContextImpl.clearContext(); server.start(); cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); tx = new TransactionImpl(server.getStorageManager(), 60 * 1000); iterator = cursor.iterator(); for (int i = 10; i <= 20; i++) { PagedReference msg = iterator.next(); assertEquals(i, msg.getMessage().getIntProperty("key").intValue()); cursor.ackTx(tx, msg); } for (int i = 100; i < NUM_MESSAGES; i++) { PagedReference msg = iterator.next(); assertEquals(i, msg.getMessage().getIntProperty("key").intValue()); cursor.ackTx(tx, msg); } tx.commit(); server.stop(); createServer(); waitCleanup(); assertEquals(1, lookupPageStore(ADDRESS).getNumberOfPages()); } @Test public void testConsumeLivePage() throws Exception { PagingStoreImpl pageStore = lookupPageStore(ADDRESS); pageStore.startPaging(); final int NUM_MESSAGES = 100; final int messageSize = 1024 * 1024; PageCursorProvider cursorProvider = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider(); System.out.println("cursorProvider = " + cursorProvider); PageSubscription cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); System.out.println("Cursor: " + cursor); RoutingContextImpl ctx = generateCTX(); LinkedListIterator<PagedReference> iterator = cursor.iterator(); for (int i = 0; i < NUM_MESSAGES; i++) { // if (i % 100 == 0) System.out.println("read/written " + i); ActiveMQBuffer buffer = RandomUtil.randomBuffer(messageSize, i + 1L); Message msg = new CoreMessage(i, buffer.writerIndex()); msg.putIntProperty("key", i); msg.getBodyBuffer().writeBytes(buffer, 0, buffer.writerIndex()); Assert.assertTrue(pageStore.page(msg, ctx.getTransaction(), ctx.getContextListing(ADDRESS), lock)); PagedReference readMessage = iterator.next(); assertNotNull(readMessage); assertEquals(i, readMessage.getMessage().getIntProperty("key").intValue()); assertNull(iterator.next()); } OperationContextImpl.clearContext(); ctx = generateCTX(); pageStore = lookupPageStore(ADDRESS); cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); iterator = cursor.iterator(); for (int i = 0; i < NUM_MESSAGES * 2; i++) { if (i % 100 == 0) System.out.println("Paged " + i); if (i >= NUM_MESSAGES) { ActiveMQBuffer buffer = RandomUtil.randomBuffer(messageSize, i + 1L); Message msg = new CoreMessage(i, buffer.writerIndex()); msg.putIntProperty("key", i); msg.getBodyBuffer().writeBytes(buffer, 0, buffer.writerIndex()); Assert.assertTrue(pageStore.page(msg, ctx.getTransaction(), ctx.getContextListing(ADDRESS), lock)); } PagedReference readMessage = iterator.next(); assertNotNull(readMessage); assertEquals(i, readMessage.getMessage().getIntProperty("key").intValue()); } OperationContextImpl.clearContext(); pageStore = lookupPageStore(ADDRESS); cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); iterator = cursor.iterator(); for (int i = 0; i < NUM_MESSAGES * 3; i++) { if (i % 100 == 0) System.out.println("Paged " + i); if (i >= NUM_MESSAGES * 2 - 1) { ActiveMQBuffer buffer = RandomUtil.randomBuffer(messageSize, i + 1L); Message msg = new CoreMessage(i, buffer.writerIndex()); msg.putIntProperty("key", i + 1); msg.getBodyBuffer().writeBytes(buffer, 0, buffer.writerIndex()); Assert.assertTrue(pageStore.page(msg, ctx.getTransaction(), ctx.getContextListing(ADDRESS), lock)); } PagedReference readMessage = iterator.next(); assertNotNull(readMessage); cursor.ack(readMessage); assertEquals(i, readMessage.getMessage().getIntProperty("key").intValue()); } PagedReference readMessage = iterator.next(); assertEquals(NUM_MESSAGES * 3, readMessage.getMessage().getIntProperty("key").intValue()); cursor.ack(readMessage); server.getStorageManager().waitOnOperations(); pageStore.flushExecutors(); assertFalse(pageStore.isPaging()); server.stop(); createServer(); assertFalse(pageStore.isPaging()); waitCleanup(); assertFalse(lookupPageStore(ADDRESS).isPaging()); } @Test public void testConsumeLivePageMultiThread() throws Exception { final PagingStoreImpl pageStore = lookupPageStore(ADDRESS); pageStore.startPaging(); final int NUM_TX = 100; final int MSGS_TX = 100; final int TOTAL_MSG = NUM_TX * MSGS_TX; final int messageSize = 1024; PageCursorProvider cursorProvider = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider(); System.out.println("cursorProvider = " + cursorProvider); PageSubscription cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); System.out.println("Cursor: " + cursor); final StorageManager storage = this.server.getStorageManager(); final AtomicInteger exceptions = new AtomicInteger(0); Thread t1 = new Thread() { @Override public void run() { try { int count = 0; for (int txCount = 0; txCount < NUM_TX; txCount++) { Transaction tx = null; if (txCount % 2 == 0) { tx = new TransactionImpl(storage); } RoutingContext ctx = generateCTX(tx); for (int i = 0; i < MSGS_TX; i++) { //System.out.println("Sending " + count); ActiveMQBuffer buffer = RandomUtil.randomBuffer(messageSize, count); Message msg = new CoreMessage(i, buffer.writerIndex()); msg.putIntProperty("key", count++); msg.getBodyBuffer().writeBytes(buffer, 0, buffer.writerIndex()); Assert.assertTrue(pageStore.page(msg, ctx.getTransaction(), ctx.getContextListing(ADDRESS), lock)); } if (tx != null) { tx.commit(); } } } catch (Throwable e) { e.printStackTrace(); exceptions.incrementAndGet(); } } }; t1.start(); LinkedListIterator<PagedReference> iterator = cursor.iterator(); for (int i = 0; i < TOTAL_MSG; i++) { assertEquals(0, exceptions.get()); PagedReference ref = null; for (int repeat = 0; repeat < 5; repeat++) { ref = iterator.next(); if (ref == null) { Thread.sleep(1000); } else { break; } } assertNotNull(ref); ref.acknowledge(); assertNotNull(ref); System.out.println("Consuming " + ref.getMessage().getIntProperty("key")); //assertEquals(i, ref.getMessage().getIntProperty("key").intValue()); } assertEquals(0, exceptions.get()); } private RoutingContextImpl generateCTX() { return generateCTX(null); } private RoutingContextImpl generateCTX(Transaction tx) { RoutingContextImpl ctx = new RoutingContextImpl(tx); ctx.addQueue(ADDRESS, queue); for (Queue q : this.queueList) { ctx.addQueue(ADDRESS, q); } return ctx; } /** * @throws Exception */ private void waitCleanup() throws Exception { // The cleanup is done asynchronously, so we need to wait some time long timeout = System.currentTimeMillis() + 10000; while (System.currentTimeMillis() < timeout && lookupPageStore(ADDRESS).getNumberOfPages() != 1) { Thread.sleep(100); } assertTrue("expected " + lookupPageStore(ADDRESS).getNumberOfPages(), lookupPageStore(ADDRESS).getNumberOfPages() <= 2); } @Test public void testLazyCommit() throws Exception { PagingStoreImpl pageStore = lookupPageStore(ADDRESS); pageStore.startPaging(); final int NUM_MESSAGES = 100; final int messageSize = 100 * 1024; PageCursorProvider cursorProvider = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider(); System.out.println("cursorProvider = " + cursorProvider); PageSubscription cursor = this.server.getPagingManager().getPageStore(ADDRESS).getCursorProvider().getSubscription(queue.getID()); LinkedListIterator<PagedReference> iterator = cursor.iterator(); System.out.println("Cursor: " + cursor); StorageManager storage = this.server.getStorageManager(); long pgtxLazy = storage.generateID(); Transaction txLazy = pgMessages(storage, pageStore, pgtxLazy, 0, NUM_MESSAGES, messageSize); addMessages(100, NUM_MESSAGES, messageSize); System.out.println("Number of pages - " + pageStore.getNumberOfPages()); // First consume what's already there without any tx as nothing was committed for (int i = 100; i < 200; i++) { PagedReference pos = iterator.next(); assertNotNull("Null at position " + i, pos); assertEquals(i, pos.getMessage().getIntProperty("key").intValue()); cursor.ack(pos); } assertNull(iterator.next()); txLazy.commit(); storage.waitOnOperations(); for (int i = 0; i < 100; i++) { PagedReference pos = iterator.next(); assertNotNull("Null at position " + i, pos); assertEquals(i, pos.getMessage().getIntProperty("key").intValue()); cursor.ack(pos); } assertNull(iterator.next()); waitCleanup(); server.stop(); createServer(); waitCleanup(); assertEquals(1, lookupPageStore(ADDRESS).getNumberOfPages()); } private int tstProperty(Message msg) { return msg.getIntProperty("key").intValue(); } @Test public void testMultipleIterators() throws Exception { final int NUM_MESSAGES = 10; int numberOfPages = addMessages(NUM_MESSAGES, 1024 * 1024); System.out.println("NumberOfPages = " + numberOfPages); PageCursorProvider cursorProvider = lookupCursorProvider(); PageSubscription cursor = cursorProvider.getSubscription(queue.getID()); LinkedListIterator<PagedReference> iter = cursor.iterator(); LinkedListIterator<PagedReference> iter2 = cursor.iterator(); assertTrue(iter.hasNext()); PagedReference msg1 = iter.next(); PagedReference msg2 = iter2.next(); assertEquals(tstProperty(msg1.getMessage()), tstProperty(msg2.getMessage())); System.out.println("property = " + tstProperty(msg1.getMessage())); msg1 = iter.next(); assertEquals(1, tstProperty(msg1.getMessage())); iter.remove(); msg2 = iter2.next(); assertEquals(2, tstProperty(msg2.getMessage())); iter2.repeat(); msg2 = iter2.next(); assertEquals(2, tstProperty(msg2.getMessage())); iter2.repeat(); assertEquals(2, tstProperty(msg2.getMessage())); msg1 = iter.next(); assertEquals(2, tstProperty(msg1.getMessage())); iter.repeat(); msg1 = iter.next(); assertEquals(2, tstProperty(msg1.getMessage())); assertTrue(iter2.hasNext()); } private int addMessages(final int numMessages, final int messageSize) throws Exception { return addMessages(0, numMessages, messageSize); } private int addMessages(final int start, final int numMessages, final int messageSize) throws Exception { PagingStoreImpl pageStore = lookupPageStore(ADDRESS); pageStore.startPaging(); RoutingContext ctx = generateCTX(); for (int i = start; i < start + numMessages; i++) { if (i % 100 == 0) System.out.println("Paged " + i); ActiveMQBuffer buffer = RandomUtil.randomBuffer(messageSize, i + 1L); Message msg = new CoreMessage(i, buffer.writerIndex()); msg.putIntProperty("key", i); // to be used on tests that are validating filters msg.putBooleanProperty("even", i % 2 == 0); msg.getBodyBuffer().writeBytes(buffer, 0, buffer.writerIndex()); Assert.assertTrue(pageStore.page(msg, ctx.getTransaction(), ctx.getContextListing(ADDRESS), lock)); } return pageStore.getNumberOfPages(); } /** * @return * @throws Exception */ private PagingStoreImpl lookupPageStore(SimpleString address) throws Exception { return (PagingStoreImpl) server.getPagingManager().getPageStore(address); } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- @Override @Before public void setUp() throws Exception { super.setUp(); OperationContextImpl.clearContext(); queueList = new ArrayList<>(); createServer(); lock = new ReentrantReadWriteLock().readLock(); } /** * @throws Exception */ private void createServer() throws Exception { OperationContextImpl.clearContext(); Configuration config = createDefaultInVMConfig().setJournalSyncNonTransactional(true); server = createServer(true, config, PAGE_SIZE, PAGE_MAX, new HashMap<String, AddressSettings>()); server.start(); queueList.clear(); try { queue = server.createQueue(ADDRESS, RoutingType.ANYCAST, ADDRESS, null, true, false); queue.pause(); } catch (Exception ignored) { } } /** * @return * @throws Exception */ private PageSubscription createNonPersistentCursor(Filter filter) throws Exception { long id = server.getStorageManager().generateID(); FakeQueue queue = new FakeQueue(new SimpleString(filter.toString()), id); queueList.add(queue); PageSubscription subs = lookupCursorProvider().createSubscription(id, filter, false); queue.setPageSubscription(subs); return subs; } /** * @return * @throws Exception */ private PageCursorProvider lookupCursorProvider() throws Exception { return lookupPageStore(ADDRESS).getCursorProvider(); } /** * @param storage * @param pageStore * @param pgParameter * @param start * @param NUM_MESSAGES * @param messageSize * @throws Exception */ private Transaction pgMessages(StorageManager storage, PagingStoreImpl pageStore, long pgParameter, int start, final int NUM_MESSAGES, final int messageSize) throws Exception { TransactionImpl txImpl = new TransactionImpl(pgParameter, null, storage); RoutingContext ctx = generateCTX(txImpl); for (int i = start; i < start + NUM_MESSAGES; i++) { ActiveMQBuffer buffer = RandomUtil.randomBuffer(messageSize, i + 1L); Message msg = new CoreMessage(storage.generateID(), buffer.writerIndex()); msg.getBodyBuffer().writeBytes(buffer, 0, buffer.writerIndex()); msg.putIntProperty("key", i); pageStore.page(msg, ctx.getTransaction(), ctx.getContextListing(ADDRESS), lock); } return txImpl; } }