/** * 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.transport.failover; import javax.jms.Connection; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.artemis.core.protocol.openwire.OpenWireConnection; import org.apache.activemq.artemis.jms.server.embedded.EmbeddedJMS; import org.apache.activemq.broker.artemiswrapper.OpenwireArtemisBaseTest; import org.apache.activemq.util.Wait; import org.jboss.byteman.contrib.bmunit.BMRule; import org.jboss.byteman.contrib.bmunit.BMRules; import org.jboss.byteman.contrib.bmunit.BMUnitRunner; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RunWith(BMUnitRunner.class) public class FailoverDuplicateTest extends OpenwireArtemisBaseTest { private static final Logger LOG = LoggerFactory.getLogger(FailoverDuplicateTest.class); private static final String QUEUE_NAME = "TestQueue"; private static final AtomicBoolean doByteman = new AtomicBoolean(false); private static final AtomicBoolean first = new AtomicBoolean(false); private static final CountDownLatch gotMessageLatch = new CountDownLatch(1); private static final CountDownLatch producersDone = new CountDownLatch(1); private String url = newURI(0); EmbeddedJMS broker; @After public void tearDown() throws Exception { stopBroker(); } public void stopBroker() throws Exception { if (broker != null) { broker.stop(); } } public void startBroker(boolean deleteAllMessagesOnStartup) throws Exception { broker = createBroker(); broker.start(); } public void startBroker() throws Exception { broker = createBroker(); broker.start(); } public void configureConnectionFactory(ActiveMQConnectionFactory factory) { factory.setAuditMaximumProducerNumber(2048); factory.setOptimizeAcknowledge(true); } @Test @BMRules( rules = {@BMRule( name = "set no return response and stop the broker", targetClass = "org.apache.activemq.artemis.core.protocol.openwire.OpenWireConnection$CommandProcessor", targetMethod = "processMessage", targetLocation = "EXIT", action = "org.apache.activemq.transport.failover.FailoverDuplicateTest.holdResponseAndStopConn($0)")}) public void testFailoverSendReplyLost() throws Exception { broker = createBroker(); broker.start(); doByteman.set(true); ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")?jms.watchTopicAdvisories=false"); configureConnectionFactory(cf); Connection sendConnection = cf.createConnection(); sendConnection.start(); final Session sendSession = sendConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); final Queue destination = sendSession.createQueue(QUEUE_NAME); final AtomicInteger receivedCount = new AtomicInteger(); MessageListener listener = new MessageListener() { @Override public void onMessage(Message message) { gotMessageLatch.countDown(); receivedCount.incrementAndGet(); } }; Connection receiveConnection; Session receiveSession = null; receiveConnection = cf.createConnection(); receiveConnection.start(); receiveSession = receiveConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); receiveSession.createConsumer(destination).setMessageListener(listener); final CountDownLatch sendDoneLatch = new CountDownLatch(1); // broker will die on send reply so this will hang till restart new Thread() { @Override public void run() { LOG.info("doing async send..."); try { produceMessage(sendSession, destination, "will resend", 1); } catch (JMSException e) { LOG.error("got send exception: ", e); Assert.fail("got unexpected send exception" + e); } sendDoneLatch.countDown(); LOG.info("done async send"); } }.start(); Assert.assertTrue("one message got through on time", gotMessageLatch.await(20, TimeUnit.SECONDS)); // send more messages, blow producer audit final int numProducers = 1050; final int numPerProducer = 2; final int totalSent = numPerProducer * numProducers + 1; for (int i = 0; i < numProducers; i++) { produceMessage(receiveSession, destination, "new producer " + i, numPerProducer); // release resend when we half done, cursor audit exhausted // and concurrent dispatch with the resend if (i == 1025) { LOG.info("count down producers done"); producersDone.countDown(); } } Assert.assertTrue("message sent complete through failover", sendDoneLatch.await(30, TimeUnit.SECONDS)); Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { LOG.info("received count:" + receivedCount.get()); return totalSent <= receivedCount.get(); } }); Assert.assertEquals("we got all produced messages", totalSent, receivedCount.get()); sendConnection.close(); receiveConnection.close(); // ensure no dangling messages with fresh broker etc broker.stop(); doByteman.set(false); LOG.info("Checking for remaining/hung messages with second restart.."); broker = createBroker(); broker.start(); // after restart, ensure no dangling messages cf = new ActiveMQConnectionFactory("failover:(" + url + ")"); configureConnectionFactory(cf); sendConnection = cf.createConnection(); sendConnection.start(); Session session2 = sendConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageConsumer consumer = session2.createConsumer(destination); Message msg = consumer.receive(1000); if (msg == null) { msg = consumer.receive(5000); } Assert.assertNull("no messges left dangling but got: " + msg, msg); sendConnection.close(); } private void produceMessage(final Session producerSession, Queue destination, final String text, final int count) throws JMSException { MessageProducer producer = producerSession.createProducer(destination); for (int i = 0; i < count; i++) { TextMessage message = producerSession.createTextMessage(text + ", count:" + i); producer.send(message); } producer.close(); } public static void holdResponseAndStopConn(final OpenWireConnection.CommandProcessor context) { if (doByteman.get()) { if (first.compareAndSet(false, true)) { context.getContext().setDontSendReponse(true); new Thread() { @Override public void run() { try { LOG.info("Waiting for recepit"); Assert.assertTrue("message received on time", gotMessageLatch.await(60, TimeUnit.SECONDS)); Assert.assertTrue("new producers done on time", producersDone.await(120, TimeUnit.SECONDS)); LOG.info("Stopping connection post send and receive and multiple producers"); context.getContext().getConnection().fail(null, "test Failoverduplicatetest"); } catch (Exception e) { e.printStackTrace(); } } }.start(); } } } }