/** * 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.usecases; import javax.jms.Connection; import javax.jms.DeliveryMode; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.MessageProducer; import javax.jms.Session; import java.util.concurrent.atomic.AtomicLong; import junit.framework.Test; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.CombinationTestSupport; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.DestinationStatistics; import org.apache.activemq.broker.region.policy.PolicyEntry; import org.apache.activemq.broker.region.policy.PolicyMap; import org.apache.activemq.broker.region.policy.VMPendingQueueMessageStoragePolicy; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQQueue; import org.apache.activemq.store.memory.MemoryPersistenceAdapter; import org.apache.activemq.util.Wait; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.activemq.TestSupport.getDestination; import static org.apache.activemq.TestSupport.getDestinationStatistics; public class ExpiredMessagesTest extends CombinationTestSupport { private static final Logger LOG = LoggerFactory.getLogger(ExpiredMessagesTest.class); BrokerService broker; Connection connection; Session session; MessageProducer producer; MessageConsumer consumer; public ActiveMQDestination destination = new ActiveMQQueue("test"); public ActiveMQDestination dlqDestination = new ActiveMQQueue("ActiveMQ.DLQ"); public boolean useTextMessage = true; public boolean useVMCursor = true; protected String brokerUri; public static Test suite() { return suite(ExpiredMessagesTest.class); } public static void main(String[] args) { junit.textui.TestRunner.run(suite()); } @Override protected void setUp() throws Exception { final boolean deleteAllMessages = true; broker = createBroker(deleteAllMessages, 100); brokerUri = broker.getTransportConnectors().get(0).getPublishableConnectString(); } @Override protected void tearDown() throws Exception { connection.stop(); broker.stop(); broker.waitUntilStopped(); } public void testExpiredMessages() throws Exception { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerUri); connection = factory.createConnection(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); producer = session.createProducer(destination); producer.setTimeToLive(100); consumer = session.createConsumer(destination); connection.start(); final AtomicLong received = new AtomicLong(); Thread consumerThread = new Thread("Consumer Thread") { @Override public void run() { long start = System.currentTimeMillis(); try { long end = System.currentTimeMillis(); while (end - start < 3000) { if (consumer.receive(1000) != null) { received.incrementAndGet(); } Thread.sleep(100); end = System.currentTimeMillis(); } consumer.close(); } catch (Throwable ex) { ex.printStackTrace(); } } }; consumerThread.start(); final int numMessagesToSend = 10000; Thread producingThread = new Thread("Producing Thread") { @Override public void run() { try { int i = 0; while (i++ < numMessagesToSend) { producer.send(session.createTextMessage("test")); } producer.close(); } catch (Throwable ex) { ex.printStackTrace(); } } }; producingThread.start(); consumerThread.join(); producingThread.join(); session.close(); final DestinationStatistics view = getDestinationStatistics(broker, destination); // wait for all to inflight to expire assertTrue("all inflight messages expired ", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return view.getInflight().getCount() == 0; } })); assertEquals("Wrong inFlightCount: ", 0, view.getInflight().getCount()); LOG.info("Stats: received: " + received.get() + ", enqueues: " + view.getEnqueues().getCount() + ", dequeues: " + view.getDequeues().getCount() + ", dispatched: " + view.getDispatched().getCount() + ", inflight: " + view.getInflight().getCount() + ", expiries: " + view.getExpired().getCount()); // wait for all sent to get delivered and expire assertTrue("all sent messages expired ", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { long oldEnqueues = view.getEnqueues().getCount(); Thread.sleep(200); LOG.info("Stats: received: " + received.get() + ", size= " + view.getMessages().getCount() + ", enqueues: " + view.getDequeues().getCount() + ", dequeues: " + view.getDequeues().getCount() + ", dispatched: " + view.getDispatched().getCount() + ", inflight: " + view.getInflight().getCount() + ", expiries: " + view.getExpired().getCount()); return oldEnqueues == view.getEnqueues().getCount(); } }, 60 * 1000)); LOG.info("Stats: received: " + received.get() + ", size= " + view.getMessages().getCount() + ", enqueues: " + view.getEnqueues().getCount() + ", dequeues: " + view.getDequeues().getCount() + ", dispatched: " + view.getDispatched().getCount() + ", inflight: " + view.getInflight().getCount() + ", expiries: " + view.getExpired().getCount()); assertTrue("got at least what did not expire", received.get() >= view.getDequeues().getCount() - view.getExpired().getCount()); assertTrue("all messages expired - queue size gone to zero " + view.getMessages().getCount(), Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { LOG.info("Stats: received: " + received.get() + ", size= " + view.getMessages().getCount() + ", enqueues: " + view.getEnqueues().getCount() + ", dequeues: " + view.getDequeues().getCount() + ", dispatched: " + view.getDispatched().getCount() + ", inflight: " + view.getInflight().getCount() + ", expiries: " + view.getExpired().getCount()); return view.getMessages().getCount() == 0; } })); final long expiredBeforeEnqueue = numMessagesToSend - view.getEnqueues().getCount(); final long totalExpiredCount = view.getExpired().getCount() + expiredBeforeEnqueue; final DestinationStatistics dlqView = getDestinationStatistics(broker, dlqDestination); LOG.info("DLQ stats: size= " + dlqView.getMessages().getCount() + ", enqueues: " + dlqView.getDequeues().getCount() + ", dequeues: " + dlqView.getDequeues().getCount() + ", dispatched: " + dlqView.getDispatched().getCount() + ", inflight: " + dlqView.getInflight().getCount() + ", expiries: " + dlqView.getExpired().getCount()); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return totalExpiredCount == dlqView.getMessages().getCount(); } }); assertEquals("dlq contains all expired", totalExpiredCount, dlqView.getMessages().getCount()); // memory check assertEquals("memory usage is back to duck egg", 0, getDestination(broker, destination).getMemoryUsage().getPercentUsage()); assertTrue("memory usage is increased ", 0 < getDestination(broker, dlqDestination).getMemoryUsage().getUsage()); // verify DLQ MessageConsumer dlqConsumer = createDlqConsumer(connection); final DLQListener dlqListener = new DLQListener(); dlqConsumer.setMessageListener(dlqListener); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return totalExpiredCount == dlqListener.count; } }, 60 * 1000); assertEquals("dlq returned all expired", dlqListener.count, totalExpiredCount); } class DLQListener implements MessageListener { int count = 0; @Override public void onMessage(Message message) { count++; } } private MessageConsumer createDlqConsumer(Connection connection) throws Exception { return connection.createSession(false, Session.AUTO_ACKNOWLEDGE).createConsumer(dlqDestination); } public void initCombosForTestRecoverExpiredMessages() { addCombinationValues("useVMCursor", new Object[]{Boolean.TRUE, Boolean.FALSE}); } public void testRecoverExpiredMessages() throws Exception { ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("failover://" + brokerUri); connection = factory.createConnection(); connection.start(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); producer = session.createProducer(destination); producer.setTimeToLive(2000); producer.setDeliveryMode(DeliveryMode.PERSISTENT); Thread producingThread = new Thread("Producing Thread") { @Override public void run() { try { int i = 0; while (i++ < 1000) { Message message = useTextMessage ? session.createTextMessage("test") : session.createObjectMessage("test"); producer.send(message); } producer.close(); } catch (Throwable ex) { ex.printStackTrace(); } } }; producingThread.start(); producingThread.join(); DestinationStatistics view = getDestinationStatistics(broker, destination); LOG.info("Stats: size: " + view.getMessages().getCount() + ", enqueues: " + view.getEnqueues().getCount() + ", dequeues: " + view.getDequeues().getCount() + ", dispatched: " + view.getDispatched().getCount() + ", inflight: " + view.getInflight().getCount() + ", expiries: " + view.getExpired().getCount()); LOG.info("stopping broker"); broker.stop(); broker.waitUntilStopped(); Thread.sleep(5000); LOG.info("recovering broker"); final boolean deleteAllMessages = false; broker = createBroker(deleteAllMessages, 5000); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { DestinationStatistics view = getDestinationStatistics(broker, destination); LOG.info("Stats: size: " + view.getMessages().getCount() + ", enqueues: " + view.getEnqueues().getCount() + ", dequeues: " + view.getDequeues().getCount() + ", dispatched: " + view.getDispatched().getCount() + ", inflight: " + view.getInflight().getCount() + ", expiries: " + view.getExpired().getCount()); return view.getMessages().getCount() == 0; } }); view = getDestinationStatistics(broker, destination); assertEquals("Expect empty queue, QueueSize: ", 0, view.getMessages().getCount()); assertEquals("all dequeues were expired", view.getDequeues().getCount(), view.getExpired().getCount()); } private BrokerService createBroker(boolean deleteAllMessages, long expireMessagesPeriod) throws Exception { BrokerService broker = new BrokerService(); broker.setBrokerName("localhost"); broker.setDestinations(new ActiveMQDestination[]{destination}); broker.setPersistenceAdapter(new MemoryPersistenceAdapter()); PolicyEntry defaultPolicy = new PolicyEntry(); if (useVMCursor) { defaultPolicy.setPendingQueuePolicy(new VMPendingQueueMessageStoragePolicy()); } defaultPolicy.setExpireMessagesPeriod(expireMessagesPeriod); defaultPolicy.setMaxExpirePageSize(1200); PolicyMap policyMap = new PolicyMap(); policyMap.setDefaultEntry(defaultPolicy); broker.setDestinationPolicy(policyMap); broker.setDeleteAllMessagesOnStartup(deleteAllMessages); broker.addConnector("tcp://localhost:0"); broker.start(); broker.waitUntilStarted(); return broker; } }