// 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 static org.junit.Assert.*; import org.junit.Test; import com.rabbitmq.client.test.BrokerTestCase; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.GetResponse; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.ShutdownSignalException; /** * Test Requeue of messages on different types of close. * Methods {@link #open} and {@link #close} must be implemented by a concrete subclass. */ public abstract class RequeueOnClose extends BrokerTestCase { private static final String Q = "RequeueOnClose"; private static final int MESSAGE_COUNT = 2000; protected abstract void open() throws IOException, TimeoutException; protected abstract void close() throws IOException; public void setUp() throws IOException { // Override to disable the default behaviour from BrokerTestCase. } public void tearDown() throws IOException { // Override to disable the default behaviour from BrokerTestCase. } private void injectMessage() throws IOException { channel.queueDeclare(Q, false, false, false, null); channel.queueDelete(Q); channel.queueDeclare(Q, false, false, false, null); channel.basicPublish("", Q, null, "RequeueOnClose message".getBytes()); } private GetResponse getMessage() throws IOException { return channel.basicGet(Q, false); } private void publishAndGet(int count, boolean doAck) throws IOException, InterruptedException, TimeoutException { openConnection(); for (int repeat = 0; repeat < count; repeat++) { open(); injectMessage(); GetResponse r1 = getMessage(); if (doAck) channel.basicAck(r1.getEnvelope().getDeliveryTag(), false); close(); open(); if (doAck) { assertNull("Expected missing second basicGet (repeat="+repeat+")", getMessage()); } else { assertNotNull("Expected present second basicGet (repeat="+repeat+")", getMessage()); } close(); } closeConnection(); } /** * Test we don't requeue acknowledged messages (using get) * @throws Exception untested */ @Test public void normal() throws Exception { publishAndGet(3, true); } /** * Test we requeue unacknowledged messages (using get) * @throws Exception untested */ @Test public void requeueing() throws Exception { publishAndGet(3, false); } /** * Test we requeue unacknowledged message (using consumer) * @throws Exception untested */ @Test public void requeueingConsumer() throws Exception { openConnection(); open(); injectMessage(); QueueingConsumer c = new QueueingConsumer(channel); channel.basicConsume(Q, c); c.nextDelivery(); close(); open(); assertNotNull(getMessage()); close(); closeConnection(); } private void publishLotsAndGet() throws IOException, InterruptedException, ShutdownSignalException, TimeoutException { openConnection(); open(); channel.queueDeclare(Q, false, false, false, null); channel.queueDelete(Q); channel.queueDeclare(Q, false, false, false, null); for (int i = 0; i < MESSAGE_COUNT; i++) { channel.basicPublish("", Q, null, "in flight message".getBytes()); } QueueingConsumer c = new QueueingConsumer(channel); channel.basicConsume(Q, c); c.nextDelivery(); close(); open(); for (int i = 0; i < MESSAGE_COUNT; i++) { assertNotNull("only got " + i + " out of " + MESSAGE_COUNT + " messages", channel.basicGet(Q, true)); } assertNull("got more messages than " + MESSAGE_COUNT + " expected", channel.basicGet(Q, true)); channel.queueDelete(Q); close(); closeConnection(); } /** * Test close while consuming many messages successfully requeues unacknowledged messages * @throws Exception untested */ @Test public void requeueInFlight() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndGet(); } } /** * Test close while consuming partially not acked with cancel successfully requeues unacknowledged messages * @throws Exception untested */ @Test public void requeueInFlightConsumerNoAck() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndConsumeSome(false, true); } } /** * Test close while consuming partially acked with cancel successfully requeues unacknowledged messages * @throws Exception untested */ @Test public void requeueInFlightConsumerAck() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndConsumeSome(true, true); } } /** * Test close while consuming partially not acked without cancel successfully requeues unacknowledged messages * @throws Exception untested */ @Test public void requeueInFlightConsumerNoAckNoCancel() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndConsumeSome(false, false); } } /** * Test close while consuming partially acked without cancel successfully requeues unacknowledged messages * @throws Exception untested */ @Test public void requeueInFlightConsumerAckNoCancel() throws Exception { for (int i = 0; i < 5; i++) { publishLotsAndConsumeSome(true, false); } } private static final int MESSAGES_TO_CONSUME = 20; private void publishLotsAndConsumeSome(boolean ack, boolean cancelBeforeFinish) throws IOException, InterruptedException, ShutdownSignalException, TimeoutException { openConnection(); open(); channel.queueDeclare(Q, false, false, false, null); channel.queueDelete(Q); channel.queueDeclare(Q, false, false, false, null); for (int i = 0; i < MESSAGE_COUNT; i++) { channel.basicPublish("", Q, null, "in flight message".getBytes()); } CountDownLatch latch = new CountDownLatch(1); PartialConsumer c = new PartialConsumer(channel, MESSAGES_TO_CONSUME, ack, latch, cancelBeforeFinish); channel.basicConsume(Q, c); latch.await(); // wait for consumer close(); open(); int requeuedMsgCount = (ack) ? MESSAGE_COUNT - MESSAGES_TO_CONSUME : MESSAGE_COUNT; for (int i = 0; i < requeuedMsgCount; i++) { assertNotNull("only got " + i + " out of " + requeuedMsgCount + " messages", channel.basicGet(Q, true)); } int countMoreMsgs = 0; while (null != channel.basicGet(Q, true)) { countMoreMsgs++; } assertTrue("got " + countMoreMsgs + " more messages than " + requeuedMsgCount + " expected", 0==countMoreMsgs); channel.queueDelete(Q); close(); closeConnection(); } private class PartialConsumer extends DefaultConsumer { private volatile int count; private final Channel channel; private final CountDownLatch latch; private volatile boolean acknowledge; private final boolean cancelBeforeFinish; public PartialConsumer(Channel channel, int count, boolean acknowledge, CountDownLatch latch, boolean cancelBeforeFinish) { super(channel); this.count = count; this.channel = channel; this.latch = latch; this.acknowledge = acknowledge; this.cancelBeforeFinish = cancelBeforeFinish; } @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { if (this.acknowledge) this.channel.basicAck(envelope.getDeliveryTag(), false); if (--this.count == 0) { if (this.cancelBeforeFinish) this.channel.basicCancel(this.getConsumerTag()); this.acknowledge = false; // don't acknowledge any more this.latch.countDown(); } } } }