/** * 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.concurrent.TimeUnit; import javax.jms.Connection; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import javax.management.ObjectName; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.command.ActiveMQQueue; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.store.kahadb.KahaDBPersistenceAdapter; import org.apache.activemq.store.kahadb.disk.journal.DataFile; import org.apache.activemq.store.kahadb.disk.journal.Journal; import org.apache.activemq.util.Wait; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AMQ4212Test { private static final Logger LOG = LoggerFactory.getLogger(AMQ4212Test.class); private BrokerService service; private String connectionUri; private ActiveMQConnectionFactory cf; private final int MSG_COUNT = 256; @Before public void setUp() throws IOException, Exception { createBroker(true, false); } public void createBroker(boolean deleteAllMessages, boolean recover) throws Exception { service = new BrokerService(); service.setBrokerName("InactiveSubTest"); service.setDeleteAllMessagesOnStartup(deleteAllMessages); service.setAdvisorySupport(false); service.setPersistent(true); service.setUseJmx(true); service.setKeepDurableSubsActive(false); KahaDBPersistenceAdapter pa=new KahaDBPersistenceAdapter(); File dataFile=new File("KahaDB"); pa.setDirectory(dataFile); pa.setJournalMaxFileLength(10*1024); pa.setCheckpointInterval(TimeUnit.SECONDS.toMillis(5)); pa.setCleanupInterval(TimeUnit.SECONDS.toMillis(5)); pa.setForceRecoverIndex(recover); pa.setPreallocationScope(Journal.PreallocationScope.ENTIRE_JOURNAL.name()); service.setPersistenceAdapter(pa); service.start(); service.waitUntilStarted(); connectionUri = "vm://InactiveSubTest?create=false"; cf = new ActiveMQConnectionFactory(connectionUri); } private void restartBroker() throws Exception { stopBroker(); createBroker(false, false); } private void recoverBroker() throws Exception { stopBroker(); createBroker(false, true); } @After public void stopBroker() throws Exception { if (service != null) { service.stop(); service.waitUntilStopped(); service = null; } } @Test public void testDurableSubPrefetchRecovered() throws Exception { ActiveMQQueue queue = new ActiveMQQueue("MyQueue"); ActiveMQTopic topic = new ActiveMQTopic("MyDurableTopic"); // Send to a Queue to create some journal files sendMessages(queue); LOG.info("There are currently [{}] journal log files.", getNumberOfJournalFiles()); createInactiveDurableSub(topic); assertTrue("Should have an inactive durable sub", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { ObjectName[] subs = service.getAdminView().getInactiveDurableTopicSubscribers(); return subs != null && subs.length == 1 ? true : false; } })); // Now send some more to the queue to create even more files. sendMessages(queue); LOG.info("There are currently [{}] journal log files.", getNumberOfJournalFiles()); assertTrue(getNumberOfJournalFiles() > 1); LOG.info("Restarting the broker."); restartBroker(); LOG.info("Restarted the broker."); LOG.info("There are currently [{}] journal log files.", getNumberOfJournalFiles()); assertTrue(getNumberOfJournalFiles() > 1); assertTrue("Should have an inactive durable sub", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { ObjectName[] subs = service.getAdminView().getInactiveDurableTopicSubscribers(); return subs != null && subs.length == 1 ? true : false; } })); // Clear out all queue data service.getAdminView().removeQueue(queue.getQueueName()); assertTrue("Less than two journal files expected, was " + getNumberOfJournalFiles(), Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return getNumberOfJournalFiles() <= 2; } }, TimeUnit.MINUTES.toMillis(2))); LOG.info("Sending {} Messages to the Topic.", MSG_COUNT); // Send some messages to the inactive destination sendMessages(topic); LOG.info("Attempt to consume {} messages from the Topic.", MSG_COUNT); assertEquals(MSG_COUNT, consumeFromInactiveDurableSub(topic)); LOG.info("Recovering the broker."); recoverBroker(); LOG.info("Recovering the broker."); assertTrue("Should have an inactive durable sub", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { ObjectName[] subs = service.getAdminView().getInactiveDurableTopicSubscribers(); return subs != null && subs.length == 1 ? true : false; } })); } @Test public void testDurableAcksNotDropped() throws Exception { ActiveMQQueue queue = new ActiveMQQueue("MyQueue"); ActiveMQTopic topic = new ActiveMQTopic("MyDurableTopic"); // Create durable sub in first data file. createInactiveDurableSub(topic); assertTrue("Should have an inactive durable sub", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { ObjectName[] subs = service.getAdminView().getInactiveDurableTopicSubscribers(); return subs != null && subs.length == 1 ? true : false; } })); // Send to a Topic sendMessages(topic, 1); // Send to a Queue to create some journal files sendMessages(queue); LOG.info("Before consume there are currently [{}] journal log files.", getNumberOfJournalFiles()); // Consume all the Messages leaving acks behind. consumeDurableMessages(topic, 1); LOG.info("After consume there are currently [{}] journal log files.", getNumberOfJournalFiles()); // Now send some more to the queue to create even more files. sendMessages(queue); LOG.info("More Queued. There are currently [{}] journal log files.", getNumberOfJournalFiles()); assertTrue(getNumberOfJournalFiles() > 1); LOG.info("Restarting the broker."); restartBroker(); LOG.info("Restarted the broker."); LOG.info("There are currently [{}] journal log files.", getNumberOfJournalFiles()); assertTrue(getNumberOfJournalFiles() > 1); assertTrue("Should have an inactive durable sub", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { ObjectName[] subs = service.getAdminView().getInactiveDurableTopicSubscribers(); return subs != null && subs.length == 1 ? true : false; } })); // Clear out all queue data service.getAdminView().removeQueue(queue.getQueueName()); assertTrue("Less than three journal file expected, was " + getNumberOfJournalFiles(), Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return getNumberOfJournalFiles() <= 3; } }, TimeUnit.MINUTES.toMillis(3))); // See if we receive any message they should all be acked. tryConsumeExpectNone(topic); LOG.info("There are currently [{}] journal log files.", getNumberOfJournalFiles()); LOG.info("Recovering the broker."); recoverBroker(); LOG.info("Recovering the broker."); LOG.info("There are currently [{}] journal log files.", getNumberOfJournalFiles()); assertTrue("Should have an inactive durable sub", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { ObjectName[] subs = service.getAdminView().getInactiveDurableTopicSubscribers(); return subs != null && subs.length == 1 ? true : false; } })); // See if we receive any message they should all be acked. tryConsumeExpectNone(topic); assertTrue("Less than three journal file expected, was " + getNumberOfJournalFiles(), Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return getNumberOfJournalFiles() == 1; } }, TimeUnit.MINUTES.toMillis(1))); } private int getNumberOfJournalFiles() throws IOException { Collection<DataFile> files = ((KahaDBPersistenceAdapter) service.getPersistenceAdapter()).getStore().getJournal().getFileMap().values(); int reality = 0; for (DataFile file : files) { if (file != null) { reality++; } } return reality; } private void createInactiveDurableSub(Topic topic) throws Exception { Connection connection = cf.createConnection(); connection.setClientID("Inactive"); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer consumer = session.createDurableSubscriber(topic, "Inactive"); consumer.close(); connection.close(); } private void consumeDurableMessages(Topic topic, int count) throws Exception { Connection connection = cf.createConnection(); connection.setClientID("Inactive"); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer consumer = session.createDurableSubscriber(topic, "Inactive"); connection.start(); for (int i = 0; i < count; ++i) { if (consumer.receive(TimeUnit.SECONDS.toMillis(10)) == null) { fail("should have received a message"); } } consumer.close(); connection.close(); } private void tryConsumeExpectNone(Topic topic) throws Exception { Connection connection = cf.createConnection(); connection.setClientID("Inactive"); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer consumer = session.createDurableSubscriber(topic, "Inactive"); connection.start(); if (consumer.receive(TimeUnit.SECONDS.toMillis(10)) != null) { fail("Should be no messages for this durable."); } consumer.close(); connection.close(); } private int consumeFromInactiveDurableSub(Topic topic) throws Exception { Connection connection = cf.createConnection(); connection.setClientID("Inactive"); connection.start(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer consumer = session.createDurableSubscriber(topic, "Inactive"); int count = 0; while (consumer.receive(10000) != null) { count++; } consumer.close(); connection.close(); return count; } private void sendMessages(Destination destination) throws Exception { sendMessages(destination, MSG_COUNT); } private void sendMessages(Destination destination, int count) throws Exception { Connection connection = cf.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = session.createProducer(destination); producer.setDeliveryMode(DeliveryMode.PERSISTENT); for (int i = 0; i < count; ++i) { TextMessage message = session.createTextMessage("Message #" + i + " for destination: " + destination); producer.send(message); } connection.close(); } }