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

com.rabbitmq.client.impl.recovery.TopologyRecoveryRetryLogic Maven / Gradle / Ivy

Go to download

The RabbitMQ Java client library allows Java applications to interface with RabbitMQ.

There is a newer version: 5.22.0
Show newest version
// Copyright (c) 2018-2020 VMware, Inc. or its affiliates.  All rights reserved.
//
// This software, the RabbitMQ Java client library, is triple-licensed under the
// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2.  For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// [email protected].

package com.rabbitmq.client.impl.recovery;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.ShutdownSignalException;
import com.rabbitmq.utility.Utility;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.Map.Entry;
import java.util.function.BiPredicate;
import static com.rabbitmq.client.impl.recovery.TopologyRecoveryRetryHandlerBuilder.builder;

/**
 * Useful ready-to-use conditions and operations for {@link DefaultRetryHandler}.
 * They're composed and used with the {@link TopologyRecoveryRetryHandlerBuilder}.
 *
 * @see DefaultRetryHandler
 * @see RetryHandler
 * @see TopologyRecoveryRetryHandlerBuilder
 * @since 5.4.0
 */
public abstract class TopologyRecoveryRetryLogic {

    /**
     * Channel has been closed because of a resource that doesn't exist.
     */
    public static final BiPredicate CHANNEL_CLOSED_NOT_FOUND = (entity, ex) -> {
        if (ex.getCause() instanceof ShutdownSignalException) {
            ShutdownSignalException cause = (ShutdownSignalException) ex.getCause();
            if (cause.getReason() instanceof AMQP.Channel.Close) {
                return ((AMQP.Channel.Close) cause.getReason()).getReplyCode() == 404;
            }
        }
        return false;
    };

    /**
     * Recover a channel.
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_CHANNEL = context -> {
        if (!context.entity().getChannel().isOpen()) {
            context.connection().recoverChannel(context.entity().getChannel());
        }
        return null;
    };
    
    /**
     * Recover a queue
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_QUEUE = context -> {
        if (context.entity() instanceof RecordedQueue) {
            final RecordedQueue recordedQueue = context.queue();
            AutorecoveringConnection connection = context.connection();
          connection.recoverQueue(recordedQueue.getName(), recordedQueue);
        }
        return null;
    };

    /**
     * Recover the destination queue of a binding.
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_BINDING_QUEUE = context -> {
        if (context.entity() instanceof RecordedQueueBinding) {
            RecordedBinding binding = context.binding();
            AutorecoveringConnection connection = context.connection();
            RecordedQueue recordedQueue = connection.getRecordedQueues().get(binding.getDestination());
            if (recordedQueue != null) {
              connection.recoverQueue(recordedQueue.getName(), recordedQueue);
            }
        }
        return null;
    };

    /**
     * Recover a binding.
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_BINDING = context -> {
        context.binding().recover();
        return null;
    };
    
    /**
     * Recover earlier bindings that share the same queue as this retry context
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_PREVIOUS_QUEUE_BINDINGS = context -> {
        if (context.entity() instanceof RecordedQueueBinding) {
            // recover all bindings for the same queue that were recovered before this current binding
            // need to do this incase some bindings had already been recovered successfully before the queue was deleted & this binding failed
            String queue = context.binding().getDestination();
            for (RecordedBinding recordedBinding : Utility.copy(context.connection().getRecordedBindings())) {
                if (recordedBinding == context.entity()) {
                    // we have gotten to the binding in this context. Since this is an ordered list we can now break
                    // as we know we have recovered all the earlier bindings that may have existed on this queue
                    break;
                } else if (recordedBinding instanceof RecordedQueueBinding && queue.equals(recordedBinding.getDestination())) {
                    recordedBinding.recover();
                }
            }
        }
        return null;
    };

    /**
     * Recover the queue of a consumer.
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_CONSUMER_QUEUE = context -> {
        if (context.entity() instanceof RecordedConsumer) {
            RecordedConsumer consumer = context.consumer();
            AutorecoveringConnection connection = context.connection();
            RecordedQueue recordedQueue = connection.getRecordedQueues().get(consumer.getQueue());
            if (recordedQueue != null) {
              connection.recoverQueue(recordedQueue.getName(), recordedQueue);
            }
        }
        return null;
    };

    /**
     * Recover all the bindings of the queue of a consumer.
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_CONSUMER_QUEUE_BINDINGS = context -> {
        if (context.entity() instanceof RecordedConsumer) {
            String queue = context.consumer().getQueue();
            for (RecordedBinding recordedBinding : Utility.copy(context.connection().getRecordedBindings())) {
                if (recordedBinding instanceof RecordedQueueBinding && queue.equals(recordedBinding.getDestination())) {
                    recordedBinding.recover();
                }
            }
        }
        return null;
    };

    /**
     * Recover a consumer.
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_CONSUMER = context -> context.consumer().recover();
    
    /**
     * Recover earlier consumers that share the same channel as this retry context
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_PREVIOUS_CONSUMERS = context -> {
        if (context.entity() instanceof RecordedConsumer) {
            // recover all consumers for the same channel that were recovered before this current
            // consumer. need to do this incase some consumers had already been recovered
            // successfully on a different queue before this one failed
            final AutorecoveringChannel channel = context.consumer().getChannel();
            for (RecordedConsumer consumer : Utility.copy(context.connection().getRecordedConsumers()).values()) {
                if (consumer == context.entity()) {
                    break;
                } else if (consumer.getChannel() == channel) {
                    final RetryContext retryContext = new RetryContext(consumer, context.exception(), context.connection());
                    RECOVER_CONSUMER_QUEUE.call(retryContext);
                    context.connection().recoverConsumer(consumer.getConsumerTag(), consumer);
                    RECOVER_CONSUMER_QUEUE_BINDINGS.call(retryContext);
                }
            }
            return context.consumer().getConsumerTag();
        }
        return null;
    };
    
    /**
     * Recover earlier auto-delete or exclusive queues that share the same channel as this retry context
     */
    public static final DefaultRetryHandler.RetryOperation RECOVER_PREVIOUS_AUTO_DELETE_QUEUES = context -> {
        if (context.entity() instanceof RecordedQueue) {
            AutorecoveringConnection connection = context.connection();
            RecordedQueue queue = context.queue();
            // recover all queues for the same channel that had already been recovered successfully before this queue failed.
            // If the previous ones were auto-delete or exclusive, they need recovered again
            for (Entry entry : Utility.copy(connection.getRecordedQueues()).entrySet()) {
                if (entry.getValue() == queue) {
                    // we have gotten to the queue in this context. Since this is an ordered map we can now break 
                    // as we know we have recovered all the earlier queues on this channel
                    break;
                } else if (queue.getChannel() == entry.getValue().getChannel() 
                        && (entry.getValue().isAutoDelete() || entry.getValue().isExclusive())) {
                    connection.recoverQueue(entry.getKey(), entry.getValue());
                }
            }
        } else if (context.entity() instanceof RecordedQueueBinding) {
            AutorecoveringConnection connection = context.connection();
            Set queues = new LinkedHashSet<>();
            for (Entry entry : Utility.copy(connection.getRecordedQueues()).entrySet()) {
                if (context.entity().getChannel() == entry.getValue().getChannel() 
                        && (entry.getValue().isAutoDelete() || entry.getValue().isExclusive())) {
                    connection.recoverQueue(entry.getKey(), entry.getValue());
                    queues.add(entry.getValue().getName());
                }
            }
            for (final RecordedBinding binding : Utility.copy(connection.getRecordedBindings())) {
                if (binding instanceof RecordedQueueBinding && queues.contains(binding.getDestination())) {
                    binding.recover();
                }
            }
        }
        return null;
    };

    /**
     * Pre-configured {@link TopologyRecoveryRetryHandlerBuilder} that retries recovery of bindings and consumers
     * when their respective queue is not found.
     * 
     * This retry handler can be useful for long recovery processes, whereby auto-delete queues
     * can be deleted between queue recovery and binding/consumer recovery.
     * 
     * Also useful to retry channel-closed 404 errors that may arise with auto-delete queues during a cluster cycle.
     */
    public static final TopologyRecoveryRetryHandlerBuilder RETRY_ON_QUEUE_NOT_FOUND_RETRY_HANDLER = builder()
        .queueRecoveryRetryCondition(CHANNEL_CLOSED_NOT_FOUND)
        .bindingRecoveryRetryCondition(CHANNEL_CLOSED_NOT_FOUND)
        .consumerRecoveryRetryCondition(CHANNEL_CLOSED_NOT_FOUND)
        .queueRecoveryRetryOperation(RECOVER_CHANNEL
            .andThen(RECOVER_QUEUE)
            .andThen(RECOVER_PREVIOUS_AUTO_DELETE_QUEUES))
        .bindingRecoveryRetryOperation(RECOVER_CHANNEL
            .andThen(RECOVER_BINDING_QUEUE)
            .andThen(RECOVER_BINDING)
            .andThen(RECOVER_PREVIOUS_QUEUE_BINDINGS)
            .andThen(RECOVER_PREVIOUS_AUTO_DELETE_QUEUES))
        .consumerRecoveryRetryOperation(RECOVER_CHANNEL
            .andThen(RECOVER_CONSUMER_QUEUE)
            .andThen(RECOVER_CONSUMER)
            .andThen(RECOVER_CONSUMER_QUEUE_BINDINGS)
            .andThen(RECOVER_PREVIOUS_CONSUMERS));
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy