All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2002-2016 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 io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

import org.aopalliance.aop.Advice;
import io.bitsensor.plugins.shaded.org.apache.commons.logging.Log;

import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpConnectException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpIOException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpIllegalStateException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.AmqpRejectAndDontRequeueException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.ImmediateAcknowledgeAmqpException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.AcknowledgeMode;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Queue;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.ConnectionFactory;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.ConsumerChannelRegistry;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.RabbitResourceHolder;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.RabbitUtils;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.connection.SimpleResourceHolder;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.core.RabbitAdmin;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.exception.FatalListenerExecutionException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.exception.FatalListenerStartupException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.ConsumerCancelledException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.ListenerContainerAware;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.ConditionalExceptionLogger;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.ConsumerTagStrategy;
import io.bitsensor.plugins.shaded.org.springframework.aop.Pointcut;
import io.bitsensor.plugins.shaded.org.springframework.aop.framework.ProxyFactory;
import io.bitsensor.plugins.shaded.org.springframework.aop.support.DefaultPointcutAdvisor;
import io.bitsensor.plugins.shaded.org.springframework.beans.BeansException;
import io.bitsensor.plugins.shaded.org.springframework.context.ApplicationContext;
import io.bitsensor.plugins.shaded.org.springframework.context.ApplicationEventPublisher;
import io.bitsensor.plugins.shaded.org.springframework.context.ApplicationEventPublisherAware;
import io.bitsensor.plugins.shaded.org.springframework.core.task.SimpleAsyncTaskExecutor;
import io.bitsensor.plugins.shaded.org.springframework.jmx.export.annotation.ManagedMetric;
import io.bitsensor.plugins.shaded.org.springframework.jmx.support.MetricType;
import io.bitsensor.plugins.shaded.org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor;
import io.bitsensor.plugins.shaded.org.springframework.transaction.PlatformTransactionManager;
import io.bitsensor.plugins.shaded.org.springframework.transaction.TransactionStatus;
import io.bitsensor.plugins.shaded.org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import io.bitsensor.plugins.shaded.org.springframework.transaction.interceptor.TransactionAttribute;
import io.bitsensor.plugins.shaded.org.springframework.transaction.support.TransactionCallback;
import io.bitsensor.plugins.shaded.org.springframework.transaction.support.TransactionSynchronizationManager;
import io.bitsensor.plugins.shaded.org.springframework.transaction.support.TransactionTemplate;
import io.bitsensor.plugins.shaded.org.springframework.util.Assert;
import io.bitsensor.plugins.shaded.org.springframework.util.StringUtils;
import io.bitsensor.plugins.shaded.org.springframework.util.backoff.BackOff;
import io.bitsensor.plugins.shaded.org.springframework.util.backoff.BackOffExecution;
import io.bitsensor.plugins.shaded.org.springframework.util.backoff.FixedBackOff;

import io.bitsensor.plugins.shaded.com.rabbitmq.client.Channel;
import io.bitsensor.plugins.shaded.com.rabbitmq.client.ShutdownSignalException;

/**
 * @author Mark Pollack
 * @author Mark Fisher
 * @author Dave Syer
 * @author Gary Russell
 * @author Artem Bilan
 * @since 1.0
 */
public class SimpleMessageListenerContainer extends AbstractMessageListenerContainer
		implements ApplicationEventPublisherAware {

	private static final long DEFAULT_START_CONSUMER_MIN_INTERVAL = 10000;

	private static final long DEFAULT_STOP_CONSUMER_MIN_INTERVAL = 60000;

	private static final int DEFAULT_CONSECUTIVE_ACTIVE_TRIGGER = 10;

	private static final int DEFAULT_CONSECUTIVE_IDLE_TRIGGER = 10;

	public static final long DEFAULT_RECEIVE_TIMEOUT = 1000;

	public static final int DEFAULT_PREFETCH_COUNT = 1;

	public static final long DEFAULT_SHUTDOWN_TIMEOUT = 5000;

	/**
	 * The default recovery interval: 5000 ms = 5 seconds.
	 */
	public static final long DEFAULT_RECOVERY_INTERVAL = 5000;

	private final AtomicLong lastNoMessageAlert = new AtomicLong();

	private volatile int prefetchCount = DEFAULT_PREFETCH_COUNT;

	private volatile long startConsumerMinInterval = DEFAULT_START_CONSUMER_MIN_INTERVAL;

	private volatile long stopConsumerMinInterval = DEFAULT_STOP_CONSUMER_MIN_INTERVAL;

	private volatile int consecutiveActiveTrigger = DEFAULT_CONSECUTIVE_ACTIVE_TRIGGER;

	private volatile int consecutiveIdleTrigger = DEFAULT_CONSECUTIVE_IDLE_TRIGGER;

	private volatile int txSize = 1;

	private volatile Executor taskExecutor = new SimpleAsyncTaskExecutor();

	private volatile boolean taskExecutorSet;

	private volatile int concurrentConsumers = 1;

	private volatile Integer maxConcurrentConsumers;

	private volatile boolean exclusive;

	private volatile long lastConsumerStarted;

	private volatile long lastConsumerStopped;

	private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;

	private volatile long shutdownTimeout = DEFAULT_SHUTDOWN_TIMEOUT;

	private BackOff recoveryBackOff = new FixedBackOff(DEFAULT_RECOVERY_INTERVAL, FixedBackOff.UNLIMITED_ATTEMPTS);

	private Set consumers;

	private final Object consumersMonitor = new Object();

	private PlatformTransactionManager transactionManager;

	private TransactionAttribute transactionAttribute = new DefaultTransactionAttribute();

	private volatile Advice[] adviceChain = new Advice[0];

	private final ActiveObjectCounter cancellationLock = new ActiveObjectCounter();

	private volatile MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter();

	private volatile boolean defaultRequeueRejected = true;

	private final Map consumerArgs = new HashMap();

	private volatile RabbitAdmin rabbitAdmin;

	private volatile boolean missingQueuesFatal = true;

	private volatile boolean missingQueuesFatalSet;

	private volatile boolean autoDeclare = true;

	private volatile boolean mismatchedQueuesFatal = false;

	private volatile ConsumerTagStrategy consumerTagStrategy;

	private volatile ApplicationEventPublisher applicationEventPublisher;

	private final ContainerDelegate delegate = new ContainerDelegate() {

		@Override
		public void invokeListener(Channel channel, Message message) throws Exception {
			SimpleMessageListenerContainer.super.invokeListener(channel, message);
		}
	};

	private ContainerDelegate proxy = this.delegate;

	private Integer declarationRetries;

	private Long failedDeclarationRetryInterval;

	private Long retryDeclarationInterval;

	private ConditionalExceptionLogger exclusiveConsumerExceptionLogger = new DefaultExclusiveConsumerLogger();

	private Long idleEventInterval;

	private volatile long lastReceive = System.currentTimeMillis();

	private TransactionTemplate transactionTemplate;

	private boolean alwaysRequeueWithTxManagerRollback = true;

	/**
	 * Default constructor for convenient dependency injection via setters.
	 */
	public SimpleMessageListenerContainer() {
	}

	/**
	 * Create a listener container from the connection factory (mandatory).
	 *
	 * @param connectionFactory the {@link ConnectionFactory}
	 */
	public SimpleMessageListenerContainer(ConnectionFactory connectionFactory) {
		this.setConnectionFactory(connectionFactory);
	}

	/**
	 * Public setter for the {@link Advice} to apply to listener executions. If {@link #setTxSize(int) txSize>1} then
	 * multiple listener executions will all be wrapped in the same advice up to that limit.
	 * 

* If a {@link #setTransactionManager(PlatformTransactionManager) transactionManager} is provided as well, then * separate advice is created for the transaction and applied first in the chain. In that case the advice chain * provided here should not contain a transaction interceptor (otherwise two transactions would be be applied). * @param adviceChain the advice chain to set */ public void setAdviceChain(Advice... adviceChain) { this.adviceChain = Arrays.copyOf(adviceChain, adviceChain.length); for (final Advice advice : this.adviceChain) { if (advice instanceof StatefulRetryOperationsInterceptor) { ((StatefulRetryOperationsInterceptor) advice).setUseRawKey(true); } } } /** * Specify the interval between recovery attempts, in milliseconds. * The default is 5000 ms, that is, 5 seconds. * @param recoveryInterval The recovery interval. */ public void setRecoveryInterval(long recoveryInterval) { this.recoveryBackOff = new FixedBackOff(recoveryInterval, FixedBackOff.UNLIMITED_ATTEMPTS); } /** * Specify the {@link BackOff} for interval between recovery attempts. * The default is 5000 ms, that is, 5 seconds. * With the {@link BackOff} you can supply the {@code maxAttempts} for recovery before * the {@link #stop()} will be performed. * @param recoveryBackOff The BackOff to recover. * @since 1.5 */ public void setRecoveryBackOff(BackOff recoveryBackOff) { Assert.notNull(recoveryBackOff, "'recoveryBackOff' must not be null."); this.recoveryBackOff = recoveryBackOff; } /** * Specify the number of concurrent consumers to create. Default is 1. *

* Raising the number of concurrent consumers is recommended in order to scale the consumption of messages coming in * from a queue. However, note that any ordering guarantees are lost once multiple consumers are registered. In * general, stick with 1 consumer for low-volume queues. Cannot be more than {@link #maxConcurrentConsumers} (if set). * @param concurrentConsumers the minimum number of consumers to create. * @see #setMaxConcurrentConsumers(int) */ public void setConcurrentConsumers(final int concurrentConsumers) { Assert.isTrue(concurrentConsumers > 0, "'concurrentConsumers' value must be at least 1 (one)"); Assert.isTrue(!this.exclusive || concurrentConsumers == 1, "When the consumer is exclusive, the concurrency must be 1"); if (this.maxConcurrentConsumers != null) { Assert.isTrue(concurrentConsumers <= this.maxConcurrentConsumers, "'concurrentConsumers' cannot be more than 'maxConcurrentConsumers'"); } synchronized (this.consumersMonitor) { if (logger.isDebugEnabled()) { logger.debug("Changing consumers from " + this.concurrentConsumers + " to " + concurrentConsumers); } int delta = this.concurrentConsumers - concurrentConsumers; this.concurrentConsumers = concurrentConsumers; if (isActive() && this.consumers != null) { if (delta > 0) { Iterator consumerIterator = this.consumers.iterator(); while (consumerIterator.hasNext() && delta > 0) { BlockingQueueConsumer consumer = consumerIterator.next(); consumer.basicCancel(true); consumerIterator.remove(); delta--; } } else { addAndStartConsumers(-delta); } } } } /** * Sets an upper limit to the number of consumers; defaults to 'concurrentConsumers'. Consumers * will be added on demand. Cannot be less than {@link #concurrentConsumers}. * @param maxConcurrentConsumers the maximum number of consumers. * @see #setConcurrentConsumers(int) * @see #setStartConsumerMinInterval(long) * @see #setStopConsumerMinInterval(long) * @see #setConsecutiveActiveTrigger(int) * @see #setConsecutiveIdleTrigger(int) */ public void setMaxConcurrentConsumers(int maxConcurrentConsumers) { Assert.isTrue(maxConcurrentConsumers >= this.concurrentConsumers, "'maxConcurrentConsumers' value must be at least 'concurrentConsumers'"); Assert.isTrue(!this.exclusive || maxConcurrentConsumers == 1, "When the consumer is exclusive, the concurrency must be 1"); this.maxConcurrentConsumers = maxConcurrentConsumers; } /** * Set to true for an exclusive consumer - if true, the concurrency must be 1. * @param exclusive true for an exclusive consumer. */ public final void setExclusive(boolean exclusive) { Assert.isTrue(!exclusive || (this.concurrentConsumers == 1 && (this.maxConcurrentConsumers == null || this.maxConcurrentConsumers == 1)), "When the consumer is exclusive, the concurrency must be 1"); this.exclusive = exclusive; } /** * If {@link #maxConcurrentConsumers} is greater then {@link #concurrentConsumers}, and * {@link #maxConcurrentConsumers} has not been reached, specifies * the minimum time (milliseconds) between starting new consumers on demand. Default is 10000 * (10 seconds). * @param startConsumerMinInterval The minimum interval between new consumer starts. * @see #setMaxConcurrentConsumers(int) * @see #setStartConsumerMinInterval(long) */ public final void setStartConsumerMinInterval(long startConsumerMinInterval) { Assert.isTrue(startConsumerMinInterval > 0, "'startConsumerMinInterval' must be > 0"); this.startConsumerMinInterval = startConsumerMinInterval; } /** * If {@link #maxConcurrentConsumers} is greater then {@link #concurrentConsumers}, and * the number of consumers exceeds {@link #concurrentConsumers}, specifies the * minimum time (milliseconds) between stopping idle consumers. Default is 60000 * (1 minute). * @param stopConsumerMinInterval The minimum interval between consumer stops. * @see #setMaxConcurrentConsumers(int) * @see #setStopConsumerMinInterval(long) */ public final void setStopConsumerMinInterval(long stopConsumerMinInterval) { Assert.isTrue(stopConsumerMinInterval > 0, "'stopConsumerMinInterval' must be > 0"); this.stopConsumerMinInterval = stopConsumerMinInterval; } /** * If {@link #maxConcurrentConsumers} is greater then {@link #concurrentConsumers}, and * {@link #maxConcurrentConsumers} has not been reached, specifies the number of * consecutive cycles when a single consumer was active, in order to consider * starting a new consumer. If the consumer goes idle for one cycle, the counter is reset. * This is impacted by the {@link #txSize}. * Default is 10 consecutive messages. * @param consecutiveActiveTrigger The number of consecutive receives to trigger a new consumer. * @see #setMaxConcurrentConsumers(int) * @see #setStartConsumerMinInterval(long) * @see #setTxSize(int) */ public final void setConsecutiveActiveTrigger(int consecutiveActiveTrigger) { Assert.isTrue(consecutiveActiveTrigger > 0, "'consecutiveActiveTrigger' must be > 0"); this.consecutiveActiveTrigger = consecutiveActiveTrigger; } /** * If {@link #maxConcurrentConsumers} is greater then {@link #concurrentConsumers}, and * the number of consumers exceeds {@link #concurrentConsumers}, specifies the * number of consecutive receive attempts that return no data; after which we consider * stopping a consumer. The idle time is effectively * {@link #receiveTimeout} * {@link #txSize} * this value because the consumer thread waits for * a message for up to {@link #receiveTimeout} up to {@link #txSize} times. * Default is 10 consecutive idles. * @param consecutiveIdleTrigger The number of consecutive timeouts to trigger stopping a consumer. * @see #setMaxConcurrentConsumers(int) * @see #setStopConsumerMinInterval(long) * @see #setReceiveTimeout(long) * @see #setTxSize(int) */ public final void setConsecutiveIdleTrigger(int consecutiveIdleTrigger) { Assert.isTrue(consecutiveIdleTrigger > 0, "'consecutiveIdleTrigger' must be > 0"); this.consecutiveIdleTrigger = consecutiveIdleTrigger; } /** * The time (in milliseconds) that a consumer should wait for data. Default * 1000 (1 second). * @param receiveTimeout the timeout. * @see #setConsecutiveIdleTrigger(int) */ public void setReceiveTimeout(long receiveTimeout) { this.receiveTimeout = receiveTimeout; } /** * The time to wait for workers in milliseconds after the container is stopped, and before the connection is forced * closed. If any workers are active when the shutdown signal comes they will be allowed to finish processing as * long as they can finish within this timeout. Otherwise the connection is closed and messages remain unacked (if * the channel is transactional). Defaults to 5 seconds. * @param shutdownTimeout the shutdown timeout to set */ public void setShutdownTimeout(long shutdownTimeout) { this.shutdownTimeout = shutdownTimeout; } public void setTaskExecutor(Executor taskExecutor) { Assert.notNull(taskExecutor, "taskExecutor must not be null"); this.taskExecutor = taskExecutor; this.taskExecutorSet = true; } /** * Tells the broker how many messages to send to each consumer in a single request. Often this can be set quite high * to improve throughput. It should be greater than or equal to {@link #setTxSize(int) the transaction size}. * @param prefetchCount the prefetch count */ public void setPrefetchCount(int prefetchCount) { this.prefetchCount = prefetchCount; } /** * Tells the container how many messages to process in a single transaction (if the channel is transactional). For * best results it should be less than or equal to {@link #setPrefetchCount(int) the prefetch count}. Also affects * how often acks are sent when using {@link AcknowledgeMode#AUTO} - one ack per txSize. Default is 1. * @param txSize the transaction size */ public void setTxSize(int txSize) { Assert.isTrue(txSize > 0, "'txSize' must be > 0"); this.txSize = txSize; } public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } /** * @param transactionAttribute the transaction attribute to set */ public void setTransactionAttribute(TransactionAttribute transactionAttribute) { Assert.notNull(transactionAttribute, "'transactionAttribute' cannot be null"); this.transactionAttribute = transactionAttribute; } /** * Set the {@link MessagePropertiesConverter} for this listener container. * @param messagePropertiesConverter The properties converter. */ public void setMessagePropertiesConverter(MessagePropertiesConverter messagePropertiesConverter) { Assert.notNull(messagePropertiesConverter, "messagePropertiesConverter must not be null"); this.messagePropertiesConverter = messagePropertiesConverter; } /** * Determines the default behavior when a message is rejected, for example because the listener * threw an exception. When true, messages will be requeued, when false, they will not. For * versions of Rabbit that support dead-lettering, the message must not be requeued in order * to be sent to the dead letter exchange. Setting to false causes all rejections to not * be requeued. When true, the default can be overridden by the listener throwing an * {@link AmqpRejectAndDontRequeueException}. Default true. * @param defaultRequeueRejected true to reject by default. */ public void setDefaultRequeueRejected(boolean defaultRequeueRejected) { this.defaultRequeueRejected = defaultRequeueRejected; } public void setConsumerArguments(Map args) { synchronized (this.consumersMonitor) { this.consumerArgs.clear(); this.consumerArgs.putAll(args); } } protected RabbitAdmin getRabbitAdmin() { return this.rabbitAdmin; } /** * Set the {@link RabbitAdmin}, used to declare any auto-delete queues, bindings * etc when the container is started. Only needed if those queues use conditional * declaration (have a 'declared-by' attribute). If not specified, an internal * admin will be used which will attempt to declare all elements not having a * 'declared-by' attribute. * @param rabbitAdmin The admin. */ public void setRabbitAdmin(RabbitAdmin rabbitAdmin) { this.rabbitAdmin = rabbitAdmin; } /** * If all of the configured queue(s) are not available on the broker, this setting * determines whether the condition is fatal (default true). When true, and * the queues are missing during startup, the context refresh() will fail. If * the queues are removed while the container is running, the container is * stopped. *

When false, the condition is not considered fatal and the container will * continue to attempt to start the consumers according to the {@link #setRecoveryInterval(long)}. * Note that each consumer will make 3 attempts (at 5 second intervals) on each * recovery attempt. * @param missingQueuesFatal the missingQueuesFatal to set. * @since 1.3.5 */ public void setMissingQueuesFatal(boolean missingQueuesFatal) { this.missingQueuesFatal = missingQueuesFatal; this.missingQueuesFatalSet = true; } /** * Prevent the container from starting if any of the queues defined in the context have * mismatched arguments (TTL etc). Default false. * @param mismatchedQueuesFatal true to fail initialization when this condition occurs. * @since 1.6 */ public void setMismatchedQueuesFatal(boolean mismatchedQueuesFatal) { this.mismatchedQueuesFatal = mismatchedQueuesFatal; } /** * {@inheritDoc} * @since 1.5 */ @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } @Override public void setQueueNames(String... queueName) { super.setQueueNames(queueName); this.queuesChanged(); } @Override public void setQueues(Queue... queues) { super.setQueues(queues); this.queuesChanged(); } /** * @param autoDeclare the boolean flag to indicate an redeclaration operation. * @since 1.4 * @see #redeclareElementsIfNecessary */ public void setAutoDeclare(boolean autoDeclare) { this.autoDeclare = autoDeclare; } /** * Add queue(s) to this container's list of queues. The existing consumers * will be cancelled after they have processed any pre-fetched messages and * new consumers will be created. The queue must exist to avoid problems when * restarting the consumers. * @param queueName The queue to add. */ @Override public void addQueueNames(String... queueName) { super.addQueueNames(queueName); this.queuesChanged(); } /** * Add queue(s) to this container's list of queues. The existing consumers * will be cancelled after they have processed any pre-fetched messages and * new consumers will be created. The queue must exist to avoid problems when * restarting the consumers. * @param queue The queue to add. */ @Override public void addQueues(Queue... queue) { super.addQueues(queue); this.queuesChanged(); } /** * Remove queues from this container's list of queues. The existing consumers * will be cancelled after they have processed any pre-fetched messages and * new consumers will be created. At least one queue must remain. * @param queueName The queue to remove. */ @Override public boolean removeQueueNames(String... queueName) { if (super.removeQueueNames(queueName)) { this.queuesChanged(); return true; } else { return false; } } /** * Remove queue(s) from this container's list of queues. The existing consumers * will be cancelled after they have processed any pre-fetched messages and * new consumers will be created. At least one queue must remain. * @param queue The queue to remove. */ @Override public boolean removeQueues(Queue... queue) { if (super.removeQueues(queue)) { this.queuesChanged(); return true; } else { return false; } } /** * Set the number of retries after passive queue declaration fails. * @param declarationRetries The number of retries, default 3. * @see #setFailedDeclarationRetryInterval(long) * @since 1.3.9 */ public void setDeclarationRetries(int declarationRetries) { this.declarationRetries = declarationRetries; } /** * Set the interval between passive queue declaration attempts in milliseconds. * @param failedDeclarationRetryInterval the interval, default 5000. * @see #setDeclarationRetries(int) * @since 1.3.9 */ public void setFailedDeclarationRetryInterval(long failedDeclarationRetryInterval) { this.failedDeclarationRetryInterval = failedDeclarationRetryInterval; } /** * When consuming multiple queues, set the interval between declaration attempts when only * a subset of the queues were available (milliseconds). * @param retryDeclarationInterval the interval, default 60000. * @since 1.3.9 */ public void setRetryDeclarationInterval(long retryDeclarationInterval) { this.retryDeclarationInterval = retryDeclarationInterval; } /** * Set the implementation of {@link ConsumerTagStrategy} to generate consumer tags. * By default, the RabbitMQ server generates consumer tags. * @param consumerTagStrategy the consumerTagStrategy to set. * @since 1.4.5 */ public void setConsumerTagStrategy(ConsumerTagStrategy consumerTagStrategy) { this.consumerTagStrategy = consumerTagStrategy; } /** * Set a {@link ConditionalExceptionLogger} for logging exclusive consumer failures. The * default is to log such failures at WARN level. * @param exclusiveConsumerExceptionLogger the conditional exception logger. * @since 1.5 */ public void setExclusiveConsumerExceptionLogger(ConditionalExceptionLogger exclusiveConsumerExceptionLogger) { this.exclusiveConsumerExceptionLogger = exclusiveConsumerExceptionLogger; } /** * How often to emit {@link ListenerContainerIdleEvent}s in milliseconds. * @param idleEventInterval the interval. */ public void setIdleEventInterval(long idleEventInterval) { this.idleEventInterval = idleEventInterval; } /** * Set to false to avoid always requeuing on transaction rollback with an external * {@link #setTransactionManager(PlatformTransactionManager) TransactionManager}. * By default, when a transaction manager was configured, a transaction * rollback always requeued\s the message. This is inconsistent with local transactions * where the normal {@link #setDefaultRequeueRejected(boolean) defaultRequeueRejected} * and {@link AmqpRejectAndDontRequeueException} logic is honored to determine whether * the message is requeued. RabbitMQ does not consider the message delivery to be part * of the transaction. * This boolean was introduced in 1.7.1, set to true by default, to be consistent with * previous behavior. Starting with version 2.0, it will be false by default. * @param alwaysRequeueWithTxManagerRollback false to not always requeue on rollback. * @since 1.7.1. */ public void setAlwaysRequeueWithTxManagerRollback(boolean alwaysRequeueWithTxManagerRollback) { this.alwaysRequeueWithTxManagerRollback = alwaysRequeueWithTxManagerRollback; } /** * Avoid the possibility of not configuring the CachingConnectionFactory in sync with the number of concurrent * consumers. */ @Override protected void validateConfiguration() { super.validateConfiguration(); Assert.state( !(getAcknowledgeMode().isAutoAck() && this.transactionManager != null), "The acknowledgeMode is NONE (autoack in Rabbit terms) which is not consistent with having an " + "external transaction manager. Either use a different AcknowledgeMode or make sure " + "the transactionManager is null."); } private void initializeProxy() { if (this.adviceChain.length == 0) { return; } ProxyFactory factory = new ProxyFactory(); for (Advice advice : getAdviceChain()) { factory.addAdvisor(new DefaultPointcutAdvisor(Pointcut.TRUE, advice)); } factory.setProxyTargetClass(false); factory.addInterface(ContainerDelegate.class); factory.setTarget(this.delegate); this.proxy = (ContainerDelegate) factory.getProxy(ContainerDelegate.class.getClassLoader()); } // ------------------------------------------------------------------------- // Implementation of AbstractMessageListenerContainer's template methods // ------------------------------------------------------------------------- /** * Always use a shared Rabbit Connection. * @return true */ protected final boolean sharedConnectionEnabled() { return true; } /** * Creates the specified number of concurrent consumers, in the form of a Rabbit Channel plus associated * MessageConsumer. * @throws Exception Any Exception. */ @Override protected void doInitialize() throws Exception { checkMissingQueuesFatal(); if (!this.isExposeListenerChannel() && this.transactionManager != null) { logger.warn("exposeListenerChannel=false is ignored when using a TransactionManager"); } if (!this.taskExecutorSet && StringUtils.hasText(this.getBeanName())) { this.taskExecutor = new SimpleAsyncTaskExecutor(this.getBeanName() + "-"); this.taskExecutorSet = true; } initializeProxy(); if (this.transactionManager != null) { if (!isChannelTransacted()) { logger.debug("The 'channelTransacted' is coerced to 'true', when 'transactionManager' is provided"); setChannelTransacted(true); } } } @ManagedMetric(metricType = MetricType.GAUGE) public int getActiveConsumerCount() { return this.cancellationLock.getCount(); } /** * Re-initializes this container's Rabbit message consumers, if not initialized already. Then submits each consumer * to this container's task executor. * @throws Exception Any Exception. */ @Override protected void doStart() throws Exception { if (getMessageListener() instanceof ListenerContainerAware) { Collection expectedQueueNames = ((ListenerContainerAware) getMessageListener()).expectedQueueNames(); if (expectedQueueNames != null) { String[] queueNames = getQueueNames(); Assert.state(expectedQueueNames.size() == queueNames.length, "Listener expects us to be listening on '" + expectedQueueNames + "'; our queues: " + Arrays.asList(queueNames)); boolean found = false; for (String queueName : queueNames) { if (expectedQueueNames.contains(queueName)) { found = true; } else { found = false; break; } } Assert.state(found, "Listener expects us to be listening on '" + expectedQueueNames + "'; our queues: " + Arrays.asList(queueNames)); } } if (this.rabbitAdmin == null && this.getApplicationContext() != null) { Map admins = this.getApplicationContext().getBeansOfType(RabbitAdmin.class); if (admins.size() == 1) { this.rabbitAdmin = admins.values().iterator().next(); } else { if (this.autoDeclare || this.mismatchedQueuesFatal) { if (logger.isDebugEnabled()) { logger.debug("For 'autoDeclare' and 'mismatchedQueuesFatal' to work, there must be exactly one " + "RabbitAdmin in the context or you must inject one into this container; found: " + admins.size() + " for container " + this.toString()); } } if (this.mismatchedQueuesFatal) { throw new IllegalStateException("When 'mismatchedQueuesFatal' is 'true', there must be exactly " + "one RabbitAdmin in the context or you must inject one into this container; found: " + admins.size() + " for container " + this.toString()); } } } checkMismatchedQueues(); super.doStart(); synchronized (this.consumersMonitor) { int newConsumers = initializeConsumers(); if (this.consumers == null) { if (logger.isInfoEnabled()) { logger.info("Consumers were initialized and then cleared " + "(presumably the container was stopped concurrently)"); } return; } if (newConsumers <= 0) { if (logger.isInfoEnabled()) { logger.info("Consumers are already running"); } return; } Set processors = new HashSet(); for (BlockingQueueConsumer consumer : this.consumers) { AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer); processors.add(processor); this.taskExecutor.execute(processor); if (this.applicationEventPublisher != null) { this.applicationEventPublisher.publishEvent(new AsyncConsumerStartedEvent(this, consumer)); } } for (AsyncMessageProcessingConsumer processor : processors) { FatalListenerStartupException startupException = processor.getStartupException(); if (startupException != null) { throw new AmqpIllegalStateException("Fatal exception on listener startup", startupException); } } } } @Override protected void doStop() { shutdown(); super.doStop(); } @Override protected void doShutdown() { if (!this.isRunning()) { return; } try { synchronized (this.consumersMonitor) { if (this.consumers != null) { Iterator consumerIterator = this.consumers.iterator(); while (consumerIterator.hasNext()) { BlockingQueueConsumer consumer = consumerIterator.next(); consumer.basicCancel(true); consumerIterator.remove(); } } } logger.info("Waiting for workers to finish."); boolean finished = this.cancellationLock.await(this.shutdownTimeout, TimeUnit.MILLISECONDS); if (finished) { logger.info("Successfully waited for workers to finish."); } else { logger.info("Workers not finished."); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.warn("Interrupted waiting for workers. Continuing with shutdown."); } synchronized (this.consumersMonitor) { this.consumers = null; } } private boolean isActive(BlockingQueueConsumer consumer) { boolean consumerActive; synchronized (this.consumersMonitor) { consumerActive = this.consumers != null && this.consumers.contains(consumer); } return consumerActive && this.isActive(); } protected int initializeConsumers() { int count = 0; synchronized (this.consumersMonitor) { if (this.consumers == null) { this.cancellationLock.reset(); this.consumers = new HashSet(this.concurrentConsumers); for (int i = 0; i < this.concurrentConsumers; i++) { BlockingQueueConsumer consumer = createBlockingQueueConsumer(); this.consumers.add(consumer); count++; } } } return count; } private void checkMissingQueuesFatal() { if (!this.missingQueuesFatalSet) { try { ApplicationContext applicationContext = getApplicationContext(); if (applicationContext != null) { Properties properties = applicationContext.getBean("spring.amqp.global.properties", Properties.class); String missingQueuesFatal = properties.getProperty("smlc.missing.queues.fatal"); if (StringUtils.hasText(missingQueuesFatal)) { this.missingQueuesFatal = Boolean.parseBoolean(missingQueuesFatal); } } } catch (BeansException be) { if (logger.isDebugEnabled()) { logger.debug("No global properties bean"); } } } } private void checkMismatchedQueues() { if (this.mismatchedQueuesFatal && this.rabbitAdmin != null) { try { this.rabbitAdmin.initialize(); } catch (AmqpConnectException e) { logger.info("Broker not available; cannot check queue declarations"); } catch (AmqpIOException e) { if (RabbitUtils.isMismatchedQueueArgs(e)) { throw new FatalListenerStartupException("Mismatched queues", e); } else { logger.info("Failed to get connection during start(): " + e); } } } } protected void addAndStartConsumers(int delta) { synchronized (this.consumersMonitor) { if (this.consumers != null) { for (int i = 0; i < delta; i++) { BlockingQueueConsumer consumer = createBlockingQueueConsumer(); this.consumers.add(consumer); AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer); if (logger.isDebugEnabled()) { logger.debug("Starting a new consumer: " + consumer); } this.taskExecutor.execute(processor); if (this.applicationEventPublisher != null) { this.applicationEventPublisher.publishEvent(new AsyncConsumerStartedEvent(this, consumer)); } try { FatalListenerStartupException startupException = processor.getStartupException(); if (startupException != null) { this.consumers.remove(consumer); throw new AmqpIllegalStateException("Fatal exception on listener startup", startupException); } } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } catch (Exception e) { consumer.stop(); logger.error("Error starting new consumer", e); this.cancellationLock.release(consumer); this.consumers.remove(consumer); } } } } } private void considerAddingAConsumer() { synchronized (this.consumersMonitor) { if (this.consumers != null && this.maxConcurrentConsumers != null && this.consumers.size() < this.maxConcurrentConsumers) { long now = System.currentTimeMillis(); if (this.lastConsumerStarted + this.startConsumerMinInterval < now) { this.addAndStartConsumers(1); this.lastConsumerStarted = now; } } } } private void considerStoppingAConsumer(BlockingQueueConsumer consumer) { synchronized (this.consumersMonitor) { if (this.consumers != null && this.consumers.size() > this.concurrentConsumers) { long now = System.currentTimeMillis(); if (this.lastConsumerStopped + this.stopConsumerMinInterval < now) { consumer.basicCancel(true); this.consumers.remove(consumer); if (logger.isDebugEnabled()) { logger.debug("Idle consumer terminating: " + consumer); } this.lastConsumerStopped = now; } } } } private void queuesChanged() { synchronized (this.consumersMonitor) { if (this.consumers != null) { int count = 0; Iterator consumerIterator = this.consumers.iterator(); while (consumerIterator.hasNext()) { BlockingQueueConsumer consumer = consumerIterator.next(); if (logger.isDebugEnabled()) { logger.debug("Queues changed; stopping consumer: " + consumer); } consumer.basicCancel(true); consumerIterator.remove(); count++; } this.addAndStartConsumers(count); } } } @Override protected boolean isChannelLocallyTransacted(Channel channel) { return super.isChannelLocallyTransacted(channel) && this.transactionManager == null; } protected BlockingQueueConsumer createBlockingQueueConsumer() { BlockingQueueConsumer consumer; String[] queues = getQueueNames(); // There's no point prefetching less than the tx size, otherwise the consumer will stall because the broker // didn't get an ack for delivered messages int actualPrefetchCount = this.prefetchCount > this.txSize ? this.prefetchCount : this.txSize; consumer = new BlockingQueueConsumer(getConnectionFactory(), this.messagePropertiesConverter, this.cancellationLock, getAcknowledgeMode(), isChannelTransacted(), actualPrefetchCount, this.defaultRequeueRejected, this.consumerArgs, this.exclusive, queues); if (this.declarationRetries != null) { consumer.setDeclarationRetries(this.declarationRetries); } if (this.failedDeclarationRetryInterval != null) { consumer.setFailedDeclarationRetryInterval(this.failedDeclarationRetryInterval); } if (this.retryDeclarationInterval != null) { consumer.setRetryDeclarationInterval(this.retryDeclarationInterval); } if (this.consumerTagStrategy != null) { consumer.setTagStrategy(this.consumerTagStrategy); } consumer.setBackOffExecution(this.recoveryBackOff.start()); consumer.setShutdownTimeout(this.shutdownTimeout); return consumer; } private void restart(BlockingQueueConsumer oldConsumer) { BlockingQueueConsumer consumer = oldConsumer; synchronized (this.consumersMonitor) { if (this.consumers != null) { try { // Need to recycle the channel in this consumer consumer.stop(); // Ensure consumer counts are correct (another is going // to start because of the exception, but // we haven't counted down yet) this.cancellationLock.release(consumer); this.consumers.remove(consumer); BlockingQueueConsumer newConsumer = createBlockingQueueConsumer(); newConsumer.setBackOffExecution(consumer.getBackOffExecution()); consumer = newConsumer; this.consumers.add(consumer); if (this.applicationEventPublisher != null) { this.applicationEventPublisher .publishEvent(new AsyncConsumerRestartedEvent(this, oldConsumer, newConsumer)); } } catch (RuntimeException e) { logger.warn("Consumer failed irretrievably on restart. " + e.getClass() + ": " + e.getMessage()); // Re-throw and have it logged properly by the caller. throw e; } this.taskExecutor.execute(new AsyncMessageProcessingConsumer(consumer)); } } } /** * Use {@link RabbitAdmin#initialize()} to redeclare everything if necessary. * Since auto deletion of a queue can cause upstream elements * (bindings, exchanges) to be deleted too, everything needs to be redeclared if * a queue is missing. * Declaration is idempotent so, aside from some network chatter, there is no issue, * and we only will do it if we detect our queue is gone. *

* In general it makes sense only for the 'auto-delete' or 'expired' queues, * but with the server TTL policy we don't have ability to determine 'expiration' * option for the queue. *

* Starting with version 1.6, if * {@link #setMismatchedQueuesFatal(boolean) mismatchedQueuesFatal} is true, * the declarations are always attempted during restart so the listener will * fail with a fatal error if mismatches occur. */ protected synchronized void redeclareElementsIfNecessary() { if (this.rabbitAdmin == null) { return; } try { ApplicationContext applicationContext = this.getApplicationContext(); if (applicationContext != null) { Set queueNames = this.getQueueNamesAsSet(); Map queueBeans = applicationContext.getBeansOfType(Queue.class); for (Map.Entry entry : queueBeans.entrySet()) { Queue queue = entry.getValue(); if (this.mismatchedQueuesFatal || (queueNames.contains(queue.getName()) && this.rabbitAdmin.getQueueProperties(queue.getName()) == null)) { if (logger.isDebugEnabled()) { logger.debug("Redeclaring context exchanges, queues, bindings."); } this.rabbitAdmin.initialize(); return; } } } } catch (Exception e) { if (RabbitUtils.isMismatchedQueueArgs(e)) { throw new FatalListenerStartupException("Mismatched queues", e); } logger.error("Failed to check/redeclare auto-delete queue(s).", e); } } private boolean receiveAndExecute(final BlockingQueueConsumer consumer) throws Throwable { if (this.transactionManager != null) { try { if (this.transactionTemplate == null) { this.transactionTemplate = new TransactionTemplate(this.transactionManager, this.transactionAttribute); } return this.transactionTemplate .execute(new TransactionCallback() { @Override public Boolean doInTransaction(TransactionStatus status) { RabbitResourceHolder resourceHolder = ConnectionFactoryUtils.bindResourceToTransaction( new RabbitResourceHolder(consumer.getChannel(), false), getConnectionFactory(), true); try { return doReceiveAndExecute(consumer); } catch (RuntimeException e) { prepareHolderForRollback(resourceHolder, e); throw e; } catch (Throwable e) { //NOSONAR // ok to catch Throwable here because we re-throw it below throw new WrappedTransactionException(e); } } }); } catch (WrappedTransactionException e) { throw e.getCause(); } } return doReceiveAndExecute(consumer); } /** * A null resource holder is rare, but possible if the transaction attribute caused no * transaction to be started (e.g. {@code TransactionDefinition.PROPAGATION_NONE}). In * that case the delivery tags will have been processed manually. * @param resourceHolder the bound resource holder (if a transaction is active). * @param exception the exception. */ private void prepareHolderForRollback(RabbitResourceHolder resourceHolder, RuntimeException exception) { if (resourceHolder != null) { resourceHolder.setRequeueOnRollback(this.alwaysRequeueWithTxManagerRollback || RabbitUtils.shouldRequeue(this.defaultRequeueRejected, exception, logger)); } } private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Throwable { //NOSONAR Channel channel = consumer.getChannel(); for (int i = 0; i < this.txSize; i++) { logger.trace("Waiting for message from consumer."); Message message = consumer.nextMessage(this.receiveTimeout); if (message == null) { break; } try { executeListener(channel, message); } catch (ImmediateAcknowledgeAmqpException e) { if (this.logger.isDebugEnabled()) { this.logger.debug("User requested ack for failed delivery: " + message.getMessageProperties().getDeliveryTag()); } break; } catch (Throwable ex) { //NOSONAR if (causeChainHasImmediateAcknowledgeAmqpException(ex)) { if (this.logger.isDebugEnabled()) { this.logger.debug("User requested ack for failed delivery: " + message.getMessageProperties().getDeliveryTag()); } break; } if (this.transactionManager != null) { if (this.transactionAttribute.rollbackOn(ex)) { RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager .getResource(getConnectionFactory()); if (resourceHolder != null) { consumer.clearDeliveryTags(); } else { /* * If we don't actually have a transaction, we have to roll back * manually. See prepareHolderForRollback(). */ consumer.rollbackOnExceptionIfNecessary(ex); } throw ex; // encompassing transaction will handle the rollback. } else { if (this.logger.isDebugEnabled()) { this.logger.debug("No rollback for " + ex); } break; } } else { consumer.rollbackOnExceptionIfNecessary(ex); throw ex; } } } return consumer.commitIfNecessary(isChannelLocallyTransacted(channel)); } private Advice[] getAdviceChain() { return this.adviceChain; } @Override protected void invokeListener(Channel channel, Message message) throws Exception { this.proxy.invokeListener(channel, message); } /** * Wait for a period determined by the {@link #setRecoveryInterval(long) recoveryInterval} * or {@link #setRecoveryBackOff(BackOff)} to give the container a * chance to recover from consumer startup failure, e.g. if the broker is down. * @param backOffExecution the BackOffExecution to get the {@code recoveryInterval} * @throws Exception if the shared connection still can't be established */ protected void handleStartupFailure(BackOffExecution backOffExecution) throws Exception { long recoveryInterval = backOffExecution.nextBackOff(); if (BackOffExecution.STOP == recoveryInterval) { synchronized (this) { if (isActive()) { logger.warn("stopping container - restart recovery attempts exhausted"); stop(); } } return; } try { if (logger.isDebugEnabled() && isActive()) { logger.debug("Recovering consumer in " + recoveryInterval + " ms."); } long timeout = System.currentTimeMillis() + recoveryInterval; while (isActive() && System.currentTimeMillis() < timeout) { Thread.sleep(200); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException("Unrecoverable interruption on consumer restart"); } } @Override public String toString() { return "SimpleMessageListenerContainer " + (getBeanName() != null ? "(" + getBeanName() + ") " : "") + "[concurrentConsumers=" + this.concurrentConsumers + (this.maxConcurrentConsumers != null ? ", maxConcurrentConsumers=" + this.maxConcurrentConsumers : "") + ", queueNames=" + Arrays.toString(getQueueNames()) + "]"; } public interface ContainerDelegate { void invokeListener(Channel channel, Message message) throws Exception; } private final class AsyncMessageProcessingConsumer implements Runnable { private final BlockingQueueConsumer consumer; private final CountDownLatch start; private volatile FatalListenerStartupException startupException; private AsyncMessageProcessingConsumer(BlockingQueueConsumer consumer) { this.consumer = consumer; this.start = new CountDownLatch(1); } /** * Retrieve the fatal startup exception if this processor completely failed to locate the broker resources it * needed. Blocks up to 60 seconds waiting for an exception to occur * (but should always return promptly in normal circumstances). * No longer fatal if the processor does not start up in 60 seconds. * @return a startup exception if there was one * @throws TimeoutException if the consumer hasn't started * @throws InterruptedException if the consumer startup is interrupted */ private FatalListenerStartupException getStartupException() throws TimeoutException, InterruptedException { this.start.await(60000L, TimeUnit.MILLISECONDS); //NOSONAR - ignore return value return this.startupException; } @Override public void run() { boolean aborted = false; int consecutiveIdles = 0; int consecutiveMessages = 0; this.consumer.setLocallyTransacted(isChannelLocallyTransacted(null)); String routingLookupKey = getRoutingLookupKey(); if (routingLookupKey != null) { SimpleResourceHolder.bind(getRoutingConnectionFactory(), routingLookupKey); } if (this.consumer.getQueueCount() < 1) { if (logger.isDebugEnabled()) { logger.debug("Consumer stopping; no queues for " + this.consumer); } SimpleMessageListenerContainer.this.cancellationLock.release(this.consumer); if (SimpleMessageListenerContainer.this.applicationEventPublisher != null) { SimpleMessageListenerContainer.this.applicationEventPublisher.publishEvent( new AsyncConsumerStoppedEvent(SimpleMessageListenerContainer.this, this.consumer)); } this.start.countDown(); return; } try { try { if (SimpleMessageListenerContainer.this.autoDeclare) { SimpleMessageListenerContainer.this.redeclareElementsIfNecessary(); } this.consumer.start(); this.start.countDown(); } catch (QueuesNotAvailableException e) { if (SimpleMessageListenerContainer.this.missingQueuesFatal) { throw e; } else { this.start.countDown(); handleStartupFailure(this.consumer.getBackOffExecution()); throw e; } } catch (FatalListenerStartupException ex) { throw ex; } catch (Throwable t) { //NOSONAR this.start.countDown(); handleStartupFailure(this.consumer.getBackOffExecution()); throw t; } if (SimpleMessageListenerContainer.this.transactionManager != null) { /* * Register the consumer's channel so it will be used by the transaction manager * if it's an instance of RabbitTransactionManager. */ ConsumerChannelRegistry.registerConsumerChannel(this.consumer.getChannel(), getConnectionFactory()); } while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) { try { boolean receivedOk = receiveAndExecute(this.consumer); // At least one message received if (SimpleMessageListenerContainer.this.maxConcurrentConsumers != null) { if (receivedOk) { if (isActive(this.consumer)) { consecutiveIdles = 0; if (consecutiveMessages++ > SimpleMessageListenerContainer.this.consecutiveActiveTrigger) { considerAddingAConsumer(); consecutiveMessages = 0; } } } else { consecutiveMessages = 0; if (consecutiveIdles++ > SimpleMessageListenerContainer.this.consecutiveIdleTrigger) { considerStoppingAConsumer(this.consumer); consecutiveIdles = 0; } } } if (SimpleMessageListenerContainer.this.idleEventInterval != null) { if (receivedOk) { SimpleMessageListenerContainer.this.lastReceive = System.currentTimeMillis(); } else { long now = System.currentTimeMillis(); long lastAlertAt = SimpleMessageListenerContainer.this.lastNoMessageAlert.get(); long lastReceive = SimpleMessageListenerContainer.this.lastReceive; if (now > lastReceive + SimpleMessageListenerContainer.this.idleEventInterval && now > lastAlertAt + SimpleMessageListenerContainer.this.idleEventInterval && SimpleMessageListenerContainer.this.lastNoMessageAlert .compareAndSet(lastAlertAt, now)) { publishIdleContainerEvent(now - lastReceive); } } } } catch (ListenerExecutionFailedException ex) { // Continue to process, otherwise re-throw if (ex.getCause() instanceof NoSuchMethodException) { throw new FatalListenerExecutionException("Invalid listener", ex); } } catch (AmqpRejectAndDontRequeueException rejectEx) { /* * These will normally be wrapped by an LEFE if thrown by the * listener, but we will also honor it if thrown by an * error handler. */ } } } catch (InterruptedException e) { logger.debug("Consumer thread interrupted, processing stopped."); Thread.currentThread().interrupt(); aborted = true; publishConsumerFailedEvent("Consumer thread interrupted, processing stopped", true, e); } catch (QueuesNotAvailableException ex) { if (SimpleMessageListenerContainer.this.missingQueuesFatal) { logger.error("Consumer received fatal exception on startup", ex); this.startupException = ex; // Fatal, but no point re-throwing, so just abort. aborted = true; } publishConsumerFailedEvent("Consumer queue(s) not available", aborted, ex); } catch (FatalListenerStartupException ex) { logger.error("Consumer received fatal exception on startup", ex); this.startupException = ex; // Fatal, but no point re-throwing, so just abort. aborted = true; publishConsumerFailedEvent("Consumer received fatal exception on startup", true, ex); } catch (FatalListenerExecutionException ex) { logger.error("Consumer received fatal exception during processing", ex); // Fatal, but no point re-throwing, so just abort. aborted = true; publishConsumerFailedEvent("Consumer received fatal exception during processing", true, ex); } catch (ShutdownSignalException e) { if (RabbitUtils.isNormalShutdown(e)) { if (logger.isDebugEnabled()) { logger.debug("Consumer received Shutdown Signal, processing stopped: " + e.getMessage()); } } else { this.logConsumerException(e); } } catch (AmqpIOException e) { if (e.getCause() instanceof IOException && e.getCause().getCause() instanceof ShutdownSignalException && e.getCause().getCause().getMessage().contains("in exclusive use")) { SimpleMessageListenerContainer.this.exclusiveConsumerExceptionLogger.log(logger, "Exclusive consumer failure", e.getCause().getCause()); publishConsumerFailedEvent("Consumer raised exception, attempting restart", false, e); } else { this.logConsumerException(e); } } catch (Error e) { //NOSONAR // ok to catch Error - we're aborting so will stop logger.error("Consumer thread error, thread abort.", e); aborted = true; } catch (Throwable t) { //NOSONAR // by now, it must be an exception if (isActive()) { this.logConsumerException(t); } } finally { if (SimpleMessageListenerContainer.this.transactionManager != null) { ConsumerChannelRegistry.unRegisterConsumerChannel(); } } // In all cases count down to allow container to progress beyond startup this.start.countDown(); if (!isActive(this.consumer) || aborted) { logger.debug("Cancelling " + this.consumer); try { this.consumer.stop(); SimpleMessageListenerContainer.this.cancellationLock.release(this.consumer); if (SimpleMessageListenerContainer.this.applicationEventPublisher != null) { SimpleMessageListenerContainer.this.applicationEventPublisher.publishEvent( new AsyncConsumerStoppedEvent(SimpleMessageListenerContainer.this, this.consumer)); } } catch (AmqpException e) { logger.info("Could not cancel message consumer", e); } if (aborted) { logger.error("Stopping container from aborted consumer"); stop(); } } else { logger.info("Restarting " + this.consumer); restart(this.consumer); } if (routingLookupKey != null) { SimpleResourceHolder.unbind(getRoutingConnectionFactory()); } } private void logConsumerException(Throwable t) { if (logger.isDebugEnabled() || !(t instanceof AmqpConnectException || t instanceof ConsumerCancelledException)) { logger.debug( "Consumer raised exception, processing can restart if the connection factory supports it", t); } else { if (t instanceof ConsumerCancelledException && this.consumer.isNormalCancel()) { if (logger.isDebugEnabled()) { logger.debug( "Consumer raised exception, processing can restart if the connection factory supports it. " + "Exception summary: " + t); } } else if (logger.isWarnEnabled()) { logger.warn( "Consumer raised exception, processing can restart if the connection factory supports it. " + "Exception summary: " + t); } } publishConsumerFailedEvent("Consumer raised exception, attempting restart", false, t); } private void publishConsumerFailedEvent(String reason, boolean fatal, Throwable t) { if (SimpleMessageListenerContainer.this.applicationEventPublisher != null) { SimpleMessageListenerContainer.this.applicationEventPublisher .publishEvent(new ListenerContainerConsumerFailedEvent(SimpleMessageListenerContainer.this, reason, t, fatal)); } } private void publishIdleContainerEvent(long idleTime) { if (SimpleMessageListenerContainer.this.applicationEventPublisher != null) { SimpleMessageListenerContainer.this.applicationEventPublisher.publishEvent( new ListenerContainerIdleEvent(SimpleMessageListenerContainer.this, idleTime, getListenerId(), getQueueNames())); } } } @SuppressWarnings("serial") private static final class WrappedTransactionException extends RuntimeException { private WrappedTransactionException(Throwable cause) { super(cause); } } /** * Default implementation of {@link ConditionalExceptionLogger} for logging exclusive * consumer failures. * @since 1.5 */ private static class DefaultExclusiveConsumerLogger implements ConditionalExceptionLogger { @Override public void log(Log logger, String message, Throwable t) { if (t instanceof ShutdownSignalException) { ShutdownSignalException cause = (ShutdownSignalException) t; if (RabbitUtils.isExclusiveUseChannelClose(cause)) { if (logger.isWarnEnabled()) { logger.warn(message + ": " + cause.toString()); } } else if (!RabbitUtils.isNormalChannelClose(cause)) { logger.error(message + ": " + cause.getMessage()); } } else { logger.error("Unexpected invocation of " + this.getClass() + ", with message: " + message, t); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy