// 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; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.net.SocketFactory; import org.junit.Test; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Command; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.Consumer; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.impl.AMQConnection; import com.rabbitmq.client.impl.DefaultExceptionHandler; import com.rabbitmq.client.impl.SocketFrameHandler; public class CloseInMainLoop extends BrokerTestCase{ private final CountDownLatch closeLatch = new CountDownLatch(1); private ConnectionFactory specialConnectionFactory() { ConnectionFactory f = TestUtils.connectionFactory(); f.setExceptionHandler(new DefaultExceptionHandler(){ @Override public void handleConsumerException(Channel channel, Throwable exception, Consumer consumer, String consumerTag, String methodName) { try { // TODO: change this to call 4-parameter close and make 6-parm one private ((AMQConnection) channel.getConnection()) .close(AMQP.INTERNAL_ERROR, "Internal error in Consumer " + consumerTag, false, exception, -1, false); } catch (Throwable e) { // Man, this clearly isn't our day. // TODO: Log the nested failure } finally { closeLatch.countDown(); } } }); return f; } class SpecialConnection extends AMQConnection{ private final AtomicBoolean validShutdown = new AtomicBoolean(false); public boolean hadValidShutdown(){ if(isOpen()) throw new IllegalStateException("hadValidShutdown called while connection is still open"); return validShutdown.get(); } public SpecialConnection() throws Exception { super(specialConnectionFactory().params(Executors.newFixedThreadPool(1)), new SocketFrameHandler(SocketFactory.getDefault().createSocket("localhost", AMQP.PROTOCOL.PORT))); this.start(); } @Override public boolean processControlCommand(Command c) throws IOException{ if(c.getMethod() instanceof AMQP.Connection.CloseOk) validShutdown.set(true); return super.processControlCommand(c); } } @Test public void closeOKNormallyReceived() throws Exception{ SpecialConnection connection = new SpecialConnection(); connection.close(); assertTrue(connection.hadValidShutdown()); } // The thrown runtime exception should get intercepted by the // consumer exception handler, and result in a clean shut down. @Test public void closeWithFaultyConsumer() throws Exception{ SpecialConnection connection = new SpecialConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare("x", "direct"); channel.queueDeclare("q", false, false, false, null); channel.queueBind("q", "x", "k"); channel.basicConsume("q", true, new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) { throw new RuntimeException("I am a bad consumer"); } }); channel.basicPublish("x", "k", null, new byte[10]); assertTrue(closeLatch.await(1000, TimeUnit.MILLISECONDS)); assertTrue(connection.hadValidShutdown()); } }