org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.java

Source

/*
 * Copyright 2002-2019 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
 *
 *      https://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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import org.springframework.amqp.AmqpAuthenticationException;
import org.springframework.amqp.AmqpConnectException;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.AmqpIOException;
import org.springframework.amqp.AmqpIllegalStateException;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.ImmediateAcknowledgeAmqpException;
import org.springframework.amqp.core.BatchMessageListener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.Queue;
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.listener.api.ChannelAwareBatchMessageListener;
import org.springframework.amqp.rabbit.listener.exception.FatalListenerExecutionException;
import org.springframework.amqp.rabbit.listener.exception.FatalListenerStartupException;
import org.springframework.amqp.rabbit.support.ActiveObjectCounter;
import org.springframework.amqp.rabbit.support.ConsumerCancelledException;
import org.springframework.amqp.rabbit.support.ListenerContainerAware;
import org.springframework.amqp.rabbit.support.ListenerExecutionFailedException;
import org.springframework.amqp.rabbit.support.RabbitExceptionTranslator;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.support.MetricType;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.backoff.BackOffExecution;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.PossibleAuthenticationFailureException;
import com.rabbitmq.client.ShutdownSignalException;

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

    private static final int RECOVERY_LOOP_WAIT_TIME = 200;

    private static final long DEFAULT_START_CONSUMER_MIN_INTERVAL = 10000;

    private static final long DEFAULT_STOP_CONSUMER_MIN_INTERVAL = 60000;

    private static final long DEFAULT_CONSUMER_START_TIMEOUT = 60000L;

    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;

    private final AtomicLong lastNoMessageAlert = new AtomicLong();

    private final AtomicReference<Thread> containerStoppingForAbort = new AtomicReference<>();

    private final BlockingQueue<ListenerContainerConsumerFailedEvent> abortEvents = new LinkedBlockingQueue<>();

    private long startConsumerMinInterval = DEFAULT_START_CONSUMER_MIN_INTERVAL;

    private long stopConsumerMinInterval = DEFAULT_STOP_CONSUMER_MIN_INTERVAL;

    private int consecutiveActiveTrigger = DEFAULT_CONSECUTIVE_ACTIVE_TRIGGER;

    private int consecutiveIdleTrigger = DEFAULT_CONSECUTIVE_IDLE_TRIGGER;

    private int batchSize = 1;

    private boolean consumerBatchEnabled;

    private long receiveTimeout = DEFAULT_RECEIVE_TIMEOUT;

    private Set<BlockingQueueConsumer> consumers;

    private final ActiveObjectCounter<BlockingQueueConsumer> cancellationLock = new ActiveObjectCounter<>();

    private Integer declarationRetries;

    private Long retryDeclarationInterval;

    private TransactionTemplate transactionTemplate;

    private long consumerStartTimeout = DEFAULT_CONSUMER_START_TIMEOUT;

    private volatile int concurrentConsumers = 1;

    private volatile Integer maxConcurrentConsumers;

    private volatile long lastConsumerStarted;

    private volatile long lastConsumerStopped;

    /**
     * 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) {
        setConnectionFactory(connectionFactory);
    }

    /**
     * Specify the number of concurrent consumers to create. Default is 1.
     * <p>
     * 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(!isExclusive() || 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()) {
                adjustConsumers(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(!isExclusive() || maxConcurrentConsumers == 1,
                "When the consumer is exclusive, the concurrency must be 1");
        Integer oldMax = this.maxConcurrentConsumers;
        this.maxConcurrentConsumers = maxConcurrentConsumers;
        if (oldMax != null && isActive()) {
            int delta = oldMax - maxConcurrentConsumers;
            if (delta > 0) { // only decrease, not increase
                adjustConsumers(delta);
            }
        }

    }

    /**
     * Specify concurrency limits via a "lower-upper" String, e.g. "5-10", or a simple
     * upper limit String, e.g. "10" (a fixed number of consumers).
     * <p>This listener container will always hold on to the minimum number of consumers
     * ({@link #setConcurrentConsumers}) and will slowly scale up to the maximum number
     * of consumers {@link #setMaxConcurrentConsumers} in case of increasing load.
     * @param concurrency the concurrency.
     * @since 2.0
     */
    public void setConcurrency(String concurrency) {
        try {
            int separatorIndex = concurrency.indexOf('-');
            if (separatorIndex != -1) {
                setConcurrentConsumers(Integer.parseInt(concurrency.substring(0, separatorIndex)));
                setMaxConcurrentConsumers(
                        Integer.parseInt(concurrency.substring(separatorIndex + 1, concurrency.length())));
            } else {
                setConcurrentConsumers(Integer.parseInt(concurrency));
            }
        } catch (NumberFormatException ex) {
            throw new IllegalArgumentException("Invalid concurrency value [" + concurrency + "]: only "
                    + "single fixed integer (e.g. \"5\") and minimum-maximum combo (e.g. \"3-5\") supported.", ex);
        }
    }

    /**
     * Set to true for an exclusive consumer - if true, the concurrency must be 1.
     * @param exclusive true for an exclusive consumer.
     */
    @Override
    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");
        super.setExclusive(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 #batchSize}.
     * Default is 10 consecutive messages.
     * @param consecutiveActiveTrigger The number of consecutive receives to trigger a new consumer.
     * @see #setMaxConcurrentConsumers(int)
     * @see #setStartConsumerMinInterval(long)
     * @see #setBatchSize(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 #batchSize} * this value because the consumer thread waits for
     * a message for up to {@link #receiveTimeout} up to {@link #batchSize} 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 #setBatchSize(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;
    }

    /**
     * This property has several functions.
     * <p>
     * When the channel is transacted, it determines how many messages to process in a
     * single transaction. It should be less than or equal to
     * {@link #setPrefetchCount(int) the prefetch count}.
     * <p>
     * It also affects how often acks are sent when using
     * {@link org.springframework.amqp.core.AcknowledgeMode#AUTO} - one ack per BatchSize.
     * <p>
     * Finally, when {@link #setConsumerBatchEnabled(boolean)} is true, it determines how
     * many records to include in the batch as long as sufficient messages arrive within
     * {@link #setReceiveTimeout(long)}.
     * <p>
     * <b>IMPORTANT</b> The batch size represents the number of physical messages
     * received. If {@link #setDeBatchingEnabled(boolean)} is true and a message is a
     * batch created by a producer, the actual number of messages received by the listener
     * will be larger than this batch size.
     * <p>
     *
     * Default is 1.
     * @param batchSize the batch size
     * @since 2.2
     * @see #setConsumerBatchEnabled(boolean)
     * @see #setDeBatchingEnabled(boolean)
     */
    public void setBatchSize(int batchSize) {
        Assert.isTrue(batchSize > 0, "'batchSize' must be > 0");
        this.batchSize = batchSize;
    }

    /**
     * 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 org.springframework.amqp.core.AcknowledgeMode#AUTO} - one
     * ack per txSize. Default is 1.
     * @param txSize the transaction size
     * @deprecated since 2.2 in favor of {@link #setBatchSize(int)}.
     */
    @Deprecated
    public void setTxSize(int txSize) {
        setBatchSize(txSize);
    }

    /**
     * Set to true to present a list of messages based on the {@link #setBatchSize(int)},
     * if the listener supports it.
     * @param consumerBatchEnabled true to create message batches in the container.
     * @since 2.2
     * @see #setBatchSize(int)
     */
    public void setConsumerBatchEnabled(boolean consumerBatchEnabled) {
        this.consumerBatchEnabled = consumerBatchEnabled;
    }

    /**
     * {@inheritDoc}
     * <p>
     * When true, if the queues are removed while the container is running, the container
     * is stopped.
     * <p>
     * Defaults to true for this container.
     */
    @Override
    public void setMissingQueuesFatal(boolean missingQueuesFatal) { // NOSONAR - noop override is for enhanced javadocs
        super.setMissingQueuesFatal(missingQueuesFatal);
    }

    @Override
    public void setQueueNames(String... queueName) {
        super.setQueueNames(queueName);
        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 queueName The queue to add.
     */
    @Override
    public void addQueueNames(String... queueName) {
        super.addQueueNames(queueName);
        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)) {
            queuesChanged();
            return true;
        } else {
            return false;
        }
    }

    /**
     * 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);
        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 queue The queue to remove.
     */
    @Override
    public boolean removeQueues(Queue... queue) {
        if (super.removeQueues(queue)) {
            queuesChanged();
            return true;
        } else {
            return false;
        }
    }

    /**
     * Set the number of retries after passive queue declaration fails.
     * @param declarationRetries The number of retries, default 3.
     * @since 1.3.9
     * @see #setFailedDeclarationRetryInterval(long)
     */
    public void setDeclarationRetries(int declarationRetries) {
        this.declarationRetries = declarationRetries;
    }

    /**
     * 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;
    }

    /**
     * When starting a consumer, if this time (ms) elapses before the consumer starts, an
     * error log is written; one possible cause would be if the
     * {@link #setTaskExecutor(java.util.concurrent.Executor) taskExecutor} has
     * insufficient threads to support the container concurrency. Default 60000.
     * @param consumerStartTimeout the timeout.
     * @since 1.7.5
     */
    public void setConsumerStartTimeout(long consumerStartTimeout) {
        this.consumerStartTimeout = consumerStartTimeout;
    }

    /**
     * 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() && getTransactionManager() != 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.");

    }

    // -------------------------------------------------------------------------
    // Implementation of AbstractMessageListenerContainer's template methods
    // -------------------------------------------------------------------------

    /**
     * Always use a shared Rabbit Connection.
     * @return true
     */
    protected final boolean sharedConnectionEnabled() {
        return true;
    }

    @Override
    protected void doInitialize() {
        Assert.state(
                !this.consumerBatchEnabled || getMessageListener() instanceof BatchMessageListener
                        || getMessageListener() instanceof ChannelAwareBatchMessageListener,
                "When setting 'consumerBatchEnabled' to true, the listener must support batching");
        Assert.state(!this.consumerBatchEnabled || isDeBatchingEnabled(),
                "When setting 'consumerBatchEnabled' to true, 'deBatchingEnabled' must also be 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.
     */
    @Override
    protected void doStart() {
        checkListenerContainerAware();
        super.doStart();
        synchronized (this.consumersMonitor) {
            if (this.consumers != null) {
                throw new IllegalStateException("A stopped container should not have consumers");
            }
            int newConsumers = initializeConsumers();
            if (this.consumers == null) {
                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<AsyncMessageProcessingConsumer> processors = new HashSet<AsyncMessageProcessingConsumer>();
            for (BlockingQueueConsumer consumer : this.consumers) {
                AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
                processors.add(processor);
                getTaskExecutor().execute(processor);
                if (getApplicationEventPublisher() != null) {
                    getApplicationEventPublisher().publishEvent(new AsyncConsumerStartedEvent(this, consumer));
                }
            }
            waitForConsumersToStart(processors);
        }
    }

    private void checkListenerContainerAware() {
        if (getMessageListener() instanceof ListenerContainerAware) {
            Collection<String> 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));
            }
        }
    }

    private void waitForConsumersToStart(Set<AsyncMessageProcessingConsumer> processors) {
        for (AsyncMessageProcessingConsumer processor : processors) {
            FatalListenerStartupException startupException = null;
            try {
                startupException = processor.getStartupException();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw RabbitExceptionTranslator.convertRabbitAccessException(e);
            }
            if (startupException != null) {
                throw new AmqpIllegalStateException("Fatal exception on listener startup", startupException);
            }
        }
    }

    @Override
    protected void doShutdown() {
        Thread thread = this.containerStoppingForAbort.get();
        if (thread != null && !thread.equals(Thread.currentThread())) {
            logger.info("Shutdown ignored - container is stopping due to an aborted consumer");
            return;
        }

        try {
            List<BlockingQueueConsumer> canceledConsumers = new ArrayList<>();
            synchronized (this.consumersMonitor) {
                if (this.consumers != null) {
                    Iterator<BlockingQueueConsumer> consumerIterator = this.consumers.iterator();
                    while (consumerIterator.hasNext()) {
                        BlockingQueueConsumer consumer = consumerIterator.next();
                        consumer.basicCancel(true);
                        canceledConsumers.add(consumer);
                        consumerIterator.remove();
                        if (consumer.declaring) {
                            consumer.thread.interrupt();
                        }
                    }
                } else {
                    logger.info("Shutdown ignored - container is already stopped");
                    return;
                }
            }
            logger.info("Waiting for workers to finish.");
            boolean finished = this.cancellationLock.await(getShutdownTimeout(), TimeUnit.MILLISECONDS);
            if (finished) {
                logger.info("Successfully waited for workers to finish.");
            } else {
                logger.info("Workers not finished.");
                if (isForceCloseChannel()) {
                    canceledConsumers.forEach(consumer -> {
                        if (logger.isWarnEnabled()) {
                            logger.warn("Closing channel for unresponsive consumer: " + consumer);
                        }
                        consumer.stop();
                    });
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            logger.warn("Interrupted waiting for workers.  Continuing with shutdown.");
        }

        synchronized (this.consumersMonitor) {
            this.consumers = null;
            this.cancellationLock.deactivate();
        }

    }

    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<BlockingQueueConsumer>(this.concurrentConsumers);
                for (int i = 0; i < this.concurrentConsumers; i++) {
                    BlockingQueueConsumer consumer = createBlockingQueueConsumer();
                    this.consumers.add(consumer);
                    count++;
                }
            }
        }
        return count;
    }

    /**
     * Adjust consumers depending on delta.
     * @param deltaArg a negative value increases, positive decreases.
     * @since 1.7.8
     */
    protected void adjustConsumers(int deltaArg) {
        int delta = deltaArg;
        synchronized (this.consumersMonitor) {
            if (isActive() && this.consumers != null) {
                if (delta > 0) {
                    Iterator<BlockingQueueConsumer> consumerIterator = this.consumers.iterator();
                    while (consumerIterator.hasNext() && delta > 0 && (this.maxConcurrentConsumers == null
                            || this.consumers.size() > this.maxConcurrentConsumers)) {
                        BlockingQueueConsumer consumer = consumerIterator.next();
                        consumer.basicCancel(true);
                        consumerIterator.remove();
                        delta--;
                    }
                } else {
                    addAndStartConsumers(-delta);
                }
            }
        }
    }

    /**
     * Start up to delta consumers, limited by {@link #setMaxConcurrentConsumers(int)}.
     * @param delta the consumers to add.
     */
    protected void addAndStartConsumers(int delta) {
        synchronized (this.consumersMonitor) {
            if (this.consumers != null) {
                for (int i = 0; i < delta; i++) {
                    if (this.maxConcurrentConsumers != null
                            && this.consumers.size() >= this.maxConcurrentConsumers) {
                        break;
                    }
                    BlockingQueueConsumer consumer = createBlockingQueueConsumer();
                    this.consumers.add(consumer);
                    AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Starting a new consumer: " + consumer);
                    }
                    getTaskExecutor().execute(processor);
                    if (this.getApplicationEventPublisher() != null) {
                        this.getApplicationEventPublisher()
                                .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<BlockingQueueConsumer> 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);
            }
        }
    }

    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 = getPrefetchCount() > this.batchSize ? getPrefetchCount() : this.batchSize;
        consumer = new BlockingQueueConsumer(getConnectionFactory(), getMessagePropertiesConverter(),
                this.cancellationLock, getAcknowledgeMode(), isChannelTransacted(), actualPrefetchCount,
                isDefaultRequeueRejected(), getConsumerArguments(), isNoLocal(), isExclusive(), queues);
        if (this.declarationRetries != null) {
            consumer.setDeclarationRetries(this.declarationRetries);
        }
        if (getFailedDeclarationRetryInterval() > 0) {
            consumer.setFailedDeclarationRetryInterval(getFailedDeclarationRetryInterval());
        }
        if (this.retryDeclarationInterval != null) {
            consumer.setRetryDeclarationInterval(this.retryDeclarationInterval);
        }
        if (getConsumerTagStrategy() != null) {
            consumer.setTagStrategy(getConsumerTagStrategy()); // NOSONAR never null here
        }
        consumer.setBackOffExecution(getRecoveryBackOff().start());
        consumer.setShutdownTimeout(getShutdownTimeout());
        consumer.setApplicationEventPublisher(getApplicationEventPublisher());
        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);
                    if (!isActive()) {
                        // Do not restart - container is stopping
                        return;
                    }
                    BlockingQueueConsumer newConsumer = createBlockingQueueConsumer();
                    newConsumer.setBackOffExecution(consumer.getBackOffExecution());
                    consumer = newConsumer;
                    this.consumers.add(consumer);
                    if (getApplicationEventPublisher() != null) {
                        getApplicationEventPublisher()
                                .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;
                }
                getTaskExecutor().execute(new AsyncMessageProcessingConsumer(consumer));
            }
        }
    }

    private boolean receiveAndExecute(final BlockingQueueConsumer consumer) throws Exception { // NOSONAR

        PlatformTransactionManager transactionManager = getTransactionManager();
        if (transactionManager != null) {
            try {
                if (this.transactionTemplate == null) {
                    this.transactionTemplate = new TransactionTemplate(transactionManager,
                            getTransactionAttribute());
                }
                return this.transactionTemplate.execute(status -> { // NOSONAR null never returned
                    RabbitResourceHolder resourceHolder = ConnectionFactoryUtils.bindResourceToTransaction(
                            new RabbitResourceHolder(consumer.getChannel(), false), getConnectionFactory(), true);
                    // unbound in ResourceHolderSynchronization.beforeCompletion()
                    try {
                        return doReceiveAndExecute(consumer);
                    } catch (RuntimeException e1) {
                        prepareHolderForRollback(resourceHolder, e1);
                        throw e1;
                    } catch (Exception e2) {
                        throw new WrappedTransactionException(e2);
                    }
                });
            } catch (WrappedTransactionException e) { // NOSONAR exception flow control
                throw (Exception) e.getCause();
            }
        }

        return doReceiveAndExecute(consumer);

    }

    private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Exception { //NOSONAR

        Channel channel = consumer.getChannel();

        List<Message> messages = null;
        long deliveryTag = 0;

        for (int i = 0; i < this.batchSize; i++) {

            logger.trace("Waiting for message from consumer.");
            Message message = consumer.nextMessage(this.receiveTimeout);
            if (message == null) {
                break;
            }
            if (this.consumerBatchEnabled) {
                Collection<MessagePostProcessor> afterReceivePostProcessors = getAfterReceivePostProcessors();
                if (afterReceivePostProcessors != null) {
                    Message original = message;
                    deliveryTag = message.getMessageProperties().getDeliveryTag();
                    for (MessagePostProcessor processor : getAfterReceivePostProcessors()) {
                        message = processor.postProcessMessage(message);
                        if (message == null) {
                            channel.basicAck(deliveryTag, false);
                            if (this.logger.isDebugEnabled()) {
                                this.logger.debug(
                                        "Message Post Processor returned 'null', discarding message " + original);
                            }
                            break;
                        }
                    }
                }
                if (message != null) {
                    if (messages == null) {
                        messages = new ArrayList<>(this.batchSize);
                    }
                    if (isDeBatchingEnabled() && getBatchingStrategy().canDebatch(message.getMessageProperties())) {
                        final List<Message> messageList = messages;
                        getBatchingStrategy().deBatch(message, fragment -> messageList.add(fragment));
                    } else {
                        messages.add(message);
                    }
                }
            } else {
                try {
                    executeListener(channel, message);
                } catch (ImmediateAcknowledgeAmqpException e) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("User requested ack for failed delivery '" + e.getMessage() + "': "
                                + message.getMessageProperties().getDeliveryTag());
                    }
                    break;
                } catch (Exception ex) {
                    if (causeChainHasImmediateAcknowledgeAmqpException(ex)) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("User requested ack for failed delivery: "
                                    + message.getMessageProperties().getDeliveryTag());
                        }
                        break;
                    }
                    if (getTransactionManager() != null) {
                        if (getTransactionAttribute().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;
                    }
                }
            }
        }
        if (this.consumerBatchEnabled && messages != null) {
            executeWithList(channel, messages, deliveryTag, consumer);
        }

        return consumer.commitIfNecessary(isChannelLocallyTransacted());

    }

    private void executeWithList(Channel channel, List<Message> messages, long deliveryTag,
            BlockingQueueConsumer consumer) {

        try {
            executeListener(channel, messages);
        } catch (ImmediateAcknowledgeAmqpException e) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("User requested ack for failed delivery '" + e.getMessage()
                        + "' (last in batch): " + deliveryTag);
            }
            return;
        } catch (Exception ex) {
            if (causeChainHasImmediateAcknowledgeAmqpException(ex)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("User requested ack for failed delivery (last in batch): " + deliveryTag);
                }
                return;
            }
            if (getTransactionManager() != null) {
                if (getTransactionAttribute().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);
                    }
                }
            } else {
                consumer.rollbackOnExceptionIfNecessary(ex);
                throw ex;
            }
        }
    }

    protected void handleStartupFailure(BackOffExecution backOffExecution) {
        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(RECOVERY_LOOP_WAIT_TIME);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("Irrecoverable interruption on consumer restart", e);
        }
    }

    @Override
    protected void publishConsumerFailedEvent(String reason, boolean fatal, Throwable t) {
        if (!fatal || !isRunning()) {
            super.publishConsumerFailedEvent(reason, fatal, t);
        } else {
            try {
                this.abortEvents.put(new ListenerContainerConsumerFailedEvent(this, reason, t, fatal));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    @Override
    public String toString() {
        return "SimpleMessageListenerContainer " + (getBeanName() != null ? "(" + getBeanName() + ") " : "")
                + "[concurrentConsumers=" + this.concurrentConsumers
                + (this.maxConcurrentConsumers != null ? ", maxConcurrentConsumers=" + this.maxConcurrentConsumers
                        : "")
                + ", queueNames=" + Arrays.toString(getQueueNames()) + "]";
    }

    private final class AsyncMessageProcessingConsumer implements Runnable {

        private static final int ABORT_EVENT_WAIT_SECONDS = 5;

        private final BlockingQueueConsumer consumer;

        private final CountDownLatch start;

        private volatile FatalListenerStartupException startupException;

        private int consecutiveIdles;

        private int consecutiveMessages;

        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 InterruptedException if the consumer startup is interrupted
         */
        private FatalListenerStartupException getStartupException() throws InterruptedException {
            if (!this.start.await(SimpleMessageListenerContainer.this.consumerStartTimeout,
                    TimeUnit.MILLISECONDS)) {
                logger.error("Consumer failed to start in "
                        + SimpleMessageListenerContainer.this.consumerStartTimeout
                        + " milliseconds; does the task executor have enough threads to support the container "
                        + "concurrency?");
            }
            return this.startupException;
        }

        @Override // NOSONAR - complexity - many catch blocks
        public void run() { // NOSONAR - line count
            if (!isActive()) {
                return;
            }

            boolean aborted = false;

            this.consumer.setLocallyTransacted(isChannelLocallyTransacted());

            String routingLookupKey = getRoutingLookupKey();
            if (routingLookupKey != null) {
                SimpleResourceHolder.bind(getRoutingConnectionFactory(), routingLookupKey); // NOSONAR both never null
            }

            if (this.consumer.getQueueCount() < 1) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Consumer stopping; no queues for " + this.consumer);
                }
                SimpleMessageListenerContainer.this.cancellationLock.release(this.consumer);
                if (getApplicationEventPublisher() != null) {
                    getApplicationEventPublisher().publishEvent(
                            new AsyncConsumerStoppedEvent(SimpleMessageListenerContainer.this, this.consumer));
                }
                this.start.countDown();
                return;
            }

            try {
                initialize();
                while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) {
                    mainLoop();
                }
            } 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) {
                logger.error("Consumer received fatal=" + isMismatchedQueuesFatal() + " exception on startup", ex);
                if (isMissingQueuesFatal()) {
                    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) { // NOSONAR exception as flow control
                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 (PossibleAuthenticationFailureException ex) {
                logger.error("Consumer received fatal=" + isPossibleAuthenticationFailureFatal()
                        + " exception during processing", ex);
                if (isPossibleAuthenticationFailureFatal()) {
                    this.startupException = new FatalListenerStartupException("Authentication failure",
                            new AmqpAuthenticationException(ex));
                    // Fatal, but no point re-throwing, so just abort.
                    aborted = true;
                }
                publishConsumerFailedEvent("Consumer received PossibleAuthenticationFailure during startup",
                        aborted, ex);
            } catch (ShutdownSignalException e) {
                if (RabbitUtils.isNormalShutdown(e)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Consumer received Shutdown Signal, processing stopped: " + e.getMessage());
                    }
                } else {
                    logConsumerException(e);
                }
            } catch (AmqpIOException e) {
                if (e.getCause() instanceof IOException
                        && e.getCause().getCause() instanceof ShutdownSignalException
                        && e.getCause().getCause().getMessage().contains("in exclusive use")) {
                    getExclusiveConsumerExceptionLogger().log(logger, "Exclusive consumer failure",
                            e.getCause().getCause());
                    publishConsumerFailedEvent("Consumer raised exception, attempting restart", false, e);
                } else {
                    logConsumerException(e);
                }
            } catch (Error e) { //NOSONAR
                // ok to catch Error - we're aborting so will stop
                logger.error("Consumer thread error, thread abort.", e);
                publishConsumerFailedEvent("Consumer threw an Error", true, e);
                aborted = true;
            } catch (Throwable t) { //NOSONAR
                // by now, it must be an exception
                if (isActive()) {
                    logConsumerException(t);
                }
            } finally {
                if (getTransactionManager() != null) {
                    ConsumerChannelRegistry.unRegisterConsumerChannel();
                }
            }

            // In all cases count down to allow container to progress beyond startup
            this.start.countDown();

            killOrRestart(aborted);

            if (routingLookupKey != null) {
                SimpleResourceHolder.unbind(getRoutingConnectionFactory()); // NOSONAR never null here
            }
        }

        private void mainLoop() throws Exception { // NOSONAR Exception
            try {
                boolean receivedOk = receiveAndExecute(this.consumer); // At least one message received
                if (SimpleMessageListenerContainer.this.maxConcurrentConsumers != null) {
                    checkAdjust(receivedOk);
                }
                long idleEventInterval = getIdleEventInterval();
                if (idleEventInterval > 0) {
                    if (receivedOk) {
                        updateLastReceive();
                    } else {
                        long now = System.currentTimeMillis();
                        long lastAlertAt = SimpleMessageListenerContainer.this.lastNoMessageAlert.get();
                        long lastReceive = getLastReceive();
                        if (now > lastReceive + idleEventInterval && now > lastAlertAt + 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.
                 */
            }
        }

        private void checkAdjust(boolean receivedOk) {
            if (receivedOk) {
                if (isActive(this.consumer)) {
                    this.consecutiveIdles = 0;
                    if (this.consecutiveMessages++ > SimpleMessageListenerContainer.this.consecutiveActiveTrigger) {
                        considerAddingAConsumer();
                        this.consecutiveMessages = 0;
                    }
                }
            } else {
                this.consecutiveMessages = 0;
                if (this.consecutiveIdles++ > SimpleMessageListenerContainer.this.consecutiveIdleTrigger) {
                    considerStoppingAConsumer(this.consumer);
                    this.consecutiveIdles = 0;
                }
            }
        }

        private void initialize() throws Throwable { // NOSONAR
            try {
                redeclareElementsIfNecessary();
                this.consumer.start();
                this.start.countDown();
            } catch (QueuesNotAvailableException e) {
                if (isMissingQueuesFatal()) {
                    throw e;
                } else {
                    this.start.countDown();
                    handleStartupFailure(this.consumer.getBackOffExecution());
                    throw e;
                }
            } catch (FatalListenerStartupException ex) {
                if (isPossibleAuthenticationFailureFatal()) {
                    throw ex;
                } else {
                    Throwable possibleAuthException = ex.getCause().getCause();
                    if (!(possibleAuthException instanceof PossibleAuthenticationFailureException)) {
                        throw ex;
                    } else {
                        this.start.countDown();
                        handleStartupFailure(this.consumer.getBackOffExecution());
                        throw possibleAuthException;
                    }
                }
            } catch (Throwable t) { //NOSONAR
                this.start.countDown();
                handleStartupFailure(this.consumer.getBackOffExecution());
                throw t;
            }

            if (getTransactionManager() != 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());
            }
        }

        private void killOrRestart(boolean aborted) {
            if (!isActive(this.consumer) || aborted) {
                logger.debug("Cancelling " + this.consumer);
                try {
                    this.consumer.stop();
                    SimpleMessageListenerContainer.this.cancellationLock.release(this.consumer);
                    if (getApplicationEventPublisher() != null) {
                        getApplicationEventPublisher().publishEvent(
                                new AsyncConsumerStoppedEvent(SimpleMessageListenerContainer.this, this.consumer));
                    }
                } catch (AmqpException e) {
                    logger.info("Could not cancel message consumer", e);
                }
                if (aborted && SimpleMessageListenerContainer.this.containerStoppingForAbort.compareAndSet(null,
                        Thread.currentThread())) {
                    logger.error("Stopping container from aborted consumer");
                    stop();
                    SimpleMessageListenerContainer.this.containerStoppingForAbort.set(null);
                    ListenerContainerConsumerFailedEvent event = null;
                    do {
                        try {
                            event = SimpleMessageListenerContainer.this.abortEvents.poll(ABORT_EVENT_WAIT_SECONDS,
                                    TimeUnit.SECONDS);
                            if (event != null) {
                                SimpleMessageListenerContainer.this.publishConsumerFailedEvent(event.getReason(),
                                        event.isFatal(), event.getThrowable());
                            }
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    } while (event != null);
                }
            } else {
                logger.info("Restarting " + this.consumer);
                restart(this.consumer);
            }
        }

        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);
        }

    }

}