/* * Copyright 2002-2017 the original author or authors. * * Licensed 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.springframework.amqp.rabbit.listener; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.util.HashSet; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.logging.log4j.Level; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.springframework.amqp.AmqpRejectAndDontRequeueException; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.rabbit.connection.ChannelProxy; import org.springframework.amqp.rabbit.connection.Connection; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.support.ConsumerCancelledException; import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter; import org.springframework.amqp.rabbit.test.LogLevelAdjuster; import org.springframework.amqp.utils.test.TestUtils; import org.springframework.beans.DirectFieldAccessor; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Consumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.ShutdownSignalException; import com.rabbitmq.client.impl.recovery.AutorecoveringChannel; /** * @author Gary Russell * @author Artem Bilan * @since 1.0.1 * */ public class BlockingQueueConsumerTests { @Rule public LogLevelAdjuster adjuster = new LogLevelAdjuster(Level.ERROR, BlockingQueueConsumer.class); @Test public void testRequeue() throws Exception { Exception ex = new RuntimeException(); testRequeueOrNotDefaultYes(ex, true); } @Test public void testRequeueNullException() throws Exception { testRequeueOrNotDefaultYes(null, true); } @Test public void testDontRequeue() throws Exception { testRequeueOrNotDefaultYes(new AmqpRejectAndDontRequeueException("fail"), false); } @Test public void testDontRequeueNested() throws Exception { Exception ex = new RuntimeException(new RuntimeException(new AmqpRejectAndDontRequeueException("fail"))); testRequeueOrNotDefaultYes(ex, false); } @Test public void testRequeueDefaultNot() throws Exception { testRequeueOrNotDefaultNo(new RuntimeException(), false); } @Test public void testRequeueNullExceptionDefaultNot() throws Exception { testRequeueOrNotDefaultNo(null, false); } @Test public void testDontRequeueDefaultNot() throws Exception { testRequeueOrNotDefaultNo(new AmqpRejectAndDontRequeueException("fail"), false); } @Test public void testDontRequeueNestedDefaultNot() throws Exception { Exception ex = new RuntimeException(new RuntimeException(new AmqpRejectAndDontRequeueException("fail"))); testRequeueOrNotDefaultNo(ex, false); } /** * We should always requeue if the exception is a * {@link MessageRejectedWhileStoppingException}. */ @Test public void testDoRequeueStoppingDefaultNot() throws Exception { testRequeueOrNotDefaultNo(new MessageRejectedWhileStoppingException(), true); } @Test public void testPrefetchIsSetOnFailedPassiveDeclaration() throws IOException { ConnectionFactory connectionFactory = mock(ConnectionFactory.class); Connection connection = mock(Connection.class); Channel channel = mock(Channel.class); when(connectionFactory.createConnection()).thenReturn(connection); when(connection.createChannel(Mockito.anyBoolean())).thenReturn(channel); when(channel.isOpen()).thenReturn(true); when(channel.queueDeclarePassive(Mockito.anyString())) .then(invocation -> { String arg = invocation.getArgument(0); if ("good".equals(arg)) { return Mockito.any(AMQP.Queue.DeclareOk.class); } else { throw new IOException(); } }); when(channel.basicConsume(anyString(), anyBoolean(), anyString(), anyBoolean(), anyBoolean(), anyMap(), any(Consumer.class))).thenReturn("consumerTag"); BlockingQueueConsumer blockingQueueConsumer = new BlockingQueueConsumer(connectionFactory, new DefaultMessagePropertiesConverter(), new ActiveObjectCounter<BlockingQueueConsumer>(), AcknowledgeMode.AUTO, true, 20, "good", "bad"); blockingQueueConsumer.setDeclarationRetries(1); blockingQueueConsumer.setRetryDeclarationInterval(10); blockingQueueConsumer.setFailedDeclarationRetryInterval(10); blockingQueueConsumer.start(); verify(channel).basicQos(20); } @Test public void testRecoverAfterDeletedQueueAndLostConnection() throws Exception { ConnectionFactory connectionFactory = mock(ConnectionFactory.class); Connection connection = mock(Connection.class); Channel channel = mock(Channel.class); given(connectionFactory.createConnection()).willReturn(connection); given(connection.createChannel(false)).willReturn(channel); given(connection.isOpen()).willReturn(true); given(channel.isOpen()).willReturn(true); final AtomicInteger n = new AtomicInteger(); ArgumentCaptor<Consumer> consumerCaptor = ArgumentCaptor.forClass(Consumer.class); final CountDownLatch consumerLatch = new CountDownLatch(2); willAnswer(invocation -> { consumerLatch.countDown(); return "consumer" + n.incrementAndGet(); }).given(channel).basicConsume(anyString(), anyBoolean(), anyString(), anyBoolean(), anyBoolean(), anyMap(), consumerCaptor.capture()); willThrow(new IOException("Intentional cancel fail")).given(channel).basicCancel("consumer2"); final BlockingQueueConsumer blockingQueueConsumer = new BlockingQueueConsumer(connectionFactory, new DefaultMessagePropertiesConverter(), new ActiveObjectCounter<BlockingQueueConsumer>(), AcknowledgeMode.AUTO, false, 1, "testQ1", "testQ2"); final CountDownLatch latch = new CountDownLatch(1); Executors.newSingleThreadExecutor().execute(() -> { blockingQueueConsumer.start(); while (true) { try { blockingQueueConsumer.nextMessage(1000); } catch (ConsumerCancelledException e) { latch.countDown(); break; } catch (ShutdownSignalException e) { } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); assertTrue(consumerLatch.await(10, TimeUnit.SECONDS)); Consumer consumer = consumerCaptor.getValue(); consumer.handleCancel("consumer1"); assertTrue(latch.await(10, TimeUnit.SECONDS)); } private void testRequeueOrNotDefaultYes(Exception ex, boolean expectedRequeue) throws Exception { ConnectionFactory connectionFactory = mock(ConnectionFactory.class); Channel channel = mock(Channel.class); BlockingQueueConsumer blockingQueueConsumer = new BlockingQueueConsumer(connectionFactory, new DefaultMessagePropertiesConverter(), new ActiveObjectCounter<BlockingQueueConsumer>(), AcknowledgeMode.AUTO, true, 1, "testQ"); testRequeueOrNotGuts(ex, expectedRequeue, channel, blockingQueueConsumer); } private void testRequeueOrNotDefaultNo(Exception ex, boolean expectedRequeue) throws Exception { ConnectionFactory connectionFactory = mock(ConnectionFactory.class); Channel channel = mock(Channel.class); BlockingQueueConsumer blockingQueueConsumer = new BlockingQueueConsumer(connectionFactory, new DefaultMessagePropertiesConverter(), new ActiveObjectCounter<BlockingQueueConsumer>(), AcknowledgeMode.AUTO, true, 1, false, "testQ"); testRequeueOrNotGuts(ex, expectedRequeue, channel, blockingQueueConsumer); } private void testRequeueOrNotGuts(Exception ex, boolean expectedRequeue, Channel channel, BlockingQueueConsumer blockingQueueConsumer) throws Exception { DirectFieldAccessor dfa = new DirectFieldAccessor(blockingQueueConsumer); dfa.setPropertyValue("channel", channel); Set<Long> deliveryTags = new HashSet<Long>(); deliveryTags.add(1L); dfa.setPropertyValue("deliveryTags", deliveryTags); blockingQueueConsumer.rollbackOnExceptionIfNecessary(ex); Mockito.verify(channel).basicNack(1L, true, expectedRequeue); } @Test public void testAlwaysCancelAutoRecoverConsumer() throws IOException { ConnectionFactory connectionFactory = mock(ConnectionFactory.class); Connection connection = mock(Connection.class); ChannelProxy channel = mock(ChannelProxy.class); Channel rabbitChannel = mock(AutorecoveringChannel.class); when(channel.getTargetChannel()).thenReturn(rabbitChannel); when(connectionFactory.createConnection()).thenReturn(connection); when(connection.createChannel(anyBoolean())).thenReturn(channel); final AtomicBoolean isOpen = new AtomicBoolean(true); doReturn(isOpen.get()).when(channel).isOpen(); when(channel.queueDeclarePassive(Mockito.anyString())) .then(invocation -> mock(AMQP.Queue.DeclareOk.class)); when(channel.basicConsume(anyString(), anyBoolean(), anyString(), anyBoolean(), anyBoolean(), anyMap(), any(Consumer.class))).thenReturn("consumerTag"); BlockingQueueConsumer blockingQueueConsumer = new BlockingQueueConsumer(connectionFactory, new DefaultMessagePropertiesConverter(), new ActiveObjectCounter<>(), AcknowledgeMode.AUTO, true, 2, "test"); blockingQueueConsumer.setDeclarationRetries(1); blockingQueueConsumer.setRetryDeclarationInterval(10); blockingQueueConsumer.setFailedDeclarationRetryInterval(10); blockingQueueConsumer.start(); verify(channel).basicQos(2); isOpen.set(false); blockingQueueConsumer.stop(); verify(channel).basicCancel("consumerTag"); } @Test public void testDrainAndReject() throws IOException { ConnectionFactory connectionFactory = mock(ConnectionFactory.class); Connection connection = mock(Connection.class); ChannelProxy channel = mock(ChannelProxy.class); Channel rabbitChannel = mock(AutorecoveringChannel.class); when(channel.getTargetChannel()).thenReturn(rabbitChannel); when(connectionFactory.createConnection()).thenReturn(connection); when(connection.createChannel(anyBoolean())).thenReturn(channel); final AtomicBoolean isOpen = new AtomicBoolean(true); doReturn(isOpen.get()).when(channel).isOpen(); when(channel.queueDeclarePassive(Mockito.anyString())) .then(invocation -> mock(AMQP.Queue.DeclareOk.class)); when(channel.basicConsume(anyString(), anyBoolean(), anyString(), anyBoolean(), anyBoolean(), anyMap(), any(Consumer.class))).thenReturn("consumerTag"); BlockingQueueConsumer blockingQueueConsumer = new BlockingQueueConsumer(connectionFactory, new DefaultMessagePropertiesConverter(), new ActiveObjectCounter<BlockingQueueConsumer>(), AcknowledgeMode.AUTO, true, 2, "test"); blockingQueueConsumer.setDeclarationRetries(1); blockingQueueConsumer.setRetryDeclarationInterval(10); blockingQueueConsumer.setFailedDeclarationRetryInterval(10); blockingQueueConsumer.start(); verify(channel).basicQos(2); Consumer consumer = TestUtils.getPropertyValue(blockingQueueConsumer, "consumer", Consumer.class); isOpen.set(false); blockingQueueConsumer.stop(); verify(channel).basicCancel("consumerTag"); Envelope envelope = new Envelope(1, false, "foo", "bar"); BasicProperties props = mock(BasicProperties.class); consumer.handleDelivery("consumerTag", envelope, props, new byte[0]); envelope = new Envelope(2, false, "foo", "bar"); consumer.handleDelivery("consumerTag", envelope, props, new byte[0]); assertThat(TestUtils.getPropertyValue(blockingQueueConsumer, "queue", BlockingQueue.class).size(), equalTo(2)); envelope = new Envelope(3, false, "foo", "bar"); consumer.handleDelivery("consumerTag", envelope, props, new byte[0]); assertThat(TestUtils.getPropertyValue(blockingQueueConsumer, "queue", BlockingQueue.class).size(), equalTo(0)); verify(channel).basicNack(3, true, true); verify(channel, times(2)).basicCancel("consumerTag"); } }