// 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.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import java.io.IOException; import java.util.concurrent.TimeoutException; import org.junit.Test; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.GetResponse; import com.rabbitmq.client.test.BrokerTestCase; public class Transactions extends BrokerTestCase { protected static final String Q = "Transactions"; protected long latestTag = 0L; protected void createResources() throws IOException { channel.queueDeclare(Q, false, false, false, null); } protected void releaseResources() throws IOException { channel.queueDelete(Q); } private void txSelect() throws IOException { channel.txSelect(); } private void txCommit() throws IOException { channel.txCommit(); } private void txRollback() throws IOException { channel.txRollback(); } private void basicPublish() throws IOException { channel.basicPublish("", Q, null, "Tx message".getBytes()); } private GetResponse basicGet(boolean noAck) throws IOException { GetResponse r = channel.basicGet(Q, noAck); latestTag = (r == null) ? 0L : r.getEnvelope().getDeliveryTag(); return r; } private GetResponse basicGet() throws IOException { return basicGet(false); } private void basicAck(long tag, boolean multiple) throws IOException { channel.basicAck(tag, multiple); } private void basicAck() throws IOException { basicAck(latestTag, false); } private long[] publishSelectAndGet(int n) throws IOException { for (int i = 0; i < n; i++) { basicPublish(); } txSelect(); long tags[] = new long[n]; for (int i = 0; i < n; i++) { tags[i] = basicGet().getEnvelope().getDeliveryTag(); } return tags; } /* publishes are embargoed until commit */ @Test public void commitPublish() throws IOException { txSelect(); basicPublish(); assertNull(basicGet()); txCommit(); assertNotNull(basicGet()); txCommit(); } /* rollback rolls back publishes */ @Test public void rollbackPublish() throws IOException { txSelect(); basicPublish(); txRollback(); assertNull(basicGet()); } /* closing a channel rolls back publishes */ @Test public void rollbackPublishOnClose() throws IOException { txSelect(); basicPublish(); closeChannel(); openChannel(); assertNull(basicGet()); } /* closing a channel requeues both ack'ed and un-ack'ed messages */ @Test public void requeueOnClose() throws IOException { basicPublish(); basicPublish(); txSelect(); basicGet(); basicAck(); basicGet(); closeChannel(); openChannel(); assertNotNull(basicGet()); basicAck(); assertNotNull(basicGet()); basicAck(); } /* messages with committed acks are not requeued on channel close, messages that weren't ack'ed are requeued on close, but not before then. */ @Test public void commitAcks() throws IOException { basicPublish(); basicPublish(); txSelect(); basicGet(); basicAck(); basicGet(); txCommit(); assertNull(basicGet()); closeChannel(); openChannel(); assertNotNull(basicGet()); basicAck(); assertNull(basicGet()); } /* */ @Test public void commitAcksOutOfOrder() throws IOException { long tags[] = publishSelectAndGet(4); channel.basicNack(tags[3], false, false); channel.basicNack(tags[2], false, false); channel.basicAck(tags[1], false); channel.basicAck(tags[0], false); txCommit(); } /* rollback rolls back acks and a rolled back ack can be re-issued */ @Test public void rollbackAcksAndReAck() throws IOException { basicPublish(); txSelect(); basicGet(); basicAck(); txRollback(); basicAck(); txRollback(); closeChannel(); openChannel(); assertNotNull(basicGet()); basicAck(); } /* it is illegal to ack with an unknown delivery tag */ @Test public void unknownTagAck() throws IOException { basicPublish(); txSelect(); basicGet(); basicAck(); basicAck(latestTag+1, true); // "On a transacted channel, this check MUST be done immediately and // not delayed until a Tx.Commit." expectError(AMQP.PRECONDITION_FAILED); } /* rollback does not requeue delivered ack'ed or un-ack'ed messages */ @Test public void noRequeueOnRollback() throws IOException { basicPublish(); basicPublish(); txSelect(); basicGet(); basicAck(); basicGet(); txRollback(); assertNull(basicGet()); } /* auto-acks are not part of tx */ @Test public void autoAck() throws IOException { basicPublish(); txSelect(); basicGet(true); closeChannel(); openChannel(); assertNull(basicGet()); } /* "ack all", once committed, acks all delivered messages */ @Test public void ackAll() throws IOException { basicPublish(); basicPublish(); txSelect(); basicGet(); basicGet(); basicAck(0L, true); txCommit(); closeChannel(); openChannel(); assertNull(basicGet()); } @Test public void nonTransactedCommit() throws IOException { try { txCommit(); fail("Expected channel error"); } catch (IOException e) { checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); } } @Test public void nonTransactedRollback() throws IOException { try { txRollback(); fail("Expected channel error"); } catch (IOException e) { checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); } } @Test public void redeliverAckedUncommitted() throws IOException { txSelect(); basicPublish(); txCommit(); basicGet(); // Ack the message but do not commit the channel. The message // should not get redelivered (see // https://bugzilla.rabbitmq.com/show_bug.cgi?id=21845#c3) basicAck(); channel.basicRecover(true); assertNull("Acked uncommitted message redelivered", basicGet(true)); } @Test public void commitWithDeletedQueue() throws IOException, TimeoutException { txSelect(); basicPublish(); releaseResources(); try { txCommit(); } catch (IOException e) { closeConnection(); openConnection(); openChannel(); fail("commit failed"); } finally { createResources(); // To allow teardown to function cleanly } } @Test public void shuffleAcksBeforeRollback() throws IOException { long tags[] = publishSelectAndGet(3); basicAck(tags[2], false); basicAck(tags[1], false); txRollback(); basicAck(tags[0], true); basicAck(tags[1], false); basicAck(tags[2], false); txCommit(); } private abstract class NackMethod { abstract public void nack(long tag, boolean requeue) throws IOException; public void nack(boolean requeue) throws IOException { nack(latestTag, requeue); } public void nack() throws IOException { nack(latestTag, true); } } private final NackMethod basicNack = new NackMethod() { public void nack(long tag, boolean requeue) throws IOException { channel.basicNack(tag, false, requeue); } }; private final NackMethod basicReject = new NackMethod() { public void nack(long tag, boolean requeue) throws IOException { channel.basicReject(tag, requeue); } }; /* messages with nacks get requeued after the transaction commit. messages with nacks with requeue = false are not requeued. */ public void commitNacks(NackMethod method) throws IOException { basicPublish(); basicPublish(); txSelect(); basicGet(); method.nack(); basicGet(); method.nack(false); assertNull(basicGet()); txCommit(); assertNotNull(basicGet()); assertNull(basicGet()); } public void rollbackNacks(NackMethod method) throws IOException { basicPublish(); txSelect(); basicGet(); method.nack(true); txRollback(); assertNull(basicGet()); } public void commitAcksAndNacks(NackMethod method) throws IOException { long tags[] = publishSelectAndGet(3); basicAck(tags[1], false); basicAck(tags[0], false); method.nack(tags[2], false); txRollback(); basicAck(tags[2], false); method.nack(tags[0], true); method.nack(tags[1], false); txCommit(); assertNotNull(basicGet()); assertNull(basicGet()); } @Test public void commitNacks() throws IOException { commitNacks(basicNack); } @Test public void rollbackNacks() throws IOException { rollbackNacks(basicNack); } @Test public void commitAcksAndNacks() throws IOException { commitAcksAndNacks(basicNack); } @Test public void commitRejects() throws IOException { commitNacks(basicReject); } @Test public void rollbackRejects() throws IOException { rollbackNacks(basicReject); } @Test public void commitAcksAndRejects() throws IOException { commitAcksAndNacks(basicReject); } }