/** * 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.bugs; import java.util.HashSet; import java.util.LinkedList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.jms.BytesMessage; import javax.jms.Connection; import javax.jms.DeliveryMode; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import junit.framework.Test; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.ActiveMQPrefetchPolicy; import org.apache.activemq.TestSupport; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.TransportConnector; import org.apache.activemq.broker.region.DestinationStatistics; import org.apache.activemq.broker.region.policy.FilePendingQueueMessageStoragePolicy; import org.apache.activemq.broker.region.policy.PolicyEntry; import org.apache.activemq.broker.region.policy.PolicyMap; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQQueue; import org.apache.activemq.store.kahadb.plist.PListStoreImpl; import org.apache.activemq.util.DefaultTestAppender; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.spi.LoggingEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AMQ4221Test extends TestSupport { private static final Logger LOG = LoggerFactory.getLogger(AMQ4221Test.class); public int PAYLOAD_SIZE_BYTES = 4 * 1024; public int NUM_TO_SEND = 60000; public int NUM_CONCURRENT_PRODUCERS = 20; public int QUEUE_COUNT = 1; public int TMP_JOURNAL_MAX_FILE_SIZE = 10 * 1024 * 1024; public int DLQ_PURGE_INTERVAL = 30000; public int MESSAGE_TIME_TO_LIVE = 20000; public int EXPIRE_SWEEP_PERIOD = 200; public int TMP_JOURNAL_GC_PERIOD = 50; public int RECEIVE_POLL_PERIOD = 4000; private int RECEIVE_BATCH = 5000; final byte[] payload = new byte[PAYLOAD_SIZE_BYTES]; final AtomicInteger counter = new AtomicInteger(0); final HashSet<Throwable> exceptions = new HashSet<Throwable>(); BrokerService brokerService; private String brokerUrlString; ExecutorService executorService = Executors.newCachedThreadPool(); final AtomicBoolean done = new AtomicBoolean(false); final LinkedList<String> errorsInLog = new LinkedList<String>(); public static Test suite() { return suite(AMQ4221Test.class); } @Override public void setUp() throws Exception { LogManager.getRootLogger().addAppender(new DefaultTestAppender() { @Override public void doAppend(LoggingEvent event) { if (event.getLevel().isGreaterOrEqual(Level.ERROR)) { System.err.println("Fail on error in log: " + event.getMessage()); done.set(true); errorsInLog.add(event.getRenderedMessage()); } } }); done.set(false); errorsInLog.clear(); brokerService = new BrokerService(); brokerService.setDeleteAllMessagesOnStartup(true); brokerService.setDestinations(new ActiveMQDestination[]{new ActiveMQQueue("ActiveMQ.DLQ")}); PolicyEntry defaultPolicy = new PolicyEntry(); defaultPolicy.setPendingQueuePolicy(new FilePendingQueueMessageStoragePolicy()); defaultPolicy.setExpireMessagesPeriod(EXPIRE_SWEEP_PERIOD); defaultPolicy.setProducerFlowControl(false); defaultPolicy.setMemoryLimit(50 * 1024 * 1024); brokerService.getSystemUsage().getMemoryUsage().setLimit(50 * 1024 * 1024); PolicyMap destinationPolicyMap = new PolicyMap(); destinationPolicyMap.setDefaultEntry(defaultPolicy); brokerService.setDestinationPolicy(destinationPolicyMap); PListStoreImpl tempDataStore = new PListStoreImpl(); tempDataStore.setDirectory(brokerService.getTmpDataDirectory()); tempDataStore.setJournalMaxFileLength(TMP_JOURNAL_MAX_FILE_SIZE); tempDataStore.setCleanupInterval(TMP_JOURNAL_GC_PERIOD); tempDataStore.setIndexPageSize(200); tempDataStore.setIndexEnablePageCaching(false); brokerService.setTempDataStore(tempDataStore); brokerService.setAdvisorySupport(false); TransportConnector tcp = brokerService.addConnector("tcp://localhost:0"); brokerService.start(); brokerUrlString = tcp.getPublishableConnectString(); } @Override public void tearDown() throws Exception { brokerService.stop(); brokerService.waitUntilStopped(); executorService.shutdownNow(); } public void testProduceConsumeExpireHalf() throws Exception { final org.apache.activemq.broker.region.Queue dlq = (org.apache.activemq.broker.region.Queue) getDestination(brokerService, new ActiveMQQueue("ActiveMQ.DLQ")); if (DLQ_PURGE_INTERVAL > 0) { executorService.execute(new Runnable() { @Override public void run() { while (!done.get()) { try { Thread.sleep(DLQ_PURGE_INTERVAL); LOG.info("Purge DLQ, current size: " + dlq.getDestinationStatistics().getMessages().getCount()); dlq.purge(); } catch (InterruptedException allDone) { } catch (Throwable e) { e.printStackTrace(); exceptions.add(e); } } } }); } final CountDownLatch latch = new CountDownLatch(QUEUE_COUNT); for (int i = 0; i < QUEUE_COUNT; i++) { final int id = i; executorService.execute(new Runnable() { @Override public void run() { try { doProduceConsumeExpireHalf(id, latch); } catch (Throwable e) { e.printStackTrace(); exceptions.add(e); } } }); } while (!done.get()) { done.set(latch.await(5, TimeUnit.SECONDS)); } executorService.shutdown(); executorService.awaitTermination(5, TimeUnit.MINUTES); assertTrue("no exceptions:" + exceptions, exceptions.isEmpty()); assertTrue("No ERROR in log:" + errorsInLog, errorsInLog.isEmpty()); } public void doProduceConsumeExpireHalf(int id, CountDownLatch latch) throws Exception { final ActiveMQQueue queue = new ActiveMQQueue("Q" + id); final ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerUrlString); ActiveMQPrefetchPolicy prefecthPolicy = new ActiveMQPrefetchPolicy(); prefecthPolicy.setAll(0); factory.setPrefetchPolicy(prefecthPolicy); Connection connection = factory.createConnection(); connection.start(); final MessageConsumer consumer = connection.createSession(false, Session.AUTO_ACKNOWLEDGE).createConsumer(queue, "on = 'true'"); executorService.execute(new Runnable() { @Override public void run() { try { while (!done.get()) { Thread.sleep(RECEIVE_POLL_PERIOD); for (int i = 0; i < RECEIVE_BATCH && !done.get(); i++) { Message message = consumer.receive(1000); if (message != null) { counter.incrementAndGet(); if (counter.get() > 0 && counter.get() % 500 == 0) { LOG.info("received: " + counter.get() + ", " + message.getJMSDestination().toString()); } } } } } catch (JMSException ignored) { } catch (Exception e) { e.printStackTrace(); exceptions.add(e); } } }); final AtomicInteger accumulator = new AtomicInteger(0); final CountDownLatch producersDone = new CountDownLatch(NUM_CONCURRENT_PRODUCERS); for (int i = 0; i < NUM_CONCURRENT_PRODUCERS; i++) { executorService.execute(new Runnable() { @Override public void run() { try { Connection sendConnection = factory.createConnection(); sendConnection.start(); Session sendSession = sendConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = sendSession.createProducer(queue); producer.setTimeToLive(MESSAGE_TIME_TO_LIVE); producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); while (accumulator.incrementAndGet() < NUM_TO_SEND && !done.get()) { BytesMessage message = sendSession.createBytesMessage(); message.writeBytes(payload); message.setStringProperty("on", String.valueOf(accumulator.get() % 2 == 0)); producer.send(message); } producersDone.countDown(); } catch (Exception e) { e.printStackTrace(); exceptions.add(e); } } }); } producersDone.await(10, TimeUnit.MINUTES); final DestinationStatistics view = getDestinationStatistics(brokerService, queue); LOG.info("total expired so far " + view.getExpired().getCount() + ", " + queue.getQueueName()); latch.countDown(); } }