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

com.rabbitmq.client.impl.AbstractMetricsCollector 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) 2007-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
//
// 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;

import com.rabbitmq.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Base class for {@link MetricsCollector}.
 * Implements tricky logic such as keeping track of acknowledged and
 * rejected messages. Sub-classes just need to implement
 * the logic to increment their metrics.
 * Note transactions are not supported (see {@link MetricsCollector}.
 *
 * @see MetricsCollector
 */
public abstract class AbstractMetricsCollector implements MetricsCollector {

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMetricsCollector.class);

    private final ConcurrentMap connectionState = new ConcurrentHashMap<>();

    private final Runnable markAcknowledgedMessageAction = () -> markAcknowledgedMessage();

    private final Runnable markRejectedMessageAction = () -> markRejectedMessage();

    @Override
    public void newConnection(final Connection connection) {
        try {
            if(connection.getId() == null) {
                connection.setId(UUID.randomUUID().toString());
            }
            incrementConnectionCount(connection);
            connectionState.put(connection.getId(), new ConnectionState(connection));
            connection.addShutdownListener(cause -> closeConnection(connection));
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in newConnection: " + e.getMessage());
        }
    }

    @Override
    public void closeConnection(Connection connection) {
        try {
            ConnectionState removed = connectionState.remove(connection.getId());
            if(removed != null) {
                decrementConnectionCount(connection);
            }
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in closeConnection: " + e.getMessage());
        }
    }

    @Override
    public void newChannel(final Channel channel) {
        if (channel != null) {
            try {
                incrementChannelCount(channel);
                channel.addShutdownListener(cause -> closeChannel(channel));
                connectionState(channel.getConnection()).channelState.put(channel.getChannelNumber(), new ChannelState(channel));
            } catch(Exception e) {
                LOGGER.info("Error while computing metrics in newChannel: " + e.getMessage());
            }
        }
    }

    @Override
    public void closeChannel(Channel channel) {
        try {
            ChannelState removed = connectionState(channel.getConnection()).channelState.remove(channel.getChannelNumber());
            if(removed != null) {
                decrementChannelCount(channel);
            }
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in closeChannel: " + e.getMessage());
        }
    }

    @Override
    public void basicPublish(Channel channel) {
        try {
            markPublishedMessage();
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicPublish: " + e.getMessage());
        }
    }

    @Override
    public void basicPublishFailure(Channel channel, Throwable cause) {
        try {
            markMessagePublishFailed();
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicPublishFailure: " + e.getMessage());
        }
    }

    @Override
    public void basicPublishAck(Channel channel, long deliveryTag, boolean multiple) {
        if (multiple) {
            // this is a naive approach, as it does not handle multiple nacks
            return;
        }
        try {
            markMessagePublishAcknowledged();
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicPublishAck: " + e.getMessage());
        }
    }

    @Override
    public void basicPublishNack(Channel channel, long deliveryTag, boolean multiple) {
        if (multiple) {
            // this is a naive approach, as it does not handle multiple nacks
            return;
        }
        try {
            markMessagePublishNotAcknowledged();
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicPublishNack: " + e.getMessage());
        }
    }

    @Override
    public void basicPublishUnrouted(Channel channel) {
        try {
            markPublishedMessageUnrouted();
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in markPublishedMessageUnrouted: " + e.getMessage());
        }
    }

    @Override
    public void basicConsume(Channel channel, String consumerTag, boolean autoAck) {
        try {
            if(!autoAck) {
                ChannelState channelState = channelState(channel);
                channelState.lock.lock();
                try {
                    channelState(channel).consumersWithManualAck.add(consumerTag);
                } finally {
                    channelState.lock.unlock();
                }
            }
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicConsume: " + e.getMessage());
        }
    }

    @Override
    public void basicCancel(Channel channel, String consumerTag) {
        try {
            ChannelState channelState = channelState(channel);
            channelState.lock.lock();
            try {
                channelState(channel).consumersWithManualAck.remove(consumerTag);
            } finally {
                channelState.lock.unlock();
            }
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicCancel: " + e.getMessage());
        }
    }

    @Override
    public void consumedMessage(Channel channel, long deliveryTag, boolean autoAck) {
        try {
            markConsumedMessage();
            if(!autoAck) {
                ChannelState channelState = channelState(channel);
                channelState.lock.lock();
                try {
                    channelState(channel).unackedMessageDeliveryTags.add(deliveryTag);
                } finally {
                    channelState.lock.unlock();
                }
            }
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in consumedMessage: " + e.getMessage());
        }
    }

    @Override
    public void consumedMessage(Channel channel, long deliveryTag, String consumerTag) {
        try {
            markConsumedMessage();
            ChannelState channelState = channelState(channel);
            channelState.lock.lock();
            try {
                if(channelState.consumersWithManualAck.contains(consumerTag)) {
                    channelState.unackedMessageDeliveryTags.add(deliveryTag);
                }
            } finally {
                channelState.lock.unlock();
            }
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in consumedMessage: " + e.getMessage());
        }
    }

    @Override
    public void basicAck(Channel channel, long deliveryTag, boolean multiple) {
        try {
            updateChannelStateAfterAckReject(channel, deliveryTag, multiple, markAcknowledgedMessageAction);
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicAck: " + e.getMessage());
        }
    }

    @Override
    public void basicNack(Channel channel, long deliveryTag) {
        try {
            updateChannelStateAfterAckReject(channel, deliveryTag, true, markRejectedMessageAction);
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicNack: " + e.getMessage());
        }
    }

    @Override
    public void basicReject(Channel channel, long deliveryTag) {
        try {
            updateChannelStateAfterAckReject(channel, deliveryTag, false, markRejectedMessageAction);
        } catch(Exception e) {
            LOGGER.info("Error while computing metrics in basicReject: " + e.getMessage());
        }
    }

    private void updateChannelStateAfterAckReject(Channel channel, long deliveryTag, boolean multiple, Runnable action) {
        ChannelState channelState = channelState(channel);
        channelState.lock.lock();
        try {
            if(multiple) {
                Iterator iterator = channelState.unackedMessageDeliveryTags.iterator();
                while(iterator.hasNext()) {
                    long messageDeliveryTag = iterator.next();
                    if(messageDeliveryTag <= deliveryTag) {
                        iterator.remove();
                        action.run();
                    }
                }
            } else {
                if (channelState.unackedMessageDeliveryTags.remove(deliveryTag)) {
                    action.run();
                }
            }
        } finally {
            channelState.lock.unlock();
        }
    }

    private ConnectionState connectionState(Connection connection) {
        return connectionState.get(connection.getId());
    }

    private ChannelState channelState(Channel channel) {
        return connectionState(channel.getConnection()).channelState.get(channel.getChannelNumber());
    }

    /**
     * Clean inner state for close connections and channels.
     * Inner state is automatically cleaned on connection
     * and channel closing.
     * Thus, this method is provided as a safety net, to be externally
     * called periodically if closing of resources wouldn't work
     * properly for some corner cases.
     */
    public void cleanStaleState() {
        try {
            Iterator> connectionStateIterator = connectionState.entrySet().iterator();
            while(connectionStateIterator.hasNext()) {
                Map.Entry connectionEntry = connectionStateIterator.next();
                Connection connection = connectionEntry.getValue().connection;
                if(connection.isOpen()) {
                    Iterator> channelStateIterator = connectionEntry.getValue().channelState.entrySet().iterator();
                    while(channelStateIterator.hasNext()) {
                        Map.Entry channelStateEntry = channelStateIterator.next();
                        Channel channel = channelStateEntry.getValue().channel;
                        if(!channel.isOpen()) {
                            channelStateIterator.remove();
                            decrementChannelCount(channel);
                            LOGGER.info("Ripped off state of channel {} of connection {}. This is abnormal, please report.",
                                channel.getChannelNumber(), connection.getId());
                        }
                    }
                } else {
                    connectionStateIterator.remove();
                    decrementConnectionCount(connection);
                    for(int i = 0; i < connectionEntry.getValue().channelState.size(); i++) {
                        decrementChannelCount(null);
                    }
                    LOGGER.info("Ripped off state of connection {}. This is abnormal, please report.",
                        connection.getId());
                }
            }
        } catch(Exception e) {
            LOGGER.info("Error during periodic clean of metricsCollector: "+e.getMessage());
        }
    }

    private static class ConnectionState {

        final ConcurrentMap channelState = new ConcurrentHashMap();
        final Connection connection;

        private ConnectionState(Connection connection) {
            this.connection = connection;
        }
    }

    private static class ChannelState {

        final Lock lock = new ReentrantLock();

        final Set unackedMessageDeliveryTags = new HashSet();
        final Set consumersWithManualAck = new HashSet();

        final Channel channel;

        private ChannelState(Channel channel) {
            this.channel = channel;
        }

    }

    /**
     * Increments connection count.
     * The connection object is passed in as complementary information
     * and without any guarantee of not being null.
     * @param connection the connection that has been created (can be null)
     */
    protected abstract void incrementConnectionCount(Connection connection);

    /**
     * Decrements connection count.
     * The connection object is passed in as complementary information
     * and without any guarantee of not being null.
     * @param connection the connection that has been closed (can be null)
     */
    protected abstract void decrementConnectionCount(Connection connection);

    /**
     * Increments channel count.
     * The channel object is passed in as complementary information
     * and without any guarantee of not being null.
     * @param channel the channel that has been created (can be null)
     */
    protected abstract void incrementChannelCount(Channel channel);

    /**
     * Decrements channel count.
     * The channel object is passed in as complementary information
     * and without any guarantee of not being null.
     * @param channel
     */
    protected abstract void decrementChannelCount(Channel channel);

    /**
     * Marks the event of a published message.
     */
    protected abstract void markPublishedMessage();

    /**
     * Marks the event of a message publishing failure.
     */
    protected abstract void markMessagePublishFailed();

    /**
     * Marks the event of a consumed message.
     */
    protected abstract void markConsumedMessage();

    /**
     * Marks the event of an acknowledged message.
     */
    protected abstract void markAcknowledgedMessage();

    /**
     * Marks the event of a rejected message.
     */
    protected abstract void markRejectedMessage();

    /**
     * Marks the event of a message publishing acknowledgement.
     */
    protected abstract void markMessagePublishAcknowledged();

    /**
     * Marks the event of a message publishing not being acknowledged.
     */
    protected abstract void markMessagePublishNotAcknowledged();
    /**
     * Marks the event of a published message not being routed.
     */
    protected abstract void markPublishedMessageUnrouted();
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy