/**
* 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.MessageProducer;
import javax.jms.Queue;
import javax.jms.ServerSession;
import javax.jms.ServerSessionPool;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.TransactionRolledBackException;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.Vector;
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 org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.ActiveMQMessageConsumer;
import org.apache.activemq.AutoFailTestSupport;
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.transport.TransportListener;
import org.apache.activemq.util.SocketProxy;
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.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// see https://issues.apache.org/activemq/browse/AMQ-2473
// https://issues.apache.org/activemq/browse/AMQ-2590
@RunWith(BMUnitRunner.class)
public class FailoverTransactionTest extends OpenwireArtemisBaseTest {
private static final Logger LOG = LoggerFactory.getLogger(FailoverTransactionTest.class);
private static final String QUEUE_NAME = "Failover.WithTx";
private String url = newURI(0);
private static final AtomicBoolean doByteman = new AtomicBoolean(false);
private static CountDownLatch brokerStopLatch;
private static SocketProxy proxy;
private static boolean firstSend;
private static int count;
private static volatile EmbeddedJMS broker;
@Before
public void setUp() throws Exception {
doByteman.set(false);
brokerStopLatch = new CountDownLatch(1);
}
@After
public void tearDown() throws Exception {
doByteman.set(false);
stopBroker();
}
public void stopBroker() throws Exception {
if (broker != null) {
broker.stop();
}
}
private void startCleanBroker() throws Exception {
startBroker();
}
public void startBroker() throws Exception {
broker = createBroker();
broker.start();
}
public void configureConnectionFactory(ActiveMQConnectionFactory factory) {
// nothing to do
}
@Test
public void testFailoverProducerCloseBeforeTransaction() throws Exception {
LOG.info(this + " running test testFailoverProducerCloseBeforeTransaction");
startCleanBroker();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Queue destination = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(destination);
produceMessage(session, destination);
// restart to force failover and connection state recovery before the commit
broker.stop();
startBroker();
session.commit();
Assert.assertNotNull("we got the message", consumer.receive(20000));
session.commit();
connection.close();
}
@Test
@BMRules(
rules = {@BMRule(
name = "set no return response and stop the broker",
targetClass = "org.apache.activemq.artemis.core.protocol.openwire.OpenWireConnection$CommandProcessor",
targetMethod = "processCommitTransactionOnePhase",
targetLocation = "EXIT",
action = "org.apache.activemq.transport.failover.FailoverTransactionTest.holdResponseAndStopBroker($0)")})
public void testFailoverCommitReplyLost() throws Exception {
LOG.info(this + " running test testFailoverCommitReplyLost");
broker = createBroker();
startBrokerWithDurableQueue();
doByteman.set(true);
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
final Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Queue destination = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(destination);
produceMessage(session, destination);
final CountDownLatch commitDoneLatch = new CountDownLatch(1);
// broker will die on commit reply so this will hang till restart
new Thread() {
@Override
public void run() {
LOG.info("doing async commit...");
try {
session.commit();
} catch (JMSException e) {
Assert.assertTrue(e instanceof TransactionRolledBackException);
LOG.info("got commit exception: ", e);
}
commitDoneLatch.countDown();
LOG.info("done async commit");
}
}.start();
// will be stopped by the plugin
brokerStopLatch.await(60, TimeUnit.SECONDS);
doByteman.set(false);
broker = createBroker();
broker.start();
Assert.assertTrue("tx committed through failover", commitDoneLatch.await(30, TimeUnit.SECONDS));
// new transaction
Message msg = consumer.receive(20000);
LOG.info("Received: " + msg);
Assert.assertNotNull("we got the message", msg);
Assert.assertNull("we got just one message", consumer.receive(2000));
session.commit();
consumer.close();
connection.close();
// ensure no dangling messages with fresh broker etc
broker.stop();
LOG.info("Checking for remaining/hung messages..");
broker = createBroker();
broker.start();
// after restart, ensure no dangling messages
cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
connection = cf.createConnection();
connection.start();
Session session2 = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
consumer = session2.createConsumer(destination);
msg = consumer.receive(1000);
if (msg == null) {
msg = consumer.receive(5000);
}
LOG.info("Received: " + msg);
Assert.assertNull("no messges left dangling but got: " + msg, msg);
connection.close();
}
@Test
public void testFailoverCommitReplyLostWithDestinationPathSeparator() throws Exception {
//the original test validates destinations using forward slash (/) as
//separators instead of dot (.). The broker internally uses a plugin
//called DestinationPathSeparatorBroker to convert every occurrence of
// "/" into "." inside the server.
//Artemis doesn't support "/" so far and this test doesn't make sense therefore.
}
@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",
binding = "owconn:OpenWireConnection = $0; context = owconn.getContext()",
action = "org.apache.activemq.transport.failover.FailoverTransactionTest.holdResponseAndStopBroker($0)")})
public void testFailoverSendReplyLost() throws Exception {
LOG.info(this + " running test testFailoverSendReplyLost");
broker = createBroker();
startBrokerWithDurableQueue();
doByteman.set(true);
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")?jms.watchTopicAdvisories=false");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
final Queue destination = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(destination);
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(session, destination);
} catch (JMSException e) {
//assertTrue(e instanceof TransactionRolledBackException);
LOG.error("got send exception: ", e);
Assert.fail("got unexpected send exception" + e);
}
sendDoneLatch.countDown();
LOG.info("done async send");
}
}.start();
// will be stopped by the plugin
brokerStopLatch.await(60, TimeUnit.SECONDS);
doByteman.set(false);
broker = createBroker();
LOG.info("restarting....");
broker.start();
Assert.assertTrue("message sent through failover", sendDoneLatch.await(30, TimeUnit.SECONDS));
// new transaction
Message msg = consumer.receive(20000);
LOG.info("Received: " + msg);
Assert.assertNotNull("we got the message", msg);
Assert.assertNull("we got just one message", consumer.receive(2000));
consumer.close();
connection.close();
// ensure no dangling messages with fresh broker etc
broker.stop();
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);
connection = cf.createConnection();
connection.start();
Session session2 = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
consumer = session2.createConsumer(destination);
msg = consumer.receive(1000);
if (msg == null) {
msg = consumer.receive(5000);
}
LOG.info("Received: " + msg);
Assert.assertNull("no messges left dangling but got: " + msg, msg);
connection.close();
}
@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.FailoverTransactionTest.holdResponseAndStopProxyOnFirstSend($0)")})
public void testFailoverConnectionSendReplyLost() throws Exception {
LOG.info(this + " running test testFailoverConnectionSendReplyLost");
broker = createBroker();
proxy = new SocketProxy();
firstSend = true;
startBrokerWithDurableQueue();
proxy.setTarget(new URI(url));
proxy.open();
doByteman.set(true);
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + proxy.getUrl().toASCIIString() + ")?jms.watchTopicAdvisories=false");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
final Queue destination = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(destination);
final CountDownLatch sendDoneLatch = new CountDownLatch(1);
// proxy connection will die on send reply so this will hang on failover reconnect till open
new Thread() {
@Override
public void run() {
LOG.info("doing async send...");
try {
produceMessage(session, destination);
} catch (JMSException e) {
//assertTrue(e instanceof TransactionRolledBackException);
LOG.info("got send exception: ", e);
}
sendDoneLatch.countDown();
LOG.info("done async send");
}
}.start();
// will be closed by the plugin
Assert.assertTrue("proxy was closed", proxy.waitUntilClosed(30));
LOG.info("restarting proxy");
proxy.open();
Assert.assertTrue("message sent through failover", sendDoneLatch.await(30, TimeUnit.SECONDS));
Message msg = consumer.receive(20000);
LOG.info("Received: " + msg);
Assert.assertNotNull("we got the message", msg);
Assert.assertNull("we got just one message", consumer.receive(2000));
consumer.close();
connection.close();
// ensure no dangling messages with fresh broker etc
broker.stop();
LOG.info("Checking for remaining/hung messages with restart..");
broker = createBroker();
broker.start();
// after restart, ensure no dangling messages
cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
connection = cf.createConnection();
connection.start();
Session session2 = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
consumer = session2.createConsumer(destination);
msg = consumer.receive(1000);
if (msg == null) {
msg = consumer.receive(5000);
}
LOG.info("Received: " + msg);
Assert.assertNull("no messges left dangling but got: " + msg, msg);
connection.close();
proxy.close();
}
@Test
public void testFailoverProducerCloseBeforeTransactionFailWhenDisabled() throws Exception {
LOG.info(this + " running test testFailoverProducerCloseBeforeTransactionFailWhenDisabled");
startCleanBroker();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")?trackTransactionProducers=false");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Queue destination = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(destination);
produceMessage(session, destination);
// restart to force failover and connection state recovery before the commit
broker.stop();
startBroker();
session.commit();
// without tracking producers, message will not be replayed on recovery
Assert.assertNull("we got the message", consumer.receive(5000));
session.commit();
connection.close();
}
@Test
public void testFailoverMultipleProducerCloseBeforeTransaction() throws Exception {
LOG.info(this + " running test testFailoverMultipleProducerCloseBeforeTransaction");
startCleanBroker();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Queue destination = session.createQueue(QUEUE_NAME);
MessageConsumer consumer = session.createConsumer(destination);
MessageProducer producer;
TextMessage message;
final int count = 10;
for (int i = 0; i < count; i++) {
producer = session.createProducer(destination);
message = session.createTextMessage("Test message: " + count);
producer.send(message);
producer.close();
}
// restart to force failover and connection state recovery before the commit
broker.stop();
startBroker();
session.commit();
for (int i = 0; i < count; i++) {
Assert.assertNotNull("we got all the message: " + count, consumer.receive(20000));
}
session.commit();
connection.close();
}
// https://issues.apache.org/activemq/browse/AMQ-2772
@Test
public void testFailoverWithConnectionConsumer() throws Exception {
LOG.info(this + " running test testFailoverWithConnectionConsumer");
startCleanBroker();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
final CountDownLatch connectionConsumerGotOne = new CountDownLatch(1);
try {
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
Queue destination = session.createQueue(QUEUE_NAME);
final Session poolSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
connection.createConnectionConsumer(destination, null, new ServerSessionPool() {
@Override
public ServerSession getServerSession() throws JMSException {
return new ServerSession() {
@Override
public Session getSession() throws JMSException {
return poolSession;
}
@Override
public void start() throws JMSException {
connectionConsumerGotOne.countDown();
poolSession.run();
}
};
}
}, 1);
MessageConsumer consumer = session.createConsumer(destination);
MessageProducer producer;
TextMessage message;
final int count = 10;
for (int i = 0; i < count; i++) {
producer = session.createProducer(destination);
message = session.createTextMessage("Test message: " + count);
producer.send(message);
producer.close();
}
// restart to force failover and connection state recovery before the commit
broker.stop();
startBroker();
session.commit();
for (int i = 0; i < count - 1; i++) {
Message received = consumer.receive(20000);
Assert.assertNotNull("Failed to get message: " + count, received);
}
session.commit();
} finally {
connection.close();
}
Assert.assertTrue("connectionconsumer did not get a message", connectionConsumerGotOne.await(10, TimeUnit.SECONDS));
}
// @Test
// @BMRules(
// rules = {
// @BMRule(
// name = "set no return response and stop the broker",
// targetClass = "org.apache.activemq.artemis.core.protocol.openwire.OpenWireConnection$CommandProcessor",
// targetMethod = "processMessageAck",
// targetLocation = "ENTRY",
// action = "org.apache.activemq.transport.failover.FailoverTransactionTest.holdResponseAndStopBroker($0)")
// }
// )
// public void testFailoverConsumerAckLost() throws Exception {
// LOG.info(this + " running test testFailoverConsumerAckLost");
// // as failure depends on hash order of state tracker recovery, do a few times
// for (int i = 0; i < 3; i++) {
// try {
// LOG.info("Iteration: " + i);
// doTestFailoverConsumerAckLost(i);
// }
// finally {
// stopBroker();
// }
// }
// }
//
public void doTestFailoverConsumerAckLost(final int pauseSeconds) throws Exception {
broker = createBroker();
broker.start();
brokerStopLatch = new CountDownLatch(1);
doByteman.set(true);
Vector<Connection> connections = new Vector<>();
Connection connection = null;
Message msg = null;
Queue destination = null;
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
try {
configureConnectionFactory(cf);
connection = cf.createConnection();
connection.start();
connections.add(connection);
final Session producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
destination = producerSession.createQueue(QUEUE_NAME + "?consumer.prefetchSize=1");
connection = cf.createConnection();
connection.start();
connections.add(connection);
final Session consumerSession1 = connection.createSession(true, Session.SESSION_TRANSACTED);
connection = cf.createConnection();
connection.start();
connections.add(connection);
final Session consumerSession2 = connection.createSession(true, Session.SESSION_TRANSACTED);
final MessageConsumer consumer1 = consumerSession1.createConsumer(destination);
final MessageConsumer consumer2 = consumerSession2.createConsumer(destination);
produceMessage(producerSession, destination);
produceMessage(producerSession, destination);
final Vector<Message> receivedMessages = new Vector<>();
final CountDownLatch commitDoneLatch = new CountDownLatch(1);
final AtomicBoolean gotTransactionRolledBackException = new AtomicBoolean(false);
Thread t = new Thread("doTestFailoverConsumerAckLost(" + pauseSeconds + ")") {
@Override
public void run() {
LOG.info("doing async commit after consume...");
try {
Message msg = consumer1.receive(20000);
LOG.info("consumer1 first attempt got message: " + msg);
receivedMessages.add(msg);
// give some variance to the runs
TimeUnit.SECONDS.sleep(pauseSeconds * 2);
// should not get a second message as there are two messages and two consumers
// and prefetch=1, but with failover and unordered connection restore it can get the second
// message.
// For the transaction to complete it needs to get the same one or two messages
// again so that the acks line up.
// If redelivery order is different, the commit should fail with an ex
//
msg = consumer1.receive(5000);
LOG.info("consumer1 second attempt got message: " + msg);
if (msg != null) {
receivedMessages.add(msg);
}
LOG.info("committing consumer1 session: " + receivedMessages.size() + " messsage(s)");
try {
consumerSession1.commit();
} catch (JMSException expectedSometimes) {
LOG.info("got exception ex on commit", expectedSometimes);
if (expectedSometimes instanceof TransactionRolledBackException) {
gotTransactionRolledBackException.set(true);
// ok, message one was not replayed so we expect the rollback
} else {
throw expectedSometimes;
}
}
commitDoneLatch.countDown();
LOG.info("done async commit");
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
// will be stopped by the plugin
brokerStopLatch.await(60, TimeUnit.SECONDS);
t.join(30000);
if (t.isAlive()) {
t.interrupt();
Assert.fail("Thread " + t.getName() + " is still alive");
}
broker = createBroker();
broker.start();
doByteman.set(false);
Assert.assertTrue("tx committed through failover", commitDoneLatch.await(30, TimeUnit.SECONDS));
LOG.info("received message count: " + receivedMessages.size());
// new transaction
msg = consumer1.receive(gotTransactionRolledBackException.get() ? 5000 : 20000);
LOG.info("post: from consumer1 received: " + msg);
if (gotTransactionRolledBackException.get()) {
Assert.assertNotNull("should be available again after commit rollback ex", msg);
} else {
Assert.assertNull("should be nothing left for consumer as receive should have committed", msg);
}
consumerSession1.commit();
if (gotTransactionRolledBackException.get() || !gotTransactionRolledBackException.get() && receivedMessages.size() == 1) {
// just one message successfully consumed or none consumed
// consumer2 should get other message
msg = consumer2.receive(10000);
LOG.info("post: from consumer2 received: " + msg);
Assert.assertNotNull("got second message on consumer2", msg);
consumerSession2.commit();
}
} finally {
for (Connection c : connections) {
c.close();
}
// ensure no dangling messages with fresh broker etc
if (broker != null) {
broker.stop();
}
}
LOG.info("Checking for remaining/hung messages..");
broker = createBroker();
broker.start();
// after restart, ensure no dangling messages
cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
connection = cf.createConnection();
try {
connection.start();
Session sweeperSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer sweeper = sweeperSession.createConsumer(destination);
msg = sweeper.receive(1000);
if (msg == null) {
msg = sweeper.receive(5000);
}
LOG.info("Sweep received: " + msg);
Assert.assertNull("no messges left dangling but got: " + msg, msg);
} finally {
connection.close();
broker.stop();
}
}
@Test
@BMRules(
rules = {@BMRule(
name = "set no return response and stop the broker",
targetClass = "org.apache.activemq.artemis.core.protocol.openwire.OpenWireConnection$CommandProcessor",
targetMethod = "processRemoveConsumer",
targetLocation = "ENTRY",
action = "org.apache.activemq.transport.failover.FailoverTransactionTest.stopBrokerOnCounter()")})
public void testPoolingNConsumesAfterReconnect() throws Exception {
LOG.info(this + " running test testPoolingNConsumesAfterReconnect");
count = 0;
broker = createBroker();
startBrokerWithDurableQueue();
doByteman.set(true);
Vector<Connection> connections = new Vector<>();
final ExecutorService executorService = Executors.newCachedThreadPool();
try {
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
Session producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
final Queue destination = producerSession.createQueue(QUEUE_NAME + "?consumer.prefetchSize=1");
produceMessage(producerSession, destination);
connection.close();
connection = cf.createConnection();
connection.start();
connections.add(connection);
final Session consumerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
final int sessionCount = 10;
final Stack<Session> sessions = new Stack<>();
for (int i = 0; i < sessionCount; i++) {
sessions.push(connection.createSession(false, Session.AUTO_ACKNOWLEDGE));
}
final int consumerCount = 1000;
final Deque<MessageConsumer> consumers = new ArrayDeque<>();
for (int i = 0; i < consumerCount; i++) {
consumers.push(consumerSession.createConsumer(destination));
}
final FailoverTransport failoverTransport = ((ActiveMQConnection) connection).getTransport().narrow(FailoverTransport.class);
final TransportListener delegate = failoverTransport.getTransportListener();
failoverTransport.setTransportListener(new TransportListener() {
@Override
public void onCommand(Object command) {
delegate.onCommand(command);
}
@Override
public void onException(IOException error) {
delegate.onException(error);
}
@Override
public void transportInterupted() {
LOG.error("Transport interrupted: " + failoverTransport, new RuntimeException("HERE"));
for (int i = 0; i < consumerCount && !consumers.isEmpty(); i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
MessageConsumer localConsumer = null;
try {
synchronized (delegate) {
localConsumer = consumers.pop();
}
localConsumer.receive(1);
LOG.info("calling close() " + ((ActiveMQMessageConsumer) localConsumer).getConsumerId());
localConsumer.close();
} catch (NoSuchElementException nse) {
} catch (Exception ignored) {
LOG.error("Ex on: " + ((ActiveMQMessageConsumer) localConsumer).getConsumerId(), ignored);
}
}
});
}
delegate.transportInterupted();
}
@Override
public void transportResumed() {
delegate.transportResumed();
}
});
MessageConsumer consumer = null;
synchronized (delegate) {
consumer = consumers.pop();
}
LOG.info("calling close to trigger broker stop " + ((ActiveMQMessageConsumer) consumer).getConsumerId());
consumer.close();
LOG.info("waiting latch: " + brokerStopLatch.getCount());
// will be stopped by the plugin
Assert.assertTrue(brokerStopLatch.await(60, TimeUnit.SECONDS));
doByteman.set(false);
broker = createBroker();
broker.start();
consumer = consumerSession.createConsumer(destination);
LOG.info("finally consuming message: " + ((ActiveMQMessageConsumer) consumer).getConsumerId());
Message msg = null;
for (int i = 0; i < 4 && msg == null; i++) {
msg = consumer.receive(1000);
}
LOG.info("post: from consumer1 received: " + msg);
Assert.assertNotNull("got message after failover", msg);
msg.acknowledge();
} finally {
executorService.shutdown();
for (Connection c : connections) {
c.close();
}
}
}
private void startBrokerWithDurableQueue() throws Exception {
broker.start();
//auto created queue can't survive a restart, so we need this
broker.getJMSServerManager().createQueue(false, QUEUE_NAME, null, true, QUEUE_NAME);
}
@Test
public void testAutoRollbackWithMissingRedeliveries() throws Exception {
LOG.info(this + " running test testAutoRollbackWithMissingRedeliveries");
broker = createBroker();
broker.start();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
try {
connection.start();
final Session producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
final Queue destination = producerSession.createQueue(QUEUE_NAME + "?consumer.prefetchSize=1");
final Session consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED);
MessageConsumer consumer = consumerSession.createConsumer(destination);
produceMessage(producerSession, destination);
Message msg = consumer.receive(20000);
Assert.assertNotNull(msg);
broker.stop();
broker = createBroker();
// use empty jdbc store so that default wait(0) for redeliveries will timeout after failover
broker.start();
try {
consumerSession.commit();
Assert.fail("expected transaction rolledback ex");
} catch (TransactionRolledBackException expected) {
}
broker.stop();
broker = createBroker();
broker.start();
Assert.assertNotNull("should get rolledback message from original restarted broker", consumer.receive(20000));
} finally {
connection.close();
}
}
@Test
public void testWaitForMissingRedeliveries() throws Exception {
LOG.info(this + " running test testWaitForMissingRedeliveries");
broker = createBroker();
broker.start();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")?jms.consumerFailoverRedeliveryWaitPeriod=30000");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
try {
connection.start();
final Session producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
final Queue destination = producerSession.createQueue(QUEUE_NAME);
final Session consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED);
MessageConsumer consumer = consumerSession.createConsumer(destination);
produceMessage(producerSession, destination);
Message msg = consumer.receive(20000);
if (msg == null) {
AutoFailTestSupport.dumpAllThreads("missing-");
}
Assert.assertNotNull("got message just produced", msg);
broker.stop();
broker = createBroker();
broker.start();
final CountDownLatch commitDone = new CountDownLatch(1);
final CountDownLatch gotException = new CountDownLatch(1);
// will block pending re-deliveries
new Thread() {
@Override
public void run() {
LOG.info("doing async commit...");
try {
consumerSession.commit();
commitDone.countDown();
}
catch (JMSException ignored) {
System.out.println("--->err: got exfeption:");
ignored.printStackTrace();
gotException.countDown();
}
finally {
commitDone.countDown();
}
}
}.start();
broker.stop();
broker = createBroker();
broker.start();
Assert.assertTrue("commit was successful", commitDone.await(30, TimeUnit.SECONDS));
Assert.assertTrue("got exception on commit", gotException.await(30, TimeUnit.SECONDS));
Assert.assertNotNull("should get failed committed message", consumer.receive(5000));
} finally {
connection.close();
}
}
@Test
public void testReDeliveryWhilePending() throws Exception {
LOG.info(this + " running test testReDeliveryWhilePending");
broker = createBroker();
broker.start();
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("failover:(" + url + ")?jms.consumerFailoverRedeliveryWaitPeriod=10000");
configureConnectionFactory(cf);
Connection connection = cf.createConnection();
connection.start();
final Session producerSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
final Queue destination = producerSession.createQueue(QUEUE_NAME + "?consumer.prefetchSize=0");
final Session consumerSession = connection.createSession(true, Session.SESSION_TRANSACTED);
MessageConsumer consumer = consumerSession.createConsumer(destination);
produceMessage(producerSession, destination);
Message msg = consumer.receive(20000);
if (msg == null) {
AutoFailTestSupport.dumpAllThreads("missing-");
}
Assert.assertNotNull("got message just produced", msg);
// add another consumer into the mix that may get the message after restart
MessageConsumer consumer2 = consumerSession.createConsumer(consumerSession.createQueue(QUEUE_NAME + "?consumer.prefetchSize=1"));
broker.stop();
broker = createBroker();
broker.start();
final CountDownLatch commitDone = new CountDownLatch(1);
final Vector<Exception> exceptions = new Vector<>();
// commit may fail if other consumer gets the message on restart
new Thread() {
@Override
public void run() {
LOG.info("doing async commit...");
try {
consumerSession.commit();
} catch (JMSException ex) {
exceptions.add(ex);
} finally {
commitDone.countDown();
}
}
}.start();
Assert.assertTrue("commit completed ", commitDone.await(15, TimeUnit.SECONDS));
// either message redelivered in existing tx or consumed by consumer2
// should not be available again in any event
Assert.assertNull("consumer should not get rolled back on non redelivered message or duplicate", consumer.receive(5000));
// consumer replay is hashmap order dependent on a failover connection state recover so need to deal with both cases
if (exceptions.isEmpty()) {
LOG.info("commit succeeded, message was redelivered to the correct consumer after restart so commit was fine");
Assert.assertNull("consumer2 not get a second message consumed by 1", consumer2.receive(2000));
} else {
LOG.info("commit failed, consumer2 should get it", exceptions.get(0));
Assert.assertNotNull("consumer2 got message", consumer2.receive(2000));
consumerSession.commit();
// no message should be in dlq
MessageConsumer dlqConsumer = consumerSession.createConsumer(consumerSession.createQueue("ActiveMQ.DLQ"));
Assert.assertNull("nothing in the dlq", dlqConsumer.receive(5000));
}
connection.close();
}
private void produceMessage(final Session producerSession, Queue destination) throws JMSException {
MessageProducer producer = producerSession.createProducer(destination);
TextMessage message = producerSession.createTextMessage("Test message");
producer.send(message);
producer.close();
}
public static void holdResponseAndStopBroker(final OpenWireConnection.CommandProcessor context) {
if (doByteman.get()) {
context.getContext().setDontSendReponse(true);
new Thread() {
@Override
public void run() {
LOG.info("Stopping broker post commit...");
try {
broker.stop();
broker = null;
} catch (Exception e) {
e.printStackTrace();
} finally {
brokerStopLatch.countDown();
}
}
}.start();
}
}
public static void holdResponseAndStopProxyOnFirstSend(final OpenWireConnection.CommandProcessor context) {
if (doByteman.get()) {
if (firstSend) {
firstSend = false;
context.getContext().setDontSendReponse(true);
new Thread() {
@Override
public void run() {
LOG.info("Stopping connection post send...");
try {
proxy.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
}
}
public static void stopBrokerOnCounter() {
LOG.info("in stopBrokerOnCounter, byteman " + doByteman.get() + " count " + count);
if (doByteman.get()) {
if (count++ == 1) {
LOG.info("ok stop broker...");
new Thread() {
@Override
public void run() {
try {
if (broker != null) {
broker.stop();
broker = null;
}
LOG.info("broker stopped.");
} catch (Exception e) {
e.printStackTrace();
} finally {
brokerStopLatch.countDown();
}
}
}.start();
}
}
}
}