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

net.leanix.dropkit.amqp.ConsumerRegistry Maven / Gradle / Ivy

package net.leanix.dropkit.amqp;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ShutdownSignalException;

@Singleton
public class ConsumerRegistry {

    private final static Logger log = LoggerFactory.getLogger(ConsumerRegistry.class);

    static class RemoveOldConsumerRunnable implements Runnable {

        private final ConsumerRegistry consumerRegistry;

        RemoveOldConsumerRunnable(ConsumerRegistry consumerRegistry) {
            this.consumerRegistry = consumerRegistry;
        }

        @Override
        public void run() {
            while (true) {
                consumerRegistry.removeOldConsumers(10L * 60 * 1000);
                try {
                    Thread.sleep(60 * 1000);
                } catch (InterruptedException e) {
                    log.info("Stopping RemoveOldConsumerRunnable loop used to clean up unused consumers.");
                    break;
                }
            }
        }
    };

    // interval that the queue will survive having no consumers
    public static final long QUEUE_X_EXPIRY_MILLIS = 2 * 60 * 1000;// 24L * 60 * 60 * 1000;

    private final ConnectionHolder connectionHolder;
    private final Map consumerMap = new HashMap<>();

    private final QueueConsumerFactory consumerFactory;

    @Inject
    public ConsumerRegistry(ConnectionHolder connectionHolder, QueueConsumerFactory queueingService) {
        this.connectionHolder = connectionHolder;
        this.consumerFactory = queueingService;

        // add simple thread that checks for consumers that are no more used
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.submit(new RemoveOldConsumerRunnable(this));
    }

    /**
     * Unregisters the consumer and close its channel.
     *
     * @param consumer
     *            the consumer to unregister.
     * @throws IllegalStateException
     *             if another consumer is stored in the registry under the passed in consumer's queue name
     */
    public void unregister(QueueConsumer consumer) {
        log.info("unregistering consumer for " + consumer.getQueueName());
        synchronized (consumerMap) {
            QueueConsumer registered = consumerMap.remove(consumer.getQueueName());
            if (registered != consumer) {
                throw new IllegalStateException("serious kuddelmuddel if this really happens");
            }

            internalPostRemove(consumer);
        }
    }

    // must be called after removing the consumer from consumerMap
    // and with a lock held on the consumerMap monitor (synchronized (this.consumerMap))
    private void internalPostRemove(QueueConsumer consumer) {
        consumer.setUnregistering();

        try {
            // we use a channel only for one consumer, so close the channel.
            if (consumer.getChannel().isOpen()) {
                log.info("closing channel for consumer '{}'", consumer.getQueueName());
                consumer.getChannel().close();
            }
        } catch (IOException | ShutdownSignalException e) {
            log.error("could not close channel - ignoring", e);
        }
    }

    /**
     * Gets the registered consumer with the given queue name; creates a new one if none is registered so far, together with a new channel,
     * starts consuming from the queue name, and puts it into this registry.
     * 

*

* The consumer will consume the queue in exclusive mode. * * @param queueName * name of the queue the consumer listens on * @return the registered consumer * @throws IOException */ public QueueConsumer consumerPresto(String queueName) throws IOException { QueueConsumer consumer; synchronized (consumerMap) { consumer = consumerMap.get(queueName); if (consumer == null) { log.info("for queue name '{}' creating a channel to AMQP server and a consumer using it", queueName); Channel channel = connectionHolder.createNewChannel(); consumer = consumerFactory.createConsumer(queueName, channel, this); consumerMap.put(queueName, consumer); // declare (create if missing) the queue as durable, // which is not removed when the connection closes (exclusive flag) // nor when there is temporarily no consumer (autodelete flag) // but after a expiry timeout without consumers (x-expires) Map args = Collections. singletonMap("x-expires", QUEUE_X_EXPIRY_MILLIS); channel.queueDeclare(queueName, true, false, false, args); // make consumer exclusive to detect some programming fault when an old consumer is not correctly // removed/shut down/cancelled/waddayacallit // use empty string as consumerTag to make the server generate one try { String consumerTag = consumer.getChannel().basicConsume(queueName, false, "", false, true, null, consumer); consumer.setRegisteredConsumerTag(consumerTag); } catch (Exception e) { log.warn("Internal error subscribing new consumer to queue '{}'", queueName); throw e; } } } return consumer; } public void removeOldConsumers(long inactivityMillis) { long now = System.currentTimeMillis(); synchronized (consumerMap) { log.debug("consumer remover: removing old consumers from {} total consumers", consumerMap.size()); int count = 0; Iterator iter = consumerMap.values().iterator(); while (iter.hasNext()) { QueueConsumer consumer = iter.next(); if (now - consumer.getLastUsed() > inactivityMillis) { log.info("removing old consumer with queue name " + consumer.getQueueName()); iter.remove(); internalPostRemove(consumer); ++count; } } log.debug("{} old consumers removed", count); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy