// Copyright (c) 2007-Present Pivotal Software, Inc. All rights reserved.
//
// This software, the RabbitMQ Java client library, is triple-licensed under the
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// info@rabbitmq.com.
package com.rabbitmq.client.test.functional;
import com.rabbitmq.client.*;
import com.rabbitmq.client.impl.NetworkConnection;
import com.rabbitmq.client.impl.recovery.*;
import com.rabbitmq.client.test.BrokerTestCase;
import com.rabbitmq.client.test.TestUtils;
import com.rabbitmq.tools.Host;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.*;
@SuppressWarnings("ThrowFromFinallyBlock")
public class ConnectionRecovery extends BrokerTestCase {
private static final long RECOVERY_INTERVAL = 2000;
@Test public void connectionRecovery() throws IOException, InterruptedException {
assertTrue(connection.isOpen());
closeAndWaitForRecovery();
assertTrue(connection.isOpen());
}
@Test public void namedConnectionRecovery()
throws IOException, InterruptedException, TimeoutException {
String connectionName = "custom name";
RecoverableConnection c = newRecoveringConnection(connectionName);
try {
assertTrue(c.isOpen());
assertEquals(connectionName, c.getClientProvidedName());
closeAndWaitForRecovery(c);
assertTrue(c.isOpen());
assertEquals(connectionName, c.getClientProvidedName());
} finally {
c.abort();
}
}
@Test public void connectionRecoveryWithServerRestart() throws IOException, InterruptedException {
assertTrue(connection.isOpen());
restartPrimaryAndWaitForRecovery();
assertTrue(connection.isOpen());
}
@Test public void connectionRecoveryWithArrayOfAddresses()
throws IOException, InterruptedException, TimeoutException {
final Address[] addresses = {new Address("127.0.0.1"), new Address("127.0.0.1", 5672)};
RecoverableConnection c = newRecoveringConnection(addresses);
try {
assertTrue(c.isOpen());
closeAndWaitForRecovery(c);
assertTrue(c.isOpen());
} finally {
c.abort();
}
}
@Test public void connectionRecoveryWithListOfAddresses()
throws IOException, InterruptedException, TimeoutException {
final List<Address> addresses = Arrays.asList(new Address("127.0.0.1"), new Address("127.0.0.1", 5672));
RecoverableConnection c = newRecoveringConnection(addresses);
try {
assertTrue(c.isOpen());
closeAndWaitForRecovery(c);
assertTrue(c.isOpen());
} finally {
c.abort();
}
}
@Test public void connectionRecoveryWithDisabledTopologyRecovery()
throws IOException, InterruptedException, TimeoutException {
RecoverableConnection c = newRecoveringConnection(true);
Channel ch = c.createChannel();
String q = "java-client.test.recovery.q2";
ch.queueDeclare(q, false, true, false, null);
ch.queueDeclarePassive(q);
assertTrue(c.isOpen());
try {
CountDownLatch shutdownLatch = prepareForShutdown(c);
CountDownLatch recoveryLatch = prepareForRecovery(c);
Host.closeConnection((NetworkConnection) c);
wait(shutdownLatch);
wait(recoveryLatch);
assertTrue(c.isOpen());
ch.queueDeclarePassive(q);
fail("expected passive declaration to throw");
} catch (java.io.IOException e) {
// expected
} finally {
c.abort();
}
}
// see https://github.com/rabbitmq/rabbitmq-java-client/issues/135
@Test public void thatShutdownHooksOnConnectionFireBeforeRecoveryStarts() throws IOException, InterruptedException {
final List<String> events = new CopyOnWriteArrayList<String>();
final CountDownLatch latch = new CountDownLatch(2); // one when started, another when complete
connection.addShutdownListener(new ShutdownListener() {
public void shutdownCompleted(ShutdownSignalException cause) {
events.add("shutdown hook 1");
}
});
connection.addShutdownListener(new ShutdownListener() {
public void shutdownCompleted(ShutdownSignalException cause) {
events.add("shutdown hook 2");
}
});
// note: we do not want to expose RecoveryCanBeginListener so this
// test does not use it
final CountDownLatch recoveryCanBeginLatch = new CountDownLatch(1);
((AutorecoveringConnection)connection).getDelegate().addRecoveryCanBeginListener(new RecoveryCanBeginListener() {
@Override
public void recoveryCanBegin(ShutdownSignalException cause) {
events.add("recovery start hook 1");
recoveryCanBeginLatch.countDown();
}
});
((RecoverableConnection)connection).addRecoveryListener(new RecoveryListener() {
@Override
public void handleRecovery(Recoverable recoverable) {
latch.countDown();
}
@Override
public void handleRecoveryStarted(Recoverable recoverable) {
latch.countDown();
}
});
assertTrue(connection.isOpen());
closeAndWaitForRecovery();
assertTrue(connection.isOpen());
assertEquals("shutdown hook 1", events.get(0));
assertEquals("shutdown hook 2", events.get(1));
recoveryCanBeginLatch.await(5, TimeUnit.SECONDS);
assertEquals("recovery start hook 1", events.get(2));
connection.close();
wait(latch);
}
@Test public void shutdownHooksRecoveryOnConnection() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(2);
connection.addShutdownListener(new ShutdownListener() {
public void shutdownCompleted(ShutdownSignalException cause) {
latch.countDown();
}
});
assertTrue(connection.isOpen());
closeAndWaitForRecovery();
assertTrue(connection.isOpen());
connection.close();
wait(latch);
}
@Test public void shutdownHooksRecoveryOnChannel() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(3);
channel.addShutdownListener(new ShutdownListener() {
public void shutdownCompleted(ShutdownSignalException cause) {
latch.countDown();
}
});
assertTrue(connection.isOpen());
closeAndWaitForRecovery();
assertTrue(connection.isOpen());
closeAndWaitForRecovery();
assertTrue(connection.isOpen());
connection.close();
wait(latch);
}
@Test public void blockedListenerRecovery() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(2);
connection.addBlockedListener(new BlockedListener() {
public void handleBlocked(String reason) throws IOException {
latch.countDown();
}
public void handleUnblocked() throws IOException {
latch.countDown();
}
});
closeAndWaitForRecovery();
block();
channel.basicPublish("", "", null, "".getBytes());
unblock();
wait(latch);
}
@Test public void channelRecovery() throws IOException, InterruptedException {
Channel ch1 = connection.createChannel();
Channel ch2 = connection.createChannel();
assertTrue(ch1.isOpen());
assertTrue(ch2.isOpen());
closeAndWaitForRecovery();
expectChannelRecovery(ch1);
expectChannelRecovery(ch2);
}
@Test public void returnListenerRecovery() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode, String replyText, String exchange,
String routingKey, AMQP.BasicProperties properties,
byte[] body) throws IOException {
latch.countDown();
}
});
closeAndWaitForRecovery();
expectChannelRecovery(channel);
channel.basicPublish("", "unknown", true, false, null, "mandatory1".getBytes());
wait(latch);
}
@Test public void confirmListenerRecovery() throws IOException, InterruptedException, TimeoutException {
final CountDownLatch latch = new CountDownLatch(1);
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
latch.countDown();
}
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
latch.countDown();
}
});
String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue();
closeAndWaitForRecovery();
expectChannelRecovery(channel);
channel.confirmSelect();
basicPublishVolatile(q);
waitForConfirms(channel);
wait(latch);
}
@Test public void exchangeRecovery() throws IOException, InterruptedException, TimeoutException {
Channel ch = connection.createChannel();
String x = "java-client.test.recovery.x1";
declareExchange(ch, x);
closeAndWaitForRecovery();
expectChannelRecovery(ch);
expectExchangeRecovery(ch, x);
ch.exchangeDelete(x);
}
@Test public void exchangeRecoveryWithNoWait() throws IOException, InterruptedException, TimeoutException {
Channel ch = connection.createChannel();
String x = "java-client.test.recovery.x1-nowait";
declareExchangeNoWait(ch, x);
closeAndWaitForRecovery();
expectChannelRecovery(ch);
expectExchangeRecovery(ch, x);
ch.exchangeDelete(x);
}
@Test public void clientNamedQueueRecovery() throws IOException, InterruptedException, TimeoutException {
testClientNamedQueueRecoveryWith("java-client.test.recovery.q1", false);
}
@Test public void clientNamedQueueRecoveryWithNoWait() throws IOException, InterruptedException, TimeoutException {
testClientNamedQueueRecoveryWith("java-client.test.recovery.q1-nowait", true);
}
private void testClientNamedQueueRecoveryWith(String q, boolean noWait) throws IOException, InterruptedException, TimeoutException {
Channel ch = connection.createChannel();
if(noWait) {
declareClientNamedQueueNoWait(ch, q);
} else {
declareClientNamedQueue(ch, q);
}
closeAndWaitForRecovery();
expectChannelRecovery(ch);
expectQueueRecovery(ch, q);
ch.queueDelete(q);
}
@Test public void clientNamedQueueBindingRecovery() throws IOException, InterruptedException, TimeoutException {
String q = "java-client.test.recovery.q2";
String x = "tmp-fanout";
Channel ch = connection.createChannel();
ch.queueDelete(q);
ch.exchangeDelete(x);
ch.exchangeDeclare(x, "fanout");
declareClientNamedAutoDeleteQueue(ch, q);
ch.queueBind(q, x, "");
closeAndWaitForRecovery();
expectChannelRecovery(ch);
expectAutoDeleteQueueAndBindingRecovery(ch, x, q);
ch.queueDelete(q);
ch.exchangeDelete(x);
}
// bug 26552
@Test public void clientNamedTransientAutoDeleteQueueAndBindingRecovery() throws IOException, InterruptedException, TimeoutException {
String q = UUID.randomUUID().toString();
String x = "tmp-fanout";
Channel ch = connection.createChannel();
ch.queueDelete(q);
ch.exchangeDelete(x);
ch.exchangeDeclare(x, "fanout");
ch.queueDeclare(q, false, false, true, null);
ch.queueBind(q, x, "");
restartPrimaryAndWaitForRecovery();
expectChannelRecovery(ch);
ch.confirmSelect();
ch.queuePurge(q);
ch.exchangeDeclare(x, "fanout");
ch.basicPublish(x, "", null, "msg".getBytes());
waitForConfirms(ch);
AMQP.Queue.DeclareOk ok = ch.queueDeclare(q, false, false, true, null);
assertEquals(1, ok.getMessageCount());
ch.queueDelete(q);
ch.exchangeDelete(x);
}
// bug 26552
@Test public void serverNamedTransientAutoDeleteQueueAndBindingRecovery() throws IOException, InterruptedException, TimeoutException {
String x = "tmp-fanout";
Channel ch = connection.createChannel();
ch.exchangeDelete(x);
ch.exchangeDeclare(x, "fanout");
String q = ch.queueDeclare("", false, false, true, null).getQueue();
final AtomicReference<String> nameBefore = new AtomicReference<String>(q);
final AtomicReference<String> nameAfter = new AtomicReference<String>();
final CountDownLatch listenerLatch = new CountDownLatch(1);
((AutorecoveringConnection)connection).addQueueRecoveryListener(new QueueRecoveryListener() {
@Override
public void queueRecovered(String oldName, String newName) {
nameBefore.set(oldName);
nameAfter.set(newName);
listenerLatch.countDown();
}
});
ch.queueBind(nameBefore.get(), x, "");
restartPrimaryAndWaitForRecovery();
expectChannelRecovery(ch);
ch.confirmSelect();
ch.exchangeDeclare(x, "fanout");
ch.basicPublish(x, "", null, "msg".getBytes());
waitForConfirms(ch);
AMQP.Queue.DeclareOk ok = ch.queueDeclarePassive(nameAfter.get());
assertEquals(1, ok.getMessageCount());
ch.queueDelete(nameAfter.get());
ch.exchangeDelete(x);
}
@Test public void declarationOfManyAutoDeleteQueuesWithTransientConsumer() throws IOException, TimeoutException {
Channel ch = connection.createChannel();
assertRecordedQueues(connection, 0);
for(int i = 0; i < 5000; i++) {
String q = UUID.randomUUID().toString();
ch.queueDeclare(q, false, false, true, null);
DefaultConsumer dummy = new DefaultConsumer(ch);
String tag = ch.basicConsume(q, true, dummy);
ch.basicCancel(tag);
}
assertRecordedQueues(connection, 0);
ch.close();
}
@Test public void declarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreUnbound() throws IOException, TimeoutException {
Channel ch = connection.createChannel();
assertRecordedExchanges(connection, 0);
for(int i = 0; i < 5000; i++) {
String x = UUID.randomUUID().toString();
ch.exchangeDeclare(x, "fanout", false, true, null);
String q = ch.queueDeclare().getQueue();
final String rk = "doesn't matter";
ch.queueBind(q, x, rk);
ch.queueUnbind(q, x, rk);
ch.queueDelete(q);
}
assertRecordedExchanges(connection, 0);
ch.close();
}
@Test public void declarationOfManyAutoDeleteExchangesWithTransientQueuesThatAreDeleted() throws IOException, TimeoutException {
Channel ch = connection.createChannel();
assertRecordedExchanges(connection, 0);
for(int i = 0; i < 5000; i++) {
String x = UUID.randomUUID().toString();
ch.exchangeDeclare(x, "fanout", false, true, null);
String q = ch.queueDeclare().getQueue();
ch.queueBind(q, x, "doesn't matter");
ch.queueDelete(q);
}
assertRecordedExchanges(connection, 0);
ch.close();
}
@Test public void declarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreUnbound() throws IOException, TimeoutException {
Channel ch = connection.createChannel();
assertRecordedExchanges(connection, 0);
for(int i = 0; i < 5000; i++) {
String src = "src-" + UUID.randomUUID().toString();
String dest = "dest-" + UUID.randomUUID().toString();
ch.exchangeDeclare(src, "fanout", false, true, null);
ch.exchangeDeclare(dest, "fanout", false, true, null);
final String rk = "doesn't matter";
ch.exchangeBind(dest, src, rk);
ch.exchangeUnbind(dest, src, rk);
ch.exchangeDelete(dest);
}
assertRecordedExchanges(connection, 0);
ch.close();
}
@Test public void declarationOfManyAutoDeleteExchangesWithTransientExchangesThatAreDeleted() throws IOException, TimeoutException {
Channel ch = connection.createChannel();
assertRecordedExchanges(connection, 0);
for(int i = 0; i < 5000; i++) {
String src = "src-" + UUID.randomUUID().toString();
String dest = "dest-" + UUID.randomUUID().toString();
ch.exchangeDeclare(src, "fanout", false, true, null);
ch.exchangeDeclare(dest, "fanout", false, true, null);
ch.exchangeBind(dest, src, "doesn't matter");
ch.exchangeDelete(dest);
}
assertRecordedExchanges(connection, 0);
ch.close();
}
@Test public void serverNamedQueueRecovery() throws IOException, InterruptedException {
String q = channel.queueDeclare("", false, false, false, null).getQueue();
String x = "amq.fanout";
channel.queueBind(q, x, "");
final AtomicReference<String> nameBefore = new AtomicReference<String>();
final AtomicReference<String> nameAfter = new AtomicReference<String>();
final CountDownLatch listenerLatch = new CountDownLatch(1);
((AutorecoveringConnection)connection).addQueueRecoveryListener(new QueueRecoveryListener() {
@Override
public void queueRecovered(String oldName, String newName) {
nameBefore.set(oldName);
nameAfter.set(newName);
listenerLatch.countDown();
}
});
closeAndWaitForRecovery();
wait(listenerLatch);
expectChannelRecovery(channel);
channel.basicPublish(x, "", null, "msg".getBytes());
assertDelivered(q, 1);
assertFalse(nameBefore.get().equals(nameAfter.get()));
channel.queueDelete(q);
}
@Test public void exchangeToExchangeBindingRecovery() throws IOException, InterruptedException {
String q = channel.queueDeclare("", false, false, false, null).getQueue();
String x1 = "amq.fanout";
String x2 = generateExchangeName();
channel.exchangeDeclare(x2, "fanout");
channel.exchangeBind(x1, x2, "");
channel.queueBind(q, x1, "");
try {
closeAndWaitForRecovery();
expectChannelRecovery(channel);
channel.basicPublish(x2, "", null, "msg".getBytes());
assertDelivered(q, 1);
} finally {
channel.exchangeDelete(x2);
channel.queueDelete(q);
}
}
@Test public void thatDeletedQueueBindingsDontReappearOnRecovery() throws IOException, InterruptedException {
String q = channel.queueDeclare("", false, false, false, null).getQueue();
String x1 = "amq.fanout";
String x2 = generateExchangeName();
channel.exchangeDeclare(x2, "fanout");
channel.exchangeBind(x1, x2, "");
channel.queueBind(q, x1, "");
channel.queueUnbind(q, x1, "");
try {
closeAndWaitForRecovery();
expectChannelRecovery(channel);
channel.basicPublish(x2, "", null, "msg".getBytes());
assertDelivered(q, 0);
} finally {
channel.exchangeDelete(x2);
channel.queueDelete(q);
}
}
@Test public void thatDeletedExchangeBindingsDontReappearOnRecovery() throws IOException, InterruptedException {
String q = channel.queueDeclare("", false, false, false, null).getQueue();
String x1 = "amq.fanout";
String x2 = generateExchangeName();
channel.exchangeDeclare(x2, "fanout");
channel.exchangeBind(x1, x2, "");
channel.queueBind(q, x1, "");
channel.exchangeUnbind(x1, x2, "");
try {
closeAndWaitForRecovery();
expectChannelRecovery(channel);
channel.basicPublish(x2, "", null, "msg".getBytes());
assertDelivered(q, 0);
} finally {
channel.exchangeDelete(x2);
channel.queueDelete(q);
}
}
@Test public void thatDeletedExchangeDoesNotReappearOnRecover() throws IOException, InterruptedException {
String x = generateExchangeName();
channel.exchangeDeclare(x, "fanout");
channel.exchangeDelete(x);
try {
closeAndWaitForRecovery();
expectChannelRecovery(channel);
channel.exchangeDeclarePassive(x);
fail("Expected passive declare to fail");
} catch (IOException ioe) {
// expected
}
}
@Test public void thatDeletedQueueDoesNotReappearOnRecover() throws IOException, InterruptedException {
String q = channel.queueDeclare().getQueue();
channel.queueDelete(q);
try {
closeAndWaitForRecovery();
expectChannelRecovery(channel);
channel.queueDeclarePassive(q);
fail("Expected passive declare to fail");
} catch (IOException ioe) {
// expected
}
}
@Test public void thatCancelledConsumerDoesNotReappearOnRecover() throws IOException, InterruptedException {
String q = UUID.randomUUID().toString();
channel.queueDeclare(q, false, false, false, null);
String tag = channel.basicConsume(q, new DefaultConsumer(channel));
assertConsumerCount(1, q);
channel.basicCancel(tag);
closeAndWaitForRecovery();
expectChannelRecovery(channel);
assertConsumerCount(0, q);
}
@Test public void consumerRecoveryWithManyConsumers() throws IOException, InterruptedException {
String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue();
final int n = 1024;
for (int i = 0; i < n; i++) {
channel.basicConsume(q, new DefaultConsumer(channel));
}
final AtomicReference<String> tagA = new AtomicReference<String>();
final AtomicReference<String> tagB = new AtomicReference<String>();
final CountDownLatch listenerLatch = new CountDownLatch(n);
((AutorecoveringConnection)connection).addConsumerRecoveryListener(new ConsumerRecoveryListener() {
@Override
public void consumerRecovered(String oldConsumerTag, String newConsumerTag) {
tagA.set(oldConsumerTag);
tagB.set(newConsumerTag);
listenerLatch.countDown();
}
});
assertConsumerCount(n, q);
closeAndWaitForRecovery();
wait(listenerLatch);
assertTrue(tagA.get().equals(tagB.get()));
expectChannelRecovery(channel);
assertConsumerCount(n, q);
}
@Test public void subsequentRecoveriesWithClientNamedQueue() throws IOException, InterruptedException {
String q = channel.queueDeclare(UUID.randomUUID().toString(), false, false, false, null).getQueue();
assertConsumerCount(0, q);
channel.basicConsume(q, new DefaultConsumer(channel));
for(int i = 0; i < 10; i++) {
assertConsumerCount(1, q);
closeAndWaitForRecovery();
}
channel.queueDelete(q);
}
@Test public void queueRecoveryWithManyQueues() throws IOException, InterruptedException, TimeoutException {
List<String> qs = new ArrayList<String>();
final int n = 1024;
for (int i = 0; i < n; i++) {
qs.add(channel.queueDeclare(UUID.randomUUID().toString(), true, false, false, null).getQueue());
}
closeAndWaitForRecovery();
expectChannelRecovery(channel);
for(String q : qs) {
expectQueueRecovery(channel, q);
channel.queueDelete(q);
}
}
@Test public void channelRecoveryCallback() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(2);
final CountDownLatch startLatch = new CountDownLatch(2);
final RecoveryListener listener = new RecoveryListener() {
public void handleRecovery(Recoverable recoverable) {
latch.countDown();
}
public void handleRecoveryStarted(Recoverable recoverable) {
startLatch.countDown();
}
};
RecoverableChannel ch1 = (RecoverableChannel) connection.createChannel();
ch1.addRecoveryListener(listener);
RecoverableChannel ch2 = (RecoverableChannel) connection.createChannel();
ch2.addRecoveryListener(listener);
assertTrue(ch1.isOpen());
assertTrue(ch2.isOpen());
closeAndWaitForRecovery();
expectChannelRecovery(ch1);
expectChannelRecovery(ch2);
wait(latch);
wait(startLatch);
}
@Test public void basicAckAfterChannelRecovery() throws IOException, InterruptedException, TimeoutException {
final AtomicInteger consumed = new AtomicInteger(0);
int n = 5;
final CountDownLatch latch = new CountDownLatch(n);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
try {
if (consumed.intValue() > 0 && consumed.intValue() % 4 == 0) {
CountDownLatch recoveryLatch = prepareForRecovery(connection);
Host.closeConnection((AutorecoveringConnection)connection);
ConnectionRecovery.wait(recoveryLatch);
}
channel.basicAck(envelope.getDeliveryTag(), false);
} catch (InterruptedException e) {
// ignore
} finally {
consumed.incrementAndGet();
latch.countDown();
}
}
};
String q = channel.queueDeclare().getQueue();
channel.basicConsume(q, consumer);
RecoverableConnection publishingConnection = newRecoveringConnection(false);
Channel publishingChannel = publishingConnection.createChannel();
for (int i = 0; i < n; i++) {
publishingChannel.basicPublish("", q, null, "msg".getBytes());
}
wait(latch);
publishingConnection.abort();
}
@Test public void consumersAreRemovedFromConnectionWhenChannelIsClosed() throws Exception {
RecoverableConnection connection = newRecoveringConnection(true);
try {
Field consumersField = AutorecoveringConnection.class.getDeclaredField("consumers");
consumersField.setAccessible(true);
Map<?, ?> connectionConsumers = (Map<?, ?>) consumersField.get(connection);
Channel channel1 = connection.createChannel();
Channel channel2 = connection.createChannel();
assertEquals(0, connectionConsumers.size());
String queue = channel1.queueDeclare().getQueue();
channel1.basicConsume(queue, true, new HashMap<String, Object>(), new DefaultConsumer(channel1));
assertEquals(1, connectionConsumers.size());
channel1.basicConsume(queue, true, new HashMap<String, Object>(), new DefaultConsumer(channel1));
assertEquals(2, connectionConsumers.size());
channel2.basicConsume(queue, true, new HashMap<String, Object>(), new DefaultConsumer(channel2));
assertEquals(3, connectionConsumers.size());
channel1.close();
assertEquals(3 - 2, connectionConsumers.size());
channel2.close();
assertEquals(0, connectionConsumers.size());
} finally {
connection.abort();
}
}
private void assertConsumerCount(int exp, String q) throws IOException {
assertEquals(exp, channel.queueDeclarePassive(q).getConsumerCount());
}
private AMQP.Queue.DeclareOk declareClientNamedQueue(Channel ch, String q) throws IOException {
return ch.queueDeclare(q, true, false, false, null);
}
private AMQP.Queue.DeclareOk declareClientNamedAutoDeleteQueue(Channel ch, String q) throws IOException {
return ch.queueDeclare(q, true, false, true, null);
}
private void declareClientNamedQueueNoWait(Channel ch, String q) throws IOException {
ch.queueDeclareNoWait(q, true, false, false, null);
}
private AMQP.Exchange.DeclareOk declareExchange(Channel ch, String x) throws IOException {
return ch.exchangeDeclare(x, "fanout", false);
}
private void declareExchangeNoWait(Channel ch, String x) throws IOException {
ch.exchangeDeclareNoWait(x, "fanout", false, false, false, null);
}
private void expectQueueRecovery(Channel ch, String q) throws IOException, InterruptedException, TimeoutException {
ch.confirmSelect();
ch.queuePurge(q);
AMQP.Queue.DeclareOk ok1 = declareClientNamedQueue(ch, q);
assertEquals(0, ok1.getMessageCount());
ch.basicPublish("", q, null, "msg".getBytes());
waitForConfirms(ch);
AMQP.Queue.DeclareOk ok2 = declareClientNamedQueue(ch, q);
assertEquals(1, ok2.getMessageCount());
}
private void expectAutoDeleteQueueAndBindingRecovery(Channel ch, String x, String q) throws IOException, InterruptedException,
TimeoutException {
ch.confirmSelect();
ch.queuePurge(q);
AMQP.Queue.DeclareOk ok1 = declareClientNamedAutoDeleteQueue(ch, q);
assertEquals(0, ok1.getMessageCount());
ch.exchangeDeclare(x, "fanout");
ch.basicPublish(x, "", null, "msg".getBytes());
waitForConfirms(ch);
AMQP.Queue.DeclareOk ok2 = declareClientNamedAutoDeleteQueue(ch, q);
assertEquals(1, ok2.getMessageCount());
}
private void expectExchangeRecovery(Channel ch, String x) throws IOException, InterruptedException, TimeoutException {
ch.confirmSelect();
String q = ch.queueDeclare().getQueue();
final String rk = "routing-key";
ch.queueBind(q, x, rk);
ch.basicPublish(x, rk, null, "msg".getBytes());
waitForConfirms(ch);
ch.exchangeDeclarePassive(x);
}
private CountDownLatch prepareForRecovery(Connection conn) {
final CountDownLatch latch = new CountDownLatch(1);
((AutorecoveringConnection)conn).addRecoveryListener(new RecoveryListener() {
public void handleRecovery(Recoverable recoverable) {
latch.countDown();
}
public void handleRecoveryStarted(Recoverable recoverable) {
// No-op
}
});
return latch;
}
private CountDownLatch prepareForShutdown(Connection conn) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
conn.addShutdownListener(new ShutdownListener() {
public void shutdownCompleted(ShutdownSignalException cause) {
latch.countDown();
}
});
return latch;
}
private void closeAndWaitForRecovery() throws IOException, InterruptedException {
closeAndWaitForRecovery((AutorecoveringConnection)this.connection);
}
private void closeAndWaitForRecovery(RecoverableConnection connection) throws IOException, InterruptedException {
CountDownLatch latch = prepareForRecovery(connection);
Host.closeConnection((NetworkConnection) connection);
wait(latch);
}
private void restartPrimaryAndWaitForRecovery() throws IOException, InterruptedException {
restartPrimaryAndWaitForRecovery(this.connection);
}
private void restartPrimaryAndWaitForRecovery(Connection connection) throws IOException, InterruptedException {
CountDownLatch latch = prepareForRecovery(connection);
// restart without tearing down and setting up
// new connection and channel
bareRestart();
wait(latch);
}
private void expectChannelRecovery(Channel ch) throws InterruptedException {
assertTrue(ch.isOpen());
}
@Override
protected ConnectionFactory newConnectionFactory() {
return buildConnectionFactoryWithRecoveryEnabled(false);
}
private RecoverableConnection newRecoveringConnection(boolean disableTopologyRecovery)
throws IOException, TimeoutException {
ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(disableTopologyRecovery);
return (AutorecoveringConnection) cf.newConnection();
}
private RecoverableConnection newRecoveringConnection(Address[] addresses)
throws IOException, TimeoutException {
ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(false);
// specifically use the Address[] overload
return (AutorecoveringConnection) cf.newConnection(addresses);
}
private RecoverableConnection newRecoveringConnection(boolean disableTopologyRecovery, List<Address> addresses)
throws IOException, TimeoutException {
ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(disableTopologyRecovery);
return (AutorecoveringConnection) cf.newConnection(addresses);
}
private RecoverableConnection newRecoveringConnection(List<Address> addresses)
throws IOException, TimeoutException {
return newRecoveringConnection(false, addresses);
}
private RecoverableConnection newRecoveringConnection(boolean disableTopologyRecovery, String connectionName)
throws IOException, TimeoutException {
ConnectionFactory cf = buildConnectionFactoryWithRecoveryEnabled(disableTopologyRecovery);
return (RecoverableConnection) cf.newConnection(connectionName);
}
private RecoverableConnection newRecoveringConnection(String connectionName)
throws IOException, TimeoutException {
return newRecoveringConnection(false, connectionName);
}
private ConnectionFactory buildConnectionFactoryWithRecoveryEnabled(boolean disableTopologyRecovery) {
ConnectionFactory cf = TestUtils.connectionFactory();
cf.setNetworkRecoveryInterval(RECOVERY_INTERVAL);
cf.setAutomaticRecoveryEnabled(true);
if (disableTopologyRecovery) {
cf.setTopologyRecoveryEnabled(false);
}
return cf;
}
private static void wait(CountDownLatch latch) throws InterruptedException {
// we want to wait for recovery to complete for a reasonable amount of time
// but still make recovery failures easy to notice in development environments
assertTrue(latch.await(90, TimeUnit.SECONDS));
}
private void waitForConfirms(Channel ch) throws InterruptedException, TimeoutException {
ch.waitForConfirms(30 * 60 * 1000);
}
private void assertRecordedQueues(Connection conn, int size) {
assertEquals(size, ((AutorecoveringConnection)conn).getRecordedQueues().size());
}
private void assertRecordedExchanges(Connection conn, int size) {
assertEquals(size, ((AutorecoveringConnection)conn).getRecordedExchanges().size());
}
}