/* * Copyright 2016-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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.logging.Log; import org.springframework.amqp.AmqpApplicationContextClosedException; import org.springframework.amqp.AmqpConnectException; import org.springframework.amqp.AmqpException; import org.springframework.amqp.AmqpIOException; import org.springframework.amqp.ImmediateAcknowledgeAmqpException; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.connection.Connection; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils; import org.springframework.amqp.rabbit.connection.ConsumerChannelRegistry; import org.springframework.amqp.rabbit.connection.RabbitResourceHolder; import org.springframework.amqp.rabbit.connection.RabbitUtils; import org.springframework.amqp.rabbit.connection.SimpleResourceHolder; import org.springframework.amqp.rabbit.core.RabbitAdmin; import org.springframework.amqp.rabbit.transaction.RabbitTransactionManager; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.interceptor.TransactionAttribute; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.backoff.BackOffExecution; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Channel; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.ShutdownSignalException; /** * The {@code SimpleMessageListenerContainer} is not so simple. Recent changes to the * rabbitmq java client has facilitated a much simpler listener container that invokes the * listener directly on the rabbit client consumer thread. There is no txSize property - * each message is acked (or nacked) individually. * * @author Gary Russell * @author Artem Bilan * * @since 2.0 * */ public class DirectMessageListenerContainer extends AbstractMessageListenerContainer { private static final int DEFAULT_MONITOR_INTERVAL = 10_000; private static final int DEFAULT_ACK_TIMEOUT = 20_000; protected final List<SimpleConsumer> consumers = new LinkedList<>(); // NOSONAR private final List<SimpleConsumer> consumersToRestart = new LinkedList<>(); private final MultiValueMap<String, SimpleConsumer> consumersByQueue = new LinkedMultiValueMap<>(); private final ActiveObjectCounter<SimpleConsumer> cancellationLock = new ActiveObjectCounter<>(); private TaskScheduler taskScheduler; private boolean taskSchedulerSet; private long monitorInterval = DEFAULT_MONITOR_INTERVAL; private int messagesPerAck; private long ackTimeout = DEFAULT_ACK_TIMEOUT; private volatile boolean started; private volatile boolean aborted; private volatile boolean hasStopped; private volatile CountDownLatch startedLatch = new CountDownLatch(1); private volatile int consumersPerQueue = 1; private volatile ScheduledFuture<?> consumerMonitorTask; private volatile long lastAlertAt; private volatile long lastRestartAttempt; /** * Create an instance; {@link #setConnectionFactory(ConnectionFactory)} must * be called before starting. */ public DirectMessageListenerContainer() { setMissingQueuesFatal(false); } /** * Create an instance with the provided connection factory. * @param connectionFactory the connection factory. */ public DirectMessageListenerContainer(ConnectionFactory connectionFactory) { setConnectionFactory(connectionFactory); setMissingQueuesFatal(false); } /** * Each queue runs in its own consumer; set this property to create multiple * consumers for each queue. * If the container is already running, the number of consumers per queue will * be adjusted up or down as necessary. * @param consumersPerQueue the consumers per queue. */ public void setConsumersPerQueue(int consumersPerQueue) { if (isRunning()) { adjustConsumers(consumersPerQueue); } this.consumersPerQueue = consumersPerQueue; } /** * Set to true for an exclusive consumer - if true, the * {@link #setConsumersPerQueue(int) consumers per queue} must be 1. * @param exclusive true for an exclusive consumer. */ @Override public final void setExclusive(boolean exclusive) { Assert.isTrue(!exclusive || (this.consumersPerQueue == 1), "When the consumer is exclusive, the consumers per queue must be 1"); super.setExclusive(exclusive); } /** * Set the task scheduler to use for the task that monitors idle containers and * failed consumers. * @param taskScheduler the scheduler. */ public void setTaskScheduler(TaskScheduler taskScheduler) { this.taskScheduler = taskScheduler; this.taskSchedulerSet = true; } /** * Set how often to run a task to check for failed consumers and idle containers. * @param monitorInterval the interval; default 10000 but it will be adjusted down * to the smallest of this, {@link #setIdleEventInterval(long) idleEventInterval} / 2 * (if configured) or * {@link #setFailedDeclarationRetryInterval(long) failedDeclarationRetryInterval}. */ public void setMonitorInterval(long monitorInterval) { this.monitorInterval = monitorInterval; } @Override public void setQueueNames(String... queueName) { Assert.state(!isRunning(), "Cannot set queue names while running, use add/remove"); super.setQueueNames(queueName); } /** * {@inheritDoc} * <p> * Defaults to false for this container. */ @Override public final void setMissingQueuesFatal(boolean missingQueuesFatal) { super.setMissingQueuesFatal(missingQueuesFatal); } /** * Set the number of messages to receive before acknowledging (success). * A failed message will short-circuit this counter. * @param messagesPerAck the number of messages. * @see #setAckTimeout(long) */ public void setMessagesPerAck(int messagesPerAck) { this.messagesPerAck = messagesPerAck; } /** * An approximate timeout; when {@link #setMessagesPerAck(int) messagesPerAck} is * greater than 1, and this time elapses since the last ack, the pending acks will be * sent either when the next message arrives, or a short time later if no additional * messages arrive. In that case, the actual time depends on the * {@link #setMonitorInterval(long) monitorInterval}. * @param ackTimeout the timeout in milliseconds (default 20000); * @see #setMessagesPerAck(int) */ public void setAckTimeout(long ackTimeout) { this.ackTimeout = ackTimeout; } @Override public void addQueueNames(String... queueNames) { try { addQueues(Arrays.stream(queueNames)); } catch (AmqpIOException e) { throw new AmqpIOException("Failed to add " + Arrays.asList(queueNames), e.getCause()); } super.addQueueNames(queueNames); } private void addQueues(Stream<String> queueNameStream) { if (isRunning()) { synchronized (this.consumersMonitor) { checkStartState(); Set<String> current = getQueueNamesAsSet(); queueNameStream.forEach(queue -> { if (current.contains(queue)) { this.logger.warn("Queue " + queue + " is already configured for this container: " + this + ", ignoring add"); } else { consumeFromQueue(queue); } }); } } } @Override public boolean removeQueueNames(String... queueNames) { removeQueues(Arrays.stream(queueNames)); return super.removeQueueNames(queueNames); } private void removeQueues(Stream<String> queueNames) { if (isRunning()) { synchronized (this.consumersMonitor) { checkStartState(); queueNames.map(this.consumersByQueue::remove) .filter(Objects::nonNull) .flatMap(Collection::stream) .forEach(this::cancelConsumer); } } } private void adjustConsumers(int newCount) { synchronized (this.consumersMonitor) { checkStartState(); this.consumersToRestart.clear(); for (String queue : getQueueNames()) { while (this.consumersByQueue.get(queue) == null || this.consumersByQueue.get(queue).size() < newCount) { doConsumeFromQueue(queue); } List<SimpleConsumer> consumerList = this.consumersByQueue.get(queue); if (consumerList != null && consumerList.size() > newCount) { int currentCount = consumerList.size(); for (int i = newCount; i < currentCount; i++) { SimpleConsumer consumer = consumerList.remove(i); cancelConsumer(consumer); } } } } } private void checkStartState() { if (!this.isRunning()) { try { Assert.state(this.startedLatch.await(60, TimeUnit.SECONDS), "Container is not started - cannot adjust queues"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new AmqpException("Interrupted waiting for start", e); } } } @Override protected void doInitialize() throws Exception { if (this.taskScheduler == null) { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setThreadNamePrefix(getBeanName() + "-consumerMonitor-"); threadPoolTaskScheduler.afterPropertiesSet(); this.taskScheduler = threadPoolTaskScheduler; } if (this.messagesPerAck > 0) { Assert.state(!isChannelTransacted(), "'messagesPerAck' is not allowed with transactions"); } } @Override protected void doStart() throws Exception { if (!this.started) { actualStart(); } } @Override protected void doStop() { super.doStop(); if (!this.taskSchedulerSet && this.taskScheduler != null) { ((ThreadPoolTaskScheduler) this.taskScheduler).shutdown(); this.taskScheduler = null; } } protected void actualStart() throws Exception { this.aborted = false; this.hasStopped = false; if (getPrefetchCount() < this.messagesPerAck) { setPrefetchCount(this.messagesPerAck); } super.doStart(); final String[] queueNames = getQueueNames(); checkMissingQueues(queueNames); if (getTaskExecutor() == null) { afterPropertiesSet(); } long idleEventInterval = getIdleEventInterval(); if (this.taskScheduler == null) { afterPropertiesSet(); } if (idleEventInterval > 0 && this.monitorInterval > idleEventInterval) { this.monitorInterval = idleEventInterval / 2; } if (getFailedDeclarationRetryInterval() < this.monitorInterval) { this.monitorInterval = getFailedDeclarationRetryInterval(); } this.lastRestartAttempt = System.currentTimeMillis(); this.consumerMonitorTask = this.taskScheduler.scheduleAtFixedRate(() -> { long now = System.currentTimeMillis(); if (idleEventInterval > 0) { if (now - getLastReceive() > idleEventInterval && now - this.lastAlertAt > idleEventInterval) { publishIdleContainerEvent(now - getLastReceive()); this.lastAlertAt = now; } } final List<SimpleConsumer> consumersToCancel; synchronized (this.consumersMonitor) { consumersToCancel = this.consumers.stream() .filter(c -> { boolean open = c.getChannel().isOpen(); if (open && this.messagesPerAck > 1) { try { c.ackIfNecessary(now); } catch (IOException e) { this.logger.error("Exception while sending delayed ack", e); } } return !open; }) .collect(Collectors.toList()); } consumersToCancel .forEach(c -> { try { RabbitUtils.closeMessageConsumer(c.getChannel(), Collections.singletonList(c.getConsumerTag()), isChannelTransacted()); } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("Error closing consumer " + c, e); } } this.logger.error("Consumer canceled - channel closed " + c); c.cancelConsumer("Consumer " + c + " channel closed"); }); if (this.lastRestartAttempt + getFailedDeclarationRetryInterval() < now) { synchronized (this.consumersMonitor) { List<SimpleConsumer> restartableConsumers = new ArrayList<>(this.consumersToRestart); this.consumersToRestart.clear(); if (this.started) { if (restartableConsumers.size() > 0) { doRedeclareElementsIfNecessary(); } for (SimpleConsumer consumer : restartableConsumers) { if (this.logger.isDebugEnabled() && restartableConsumers.size() > 0) { this.logger.debug("Attempting to restart consumer " + consumer); } try { doConsumeFromQueue(consumer.getQueue()); } catch (AmqpConnectException | AmqpIOException e) { this.logger.error("Cannot connect to server", e); if (e.getCause() instanceof AmqpApplicationContextClosedException) { this.logger.error("Application context is closed, terminating"); this.taskScheduler.schedule(this::stop, new Date()); } break; } } this.lastRestartAttempt = now; } } } processMonitorTask(); }, this.monitorInterval); if (queueNames.length > 0) { doRedeclareElementsIfNecessary(); getTaskExecutor().execute(() -> { synchronized (this.consumersMonitor) { if (this.hasStopped) { // container stopped before we got the lock if (this.logger.isDebugEnabled()) { this.logger.debug("Consumer start aborted - container stopping"); } } else { BackOffExecution backOffExecution = getRecoveryBackOff().start(); while (!DirectMessageListenerContainer.this.started && isRunning()) { this.cancellationLock.reset(); try { for (String queue : queueNames) { consumeFromQueue(queue); } } catch (AmqpConnectException | AmqpIOException e) { long nextBackOff = backOffExecution.nextBackOff(); if (nextBackOff < 0 || e.getCause() instanceof AmqpApplicationContextClosedException) { DirectMessageListenerContainer.this.aborted = true; shutdown(); this.logger.error("Failed to start container - fatal error or backOffs exhausted", e); this.taskScheduler.schedule(this::stop, new Date()); break; } this.logger.error("Error creating consumer; retrying in " + nextBackOff, e); doShutdown(); try { Thread.sleep(nextBackOff); } catch (InterruptedException e1) { Thread.currentThread().interrupt(); } continue; // initialization failed; try again having rested for backOff-interval } DirectMessageListenerContainer.this.started = true; DirectMessageListenerContainer.this.startedLatch.countDown(); } } } }); } else { this.started = true; this.startedLatch.countDown(); } if (logger.isInfoEnabled()) { this.logger.info("Container initialized for queues: " + Arrays.asList(queueNames)); } } protected void doRedeclareElementsIfNecessary() { String routingLookupKey = getRoutingLookupKey(); if (routingLookupKey != null) { SimpleResourceHolder.bind(getRoutingConnectionFactory(), routingLookupKey); } try { redeclareElementsIfNecessary(); } catch (Exception e) { this.logger.error("Failed to redeclare elements", e); } finally { if (routingLookupKey != null) { SimpleResourceHolder.unbind(getRoutingConnectionFactory()); } } } /** * Subclasses can override this to take additional actions when the monitor task runs. */ protected void processMonitorTask() { // default do nothing } private void checkMissingQueues(String[] queueNames) { if (isMissingQueuesFatal()) { RabbitAdmin checkAdmin = getRabbitAdmin(); if (checkAdmin == null) { /* * Checking queue existence doesn't require an admin in the context or injected into * the container. If there's no such admin, just create a local one here. */ checkAdmin = new RabbitAdmin(getConnectionFactory()); } for (String queue : queueNames) { Properties queueProperties = checkAdmin.getQueueProperties(queue); if (queueProperties == null && isMissingQueuesFatal()) { throw new IllegalStateException("At least one of the configured queues is missing"); } } } } private void consumeFromQueue(String queue) { List<SimpleConsumer> list = this.consumersByQueue.get(queue); // Possible race with setConsumersPerQueue and the task launched by start() if (CollectionUtils.isEmpty(list)) { for (int i = 0; i < this.consumersPerQueue; i++) { doConsumeFromQueue(queue); } } } private void doConsumeFromQueue(String queue) { if (!isActive()) { if (this.logger.isDebugEnabled()) { this.logger.debug("Consume from queue " + queue + " ignore, container stopping"); } return; } String routingLookupKey = getRoutingLookupKey(); if (routingLookupKey != null) { SimpleResourceHolder.bind(getRoutingConnectionFactory(), routingLookupKey); } Connection connection = null; // NOSONAR (close) try { connection = getConnectionFactory().createConnection(); } catch (Exception e) { this.consumersToRestart.add(new SimpleConsumer(null, null, queue)); throw new AmqpConnectException(e); } finally { if (routingLookupKey != null) { SimpleResourceHolder.unbind(getRoutingConnectionFactory()); } } Channel channel = null; SimpleConsumer consumer = null; try { channel = connection.createChannel(isChannelTransacted()); channel.basicQos(getPrefetchCount()); consumer = new SimpleConsumer(connection, channel, queue); channel.queueDeclarePassive(queue); consumer.consumerTag = channel.basicConsume(queue, getAcknowledgeMode().isAutoAck(), (getConsumerTagStrategy() != null ? getConsumerTagStrategy().createConsumerTag(queue) : ""), false, isExclusive(), getConsumerArguments(), consumer); } catch (AmqpApplicationContextClosedException e) { throw new AmqpConnectException(e); } catch (IOException e) { RabbitUtils.closeChannel(channel); RabbitUtils.closeConnection(connection); if (e.getCause() instanceof ShutdownSignalException && e.getCause().getMessage().contains("in exclusive use")) { getExclusiveConsumerExceptionLogger().log(logger, "Exclusive consumer failure", e.getCause()); publishConsumerFailedEvent("Consumer raised exception, attempting restart", false, e); } else if (this.logger.isDebugEnabled()) { this.logger.debug( "Queue not present or basicConsume failed, scheduling consumer " + consumer + " for restart"); } this.consumersToRestart.add(consumer); consumer = null; } synchronized (this.consumersMonitor) { if (consumer != null) { this.cancellationLock.add(consumer); this.consumers.add(consumer); this.consumersByQueue.add(queue, consumer); if (this.logger.isInfoEnabled()) { this.logger.info(consumer + " started"); } } } } @Override protected void doShutdown() { boolean waitForConsumers = false; synchronized (this.consumersMonitor) { if (this.started || this.aborted) { actualShutDown(); waitForConsumers = true; } } if (waitForConsumers) { try { if (this.cancellationLock.await(getShutdownTimeout(), TimeUnit.MILLISECONDS)) { this.logger.info("Successfully waited for consumers to finish."); } else { this.logger.info("Consumers not finished."); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); this.logger.warn("Interrupted waiting for consumers. Continuing with shutdown."); } finally { this.startedLatch = new CountDownLatch(1); this.started = false; this.aborted = false; this.hasStopped = true; } } } /** * Must hold this.consumersMonitor. */ private void actualShutDown() { Assert.state(getTaskExecutor() != null, "Cannot shut down if not initialized"); this.logger.debug("Shutting down"); // Copy in the same order to avoid ConcurrentModificationException during remove in the cancelConsumer(). new LinkedList<>(this.consumers).forEach(this::cancelConsumer); this.consumers.clear(); this.consumersByQueue.clear(); this.logger.debug("All consumers canceled"); if (this.consumerMonitorTask != null) { this.consumerMonitorTask.cancel(true); this.consumerMonitorTask = null; } } private void cancelConsumer(SimpleConsumer consumer) { try { if (this.logger.isDebugEnabled()) { this.logger.debug("Canceling " + consumer); } synchronized (consumer) { consumer.canceled = true; if (this.messagesPerAck > 1) { consumer.ackIfNecessary(0L); } } consumer.getChannel().basicCancel(consumer.getConsumerTag()); } catch (IOException e) { this.logger.error("Failed to cancel consumer: " + consumer, e); } finally { this.consumers.remove(consumer); consumerRemoved(consumer); } } /** * Called whenever a consumer is removed. * @param consumer the consumer. */ protected void consumerRemoved(SimpleConsumer consumer) { // default empty } /** * The consumer object. */ final class SimpleConsumer extends DefaultConsumer { private final Log logger = DirectMessageListenerContainer.this.logger; private final Connection connection; private final String queue; private final boolean ackRequired; private final ConnectionFactory connectionFactory = getConnectionFactory(); private final PlatformTransactionManager transactionManager = getTransactionManager(); private final TransactionAttribute transactionAttribute = getTransactionAttribute(); private final boolean isRabbitTxManager = this.transactionManager instanceof RabbitTransactionManager; private final int messagesPerAck = DirectMessageListenerContainer.this.messagesPerAck; private final long ackTimeout = DirectMessageListenerContainer.this.ackTimeout; private int pendingAcks; private long lastAck = System.currentTimeMillis(); private long lastDeliveryComplete = this.lastAck; private long deliveryTag; private volatile String consumerTag; private volatile int epoch; private volatile TransactionTemplate transactionTemplate; private volatile boolean canceled; private SimpleConsumer(Connection connection, Channel channel, String queue) { super(channel); this.connection = connection; this.queue = queue; this.ackRequired = !getAcknowledgeMode().isAutoAck() && !getAcknowledgeMode().isManual(); } private String getQueue() { return this.queue; } @Override public String getConsumerTag() { return this.consumerTag; } /** * Return the current epoch for this consumer; consumersMonitor must be held. * @return the epoch. */ int getEpoch() { return this.epoch; } /** * Increment and return the current epoch for this consumer; consumersMonitor must * be held. * @return the epoch. */ int incrementAndGetEpoch() { return ++this.epoch; } @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { MessageProperties messageProperties = getMessagePropertiesConverter().toMessageProperties(properties, envelope, "UTF-8"); messageProperties.setConsumerTag(consumerTag); messageProperties.setConsumerQueue(this.queue); Message message = new Message(body, messageProperties); long deliveryTag = envelope.getDeliveryTag(); if (this.logger.isDebugEnabled()) { this.logger.debug(this + " received " + message); } updateLastReceive(); if (this.transactionManager != null) { try { if (this.isRabbitTxManager) { ConsumerChannelRegistry.registerConsumerChannel(getChannel(), this.connectionFactory); } if (this.transactionTemplate == null) { this.transactionTemplate = new TransactionTemplate(this.transactionManager, this.transactionAttribute); } this.transactionTemplate.execute(s -> { RabbitResourceHolder resourceHolder = ConnectionFactoryUtils.bindResourceToTransaction( new RabbitResourceHolder(getChannel(), false), this.connectionFactory, true); if (resourceHolder != null) { resourceHolder.addDeliveryTag(getChannel(), deliveryTag); } // unbound in ResourceHolderSynchronization.beforeCompletion() try { callExecuteListener(message, deliveryTag); } catch (RuntimeException e1) { prepareHolderForRollback(resourceHolder, e1); throw e1; } catch (Throwable e2) { //NOSONAR ok to catch Throwable here because we re-throw it below throw new WrappedTransactionException(e2); } return null; }); } catch (Throwable e) { // NOSONAR - errors are rethrown if (e instanceof WrappedTransactionException) { if (e.getCause() instanceof Error) { throw (Error) e.getCause(); } } } finally { if (this.isRabbitTxManager) { ConsumerChannelRegistry.unRegisterConsumerChannel(); } } } else { try { callExecuteListener(message, deliveryTag); } catch (Exception e) { // NOSONAR } } } private void callExecuteListener(Message message, long deliveryTag) throws Exception { boolean channelLocallyTransacted = isChannelLocallyTransacted(); try { executeListener(getChannel(), message); handleAck(deliveryTag, channelLocallyTransacted); } catch (ImmediateAcknowledgeAmqpException e) { if (this.logger.isDebugEnabled()) { this.logger.debug("User requested ack for failed delivery: " + deliveryTag); } handleAck(deliveryTag, channelLocallyTransacted); } catch (Exception e) { if (causeChainHasImmediateAcknowledgeAmqpException(e)) { if (this.logger.isDebugEnabled()) { this.logger.debug("User requested ack for failed delivery: " + deliveryTag); } handleAck(deliveryTag, channelLocallyTransacted); } else { this.logger.error("Failed to invoke listener", e); if (this.transactionManager != null) { if (this.transactionAttribute.rollbackOn(e)) { RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager .getResource(getConnectionFactory()); if (resourceHolder == null) { /* * If we don't actually have a transaction, we have to roll back * manually. See prepareHolderForRollback(). */ rollback(deliveryTag, e); } throw e; // encompassing transaction will handle the rollback. } else { if (this.logger.isDebugEnabled()) { this.logger.debug("No rollback for " + e); } } } else { rollback(deliveryTag, e); // no need to rethrow e - we'd ignore it anyway, not throw to client } } } } private void handleAck(long deliveryTag, boolean channelLocallyTransacted) throws IOException { /* * If we have a TX Manager, but no TX, act like we are locally transacted. */ boolean isLocallyTransacted = channelLocallyTransacted || (isChannelTransacted() && TransactionSynchronizationManager.getResource(this.connectionFactory) == null); try { if (this.ackRequired) { if (this.messagesPerAck > 1) { synchronized (this) { this.deliveryTag = deliveryTag; this.pendingAcks++; this.lastDeliveryComplete = System.currentTimeMillis(); ackIfNecessary(this.lastDeliveryComplete); } } else if (!isChannelTransacted() || isLocallyTransacted) { getChannel().basicAck(deliveryTag, false); } } if (isLocallyTransacted) { RabbitUtils.commitIfNecessary(getChannel()); } } catch (Exception e) { this.logger.error("Error acking", e); } } /** * Send the ack if we've reached the threshold (count or time) or immediately if * we are processing deliveries after a cancel has been issued. * @param now the current time. * @throws IOException if one occurs. */ private synchronized void ackIfNecessary(long now) throws IOException { if (this.pendingAcks >= this.messagesPerAck || ( this.pendingAcks > 0 && (now - this.lastAck > this.ackTimeout || this.canceled))) { sendAck(now); } } private void rollback(long deliveryTag, Exception e) { if (isChannelTransacted()) { RabbitUtils.rollbackIfNecessary(getChannel()); } if (this.ackRequired) { try { if (this.messagesPerAck > 1) { synchronized (this) { if (this.pendingAcks > 0) { sendAck(System.currentTimeMillis()); } } } getChannel().basicNack(deliveryTag, true, RabbitUtils.shouldRequeue(isDefaultRequeueRejected(), e, this.logger)); } catch (IOException e1) { this.logger.error("Failed to nack message", e1); } } if (isChannelTransacted()) { RabbitUtils.commitIfNecessary(getChannel()); } } protected synchronized void sendAck(long now) throws IOException { getChannel().basicAck(this.deliveryTag, true); this.lastAck = now; this.pendingAcks = 0; } @Override public void handleConsumeOk(String consumerTag) { super.handleConsumeOk(consumerTag); if (this.logger.isDebugEnabled()) { this.logger.debug("New " + this + " consumeOk"); } } @Override public void handleCancelOk(String consumerTag) { if (this.logger.isDebugEnabled()) { this.logger.debug("CancelOk " + this); } finalizeConsumer(); } @Override public void handleCancel(String consumerTag) throws IOException { this.logger.error("Consumer canceled - queue deleted? " + this); cancelConsumer("Consumer " + this + " canceled"); } void cancelConsumer(final String eventMessage) { publishConsumerFailedEvent(eventMessage, true, null); synchronized (DirectMessageListenerContainer.this.consumersMonitor) { List<SimpleConsumer> list = DirectMessageListenerContainer.this.consumersByQueue.get(this.queue); if (list != null) { list.remove(this); } DirectMessageListenerContainer.this.consumers.remove(this); DirectMessageListenerContainer.this.consumersToRestart.add(this); } finalizeConsumer(); } private void finalizeConsumer() { RabbitUtils.setPhysicalCloseRequired(true); RabbitUtils.closeChannel(getChannel()); RabbitUtils.closeConnection(this.connection); DirectMessageListenerContainer.this.cancellationLock.release(this); consumerRemoved(this); } @Override public String toString() { return "SimpleConsumer [queue=" + this.queue + ", consumerTag=" + this.consumerTag + " identity=" + ObjectUtils.getIdentityHexString(this) + "]"; } } }