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

com.rabbitmq.jms.client.DeliveryExecutor Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2013-2023 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
package com.rabbitmq.jms.client;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import jakarta.jms.JMSException;
import jakarta.jms.MessageListener;

import com.rabbitmq.jms.util.RMQJMSException;

/**
 * Class to deliver messages to the onMessage() callback. Handles execution on a different thread, timeout
 * if execution takes too long (set on instantiation), and interrupts execution on closure or timeout. Also serialises
 * calls. There is one instance of this executor per session.
 */
class DeliveryExecutor {

    private final class CallOnMessage implements Callable {
        private final RMQMessage rmqMessage;
        private final MessageListener messageListener;

        private CallOnMessage(RMQMessage rmqMessage, MessageListener messageListener) {
            this.rmqMessage = rmqMessage;
            this.messageListener = messageListener;
        }

        public Boolean call() throws Exception {
            this.messageListener.onMessage(this.rmqMessage);
            return true;
        }
    }

    /** Timeout for onMessage executions */
    private final long onMessageTimeoutMs;

    private final boolean closeOnTimeout;

    /** Executor allocated if/when onMessage calls are made; used to isolate us from potential hangs. */
    private ExecutorService onMessageExecutorService = null;
    private final Object lockOnMessageExecutorService = new Object();

    DeliveryExecutor(long onMessageTimeoutMs, boolean closeOnTimeout) {
        this.onMessageTimeoutMs = onMessageTimeoutMs;
        this.closeOnTimeout = closeOnTimeout;
    }

    /**
     * Method to deliver message to the client, on a separate executor thread so we can abort this if it takes too long.
     * The executor service (with one thread) is allocated one per session, as and when needed, and we reserve the right
     * to interrupt it if it takes too long to process the onMessage call. Within a session there should be
     * only one onMessage call being executed at any one time.
     *
     * @param rmqMessage the message to deliver
     * @param messageListener JMS message listener that will handle the delivery
     * @throws JMSException if the delivery takes too long and is aborted
     * @throws InterruptedException if executing thread is interrupted
     */
    public void deliverMessageWithProtection(RMQMessage rmqMessage, MessageListener messageListener) throws JMSException, InterruptedException {
        Future task = null;
        try {
            task = this.getExecutorService().submit(new CallOnMessage(rmqMessage, messageListener));
            task.get(this.onMessageTimeoutMs, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            if (this.closeOnTimeout) {
                this.closeAbruptly();
                throw new RMQJMSException("onMessage took too long and was interrupted", null);
            } else {
                task.cancel(true);
                throw new DeliveryProcessingTimeoutException();
            }
        } catch (ExecutionException e) {
            throw new RMQMessageListenerExecutionJMSException("onMessage threw exception", e.getCause());
        }
    }

    public void close() {
        closeExecutorService(this.takeExecutorService());
    }

    private void closeAbruptly() {
        this.takeExecutorService().shutdownNow();
    }

    private void closeExecutorService(ExecutorService executorService) {
        if (executorService != null) {
            executorService.shutdown();
            if (!this.waitForTerminatedExecutorService(executorService)) {
                executorService.shutdownNow();
            }
        }
    }

    private ExecutorService takeExecutorService() {
        synchronized (this.lockOnMessageExecutorService) {
            ExecutorService es = this.onMessageExecutorService;
            this.onMessageExecutorService = null;
            return es;
        }
    }

    private ExecutorService getExecutorService() {
        synchronized (this.lockOnMessageExecutorService) {
            if (this.onMessageExecutorService == null) {
                this.onMessageExecutorService = Executors.newSingleThreadExecutor();
            }
            return this.onMessageExecutorService;
        }
    }

    private boolean waitForTerminatedExecutorService(ExecutorService executorService) {
        try {
            return executorService.awaitTermination(this.onMessageTimeoutMs, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            return false;
        }
    }

    static class DeliveryProcessingTimeoutException extends RuntimeException {

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy