Package org.apache.activemq.broker.region

Source Code of org.apache.activemq.broker.region.PrefetchSubscription

/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
*      http://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.apache.activemq.broker.region;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.jms.InvalidSelectorException;
import javax.jms.JMSException;

import org.apache.activemq.ActiveMQMessageAudit;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.region.cursors.PendingMessageCursor;
import org.apache.activemq.broker.region.cursors.VMPendingMessageCursor;
import org.apache.activemq.command.ConsumerControl;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.Message;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.MessageDispatch;
import org.apache.activemq.command.MessageDispatchNotification;
import org.apache.activemq.command.MessageId;
import org.apache.activemq.command.MessagePull;
import org.apache.activemq.command.Response;
import org.apache.activemq.thread.Scheduler;
import org.apache.activemq.transaction.Synchronization;
import org.apache.activemq.usage.SystemUsage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* A subscription that honors the pre-fetch option of the ConsumerInfo.
*
* @version $Revision: 1.15 $
*/
public abstract class PrefetchSubscription extends AbstractSubscription {

    private static final Log LOG = LogFactory.getLog(PrefetchSubscription.class);
    protected PendingMessageCursor pending;
    protected final List<MessageReference> dispatched = new CopyOnWriteArrayList<MessageReference>();
    protected int prefetchExtension;
    protected long enqueueCounter;
    protected long dispatchCounter;
    protected long dequeueCounter;
    protected boolean optimizedDispatch=true;
    private int maxProducersToAudit=32;
    private int maxAuditDepth=2048;
    protected final SystemUsage usageManager;
    protected ActiveMQMessageAudit audit = new ActiveMQMessageAudit();

    public PrefetchSubscription(Broker broker, SystemUsage usageManager, ConnectionContext context, ConsumerInfo info, PendingMessageCursor cursor) throws InvalidSelectorException {
        super(broker, context, info);
        this.usageManager=usageManager;
        pending = cursor;
    }

    public PrefetchSubscription(Broker broker, SystemUsage usageManager, ConnectionContext context, ConsumerInfo info) throws InvalidSelectorException {
        this(broker,usageManager,context, info, new VMPendingMessageCursor());
    }

    /**
     * Allows a message to be pulled on demand by a client
     */
    public synchronized Response pullMessage(ConnectionContext context, MessagePull pull) throws Exception {
        // The slave should not deliver pull messages. TODO: when the slave
        // becomes a master,
        // He should send a NULL message to all the consumers to 'wake them up'
        // in case
        // they were waiting for a message.
        if (getPrefetchSize() == 0 && !isSlave()) {
            prefetchExtension++;
            final long dispatchCounterBeforePull = dispatchCounter;
            dispatchMatched();
            // If there was nothing dispatched.. we may need to setup a timeout.
            if (dispatchCounterBeforePull == dispatchCounter) {
                // imediate timeout used by receiveNoWait()
                if (pull.getTimeout() == -1) {
                    // Send a NULL message.
                    add(QueueMessageReference.NULL_MESSAGE);
                    dispatchMatched();
                }
                if (pull.getTimeout() > 0) {
                    Scheduler.executeAfterDelay(new Runnable() {

                        public void run() {
                            pullTimeout(dispatchCounterBeforePull);
                        }
                    }, pull.getTimeout());
                }
            }
        }
        return null;
    }

    /**
     * Occurs when a pull times out. If nothing has been dispatched since the
     * timeout was setup, then send the NULL message.
     */
    final synchronized void pullTimeout(long dispatchCounterBeforePull) {
        if (dispatchCounterBeforePull == dispatchCounter) {
            try {
                add(QueueMessageReference.NULL_MESSAGE);
                dispatchMatched();
            } catch (Exception e) {
                context.getConnection().serviceException(e);
            }
        }
    }

    public synchronized void add(MessageReference node) throws Exception {
        boolean pendingEmpty = false;
        pendingEmpty = pending.isEmpty();
        enqueueCounter++;
        if (optimizedDispatch && !isFull() && pendingEmpty && !isSlave()) {
            dispatch(node);
        } else {
            optimizePrefetch();
            synchronized (pending) {
                if (pending.isEmpty() && LOG.isDebugEnabled()) {
                    LOG.debug("Prefetch limit.");
                }
                pending.addMessageLast(node);
                dispatchMatched();
            }
        }
    }

    public synchronized void processMessageDispatchNotification(MessageDispatchNotification mdn) throws Exception {
        try {
            pending.reset();
            while (pending.hasNext()) {
                MessageReference node = pending.next();
                if (node.getMessageId().equals(mdn.getMessageId())) {
                    pending.remove();
                    createMessageDispatch(node, node.getMessage());
                    dispatched.add(node);
                    return;
                }
            }
        } finally {
            pending.release();
        }
        throw new JMSException("Slave broker out of sync with master: Dispatched message (" + mdn.getMessageId() + ") was not in the pending list");
    }

    public synchronized void acknowledge(final ConnectionContext context,
            final MessageAck ack) throws Exception {
        // Handle the standard acknowledgment case.
        boolean callDispatchMatched = false;
        if (ack.isStandardAck()) {
            // Acknowledge all dispatched messages up till the message id of the
            // acknowledgment.
            int index = 0;
            boolean inAckRange = false;
            List<MessageReference> removeList = new ArrayList<MessageReference>();
            for (final MessageReference node : dispatched) {
                MessageId messageId = node.getMessageId();
                if (ack.getFirstMessageId() == null
                        || ack.getFirstMessageId().equals(messageId)) {
                    inAckRange = true;
                }
                if (inAckRange) {
                    // Don't remove the nodes until we are committed.
                    if (!context.isInTransaction()) {
                        dequeueCounter++;
                        node.getRegionDestination().getDestinationStatistics()
                                .getDequeues().increment();
                        removeList.add(node);
                    } else {
                        // setup a Synchronization to remove nodes from the
                        // dispatched list.
                        context.getTransaction().addSynchronization(
                                new Synchronization() {

                                    public void afterCommit() throws Exception {
                                        synchronized (PrefetchSubscription.this) {
                                            dequeueCounter++;
                                            dispatched.remove(node);
                                            node.getRegionDestination()
                                                    .getDestinationStatistics()
                                                    .getDequeues().increment();
                                            prefetchExtension--;
                                        }
                                    }

                                    public void afterRollback()
                                            throws Exception {
                                        super.afterRollback();
                                    }
                                });
                    }
                    index++;
                    acknowledge(context, ack, node);
                    if (ack.getLastMessageId().equals(messageId)) {
                        if (context.isInTransaction()) {
                            // extend prefetch window only if not a pulling
                            // consumer
                            if (getPrefetchSize() != 0) {
                                prefetchExtension = Math.max(prefetchExtension,
                                        index + 1);
                            }
                        } else {
                            prefetchExtension = Math.max(0, prefetchExtension
                                    - (index + 1));
                        }
                        callDispatchMatched = true;
                        break;
                    }
                }
            }
            for (final MessageReference node : removeList) {
                dispatched.remove(node);
            }
            // this only happens after a reconnect - get an ack which is not
            // valid
            if (!callDispatchMatched) {
                if (LOG.isDebugEnabled()) {
                    LOG
                            .debug("Could not correlate acknowledgment with dispatched message: "
                                    + ack);
                }
            }
        } else if (ack.isDeliveredAck()) {
            // Message was delivered but not acknowledged: update pre-fetch
            // counters.
            // Acknowledge all dispatched messages up till the message id of the
            // acknowledgment.
            int index = 0;
            for (Iterator<MessageReference> iter = dispatched.iterator(); iter
                    .hasNext(); index++) {
                final MessageReference node = iter.next();
                if (ack.getLastMessageId().equals(node.getMessageId())) {
                    prefetchExtension = Math.max(prefetchExtension, index + 1);
                    callDispatchMatched = true;
                    break;
                }
            }
            if (!callDispatchMatched) {
                throw new JMSException(
                        "Could not correlate acknowledgment with dispatched message: "
                                + ack);
            }
        } else if (ack.isRedeliveredAck()) {
            // Message was re-delivered but it was not yet considered to be a
            // DLQ message.
            // Acknowledge all dispatched messages up till the message id of the
            // acknowledgment.
            boolean inAckRange = false;
            for (final MessageReference node : dispatched) {
                MessageId messageId = node.getMessageId();
                if (ack.getFirstMessageId() == null
                        || ack.getFirstMessageId().equals(messageId)) {
                    inAckRange = true;
                }
                if (inAckRange) {
                    node.incrementRedeliveryCounter();
                    if (ack.getLastMessageId().equals(messageId)) {
                        callDispatchMatched = true;
                        break;
                    }
                }
            }
            if (!callDispatchMatched) {
                throw new JMSException(
                        "Could not correlate acknowledgment with dispatched message: "
                                + ack);
            }
        } else if (ack.isPoisonAck()) {
            // TODO: what if the message is already in a DLQ???
            // Handle the poison ACK case: we need to send the message to a DLQ
            if (ack.isInTransaction()) {
                throw new JMSException("Poison ack cannot be transacted: "
                        + ack);
            }
            // Acknowledge all dispatched messages up till the message id of the
            // acknowledgment.
            int index = 0;
            boolean inAckRange = false;
            List<MessageReference> removeList = new ArrayList<MessageReference>();
            for (final MessageReference node : dispatched) {
                MessageId messageId = node.getMessageId();
                if (ack.getFirstMessageId() == null
                        || ack.getFirstMessageId().equals(messageId)) {
                    inAckRange = true;
                }
                if (inAckRange) {
                    sendToDLQ(context, node);
                    node.getRegionDestination().getDestinationStatistics()
                            .getDequeues().increment();
                    removeList.add(node);
                    dequeueCounter++;
                    index++;
                    acknowledge(context, ack, node);
                    if (ack.getLastMessageId().equals(messageId)) {
                        prefetchExtension = Math.max(0, prefetchExtension
                                - (index + 1));
                        callDispatchMatched = true;
                        break;
                    }
                }
            }
            for (final MessageReference node : removeList) {
                dispatched.remove(node);
            }
            if (!callDispatchMatched) {
                throw new JMSException(
                        "Could not correlate acknowledgment with dispatched message: "
                                + ack);
            }
        }
        if (callDispatchMatched) {
            dispatchMatched();
        } else {
            if (isSlave()) {
                throw new JMSException(
                        "Slave broker out of sync with master: Acknowledgment ("
                                + ack + ") was not in the dispatch list: "
                                + dispatched);
            } else {
                LOG
                        .debug("Acknowledgment out of sync (Normally occurs when failover connection reconnects): "
                                + ack);
            }
        }
    }

    /**
     * @param context
     * @param node
     * @throws IOException
     * @throws Exception
     */
    protected void sendToDLQ(final ConnectionContext context, final MessageReference node) throws IOException, Exception {
        broker.sendToDeadLetterQueue(context, node);
    }

    /**
     * Used to determine if the broker can dispatch to the consumer.
     *
     * @return
     */
    protected synchronized boolean isFull() {
        return isSlave() || dispatched.size() - prefetchExtension >= info.getPrefetchSize();
    }

    /**
     * @return true when 60% or more room is left for dispatching messages
     */
    public synchronized boolean isLowWaterMark() {
        return (dispatched.size() - prefetchExtension) <= (info.getPrefetchSize() * .4);
    }

    /**
     * @return true when 10% or less room is left for dispatching messages
     */
    public synchronized boolean isHighWaterMark() {
        return (dispatched.size() - prefetchExtension) >= (info.getPrefetchSize() * .9);
    }

    public synchronized int countBeforeFull() {
        return info.getPrefetchSize() + prefetchExtension - dispatched.size();
    }

    public synchronized int getPendingQueueSize() {
        return pending.size();
    }

    public synchronized int getDispatchedQueueSize() {
        return dispatched.size();
    }

    public synchronized long getDequeueCounter() {
        return dequeueCounter;
    }

    public synchronized long getDispatchedCounter() {
        return dispatchCounter;
    }

    public synchronized long getEnqueueCounter() {
        return enqueueCounter;
    }

    public boolean isRecoveryRequired() {
        return pending.isRecoveryRequired();
    }

    public synchronized PendingMessageCursor getPending() {
        return this.pending;
    }

    public synchronized void setPending(PendingMessageCursor pending) {
        this.pending = pending;
        if (this.pending!=null) {
            this.pending.setSystemUsage(usageManager);
        }
    }

    /**
     * optimize message consumer prefetch if the consumer supports it
     */
    public void optimizePrefetch() {
        /*
         * if(info!=null&&info.isOptimizedAcknowledge()&&context!=null&&context.getConnection()!=null
         * &&context.getConnection().isManageable()){
         * if(info.getCurrentPrefetchSize()!=info.getPrefetchSize() &&
         * isLowWaterMark()){
         * info.setCurrentPrefetchSize(info.getPrefetchSize());
         * updateConsumerPrefetch(info.getPrefetchSize()); }else
         * if(info.getCurrentPrefetchSize()==info.getPrefetchSize() &&
         * isHighWaterMark()){ // want to purge any outstanding acks held by the
         * consumer info.setCurrentPrefetchSize(1); updateConsumerPrefetch(1); } }
         */
    }

    public synchronized void add(ConnectionContext context, Destination destination) throws Exception {
        super.add(context, destination);
        pending.add(context, destination);
    }

    public synchronized void remove(ConnectionContext context, Destination destination) throws Exception {
        super.remove(context, destination);
        pending.remove(context, destination);
    }

    protected synchronized void dispatchMatched() throws IOException {
        if (!isSlave()) {
            try {
                int numberToDispatch = countBeforeFull();
                if (numberToDispatch > 0) {
                    pending.setMaxBatchSize(numberToDispatch);
                    int count = 0;
                    pending.reset();
                    while (pending.hasNext() && !isFull() && count < numberToDispatch) {
                        MessageReference node = pending.next();
                        if (node == null) {
                            break;
                        }
                        if (canDispatch(node)) {
                            pending.remove();
                            // Message may have been sitting in the pending list
                            // a while
                            // waiting for the consumer to ak the message.
                            if (node != QueueMessageReference.NULL_MESSAGE && broker.isExpired(node)) {
                                broker.messageExpired(getContext(), node);
                                dequeueCounter++;
                                continue;
                            }
                            dispatch(node);
                            count++;
                        }
                    }
                }
            } finally {
                pending.release();
            }
        }
    }

    protected synchronized boolean dispatch(final MessageReference node) throws IOException {
        final Message message = node.getMessage();
        if (message == null) {
            return false;
        }
               
        // Make sure we can dispatch a message.
        if (canDispatch(node) && !isSlave()) {
            MessageDispatch md = createMessageDispatch(node, message);
            // NULL messages don't count... they don't get Acked.
            if (node != QueueMessageReference.NULL_MESSAGE) {
                dispatchCounter++;
                dispatched.add(node);
                if(pending != null) {
                    pending.dispatched(message);
                }
            } else {
                prefetchExtension = Math.max(0, prefetchExtension - 1);
            }
            if (info.isDispatchAsync()) {
                md.setTransmitCallback(new Runnable() {

                    public void run() {
                        // Since the message gets queued up in async dispatch,
                        // we don't want to
                        // decrease the reference count until it gets put on the
                        // wire.
                        onDispatch(node, message);
                    }
                });
                context.getConnection().dispatchAsync(md);
            } else {
                context.getConnection().dispatchSync(md);
                onDispatch(node, message);
            }
            return true;
        } else {
            return false;
        }
    }

    protected void onDispatch(final MessageReference node, final Message message) {
        if (node.getRegionDestination() != null) {
            if (node != QueueMessageReference.NULL_MESSAGE) {
                node.getRegionDestination().getDestinationStatistics().getDispatched().increment();
            }
        }
        if (info.isDispatchAsync()) {
            try {
                dispatchMatched();
            } catch (IOException e) {
                context.getConnection().serviceExceptionAsync(e);
            }
        }
    }

    /**
     * inform the MessageConsumer on the client to change it's prefetch
     *
     * @param newPrefetch
     */
    public void updateConsumerPrefetch(int newPrefetch) {
        if (context != null && context.getConnection() != null && context.getConnection().isManageable()) {
            ConsumerControl cc = new ConsumerControl();
            cc.setConsumerId(info.getConsumerId());
            cc.setPrefetch(newPrefetch);
            context.getConnection().dispatchAsync(cc);
        }
    }

    /**
     * @param node
     * @param message
     * @return MessageDispatch
     */
    protected MessageDispatch createMessageDispatch(MessageReference node, Message message) {
        if (node == QueueMessageReference.NULL_MESSAGE) {
            MessageDispatch md = new MessageDispatch();
            md.setMessage(null);
            md.setConsumerId(info.getConsumerId());
            md.setDestination(null);
            return md;
        } else {
            MessageDispatch md = new MessageDispatch();
            md.setConsumerId(info.getConsumerId());
            md.setDestination(node.getRegionDestination().getActiveMQDestination());
            md.setMessage(message);
            md.setRedeliveryCounter(node.getRedeliveryCounter());
            return md;
        }
    }

    /**
     * Use when a matched message is about to be dispatched to the client.
     *
     * @param node
     * @return false if the message should not be dispatched to the client
     *         (another sub may have already dispatched it for example).
     * @throws IOException
     */
    protected abstract boolean canDispatch(MessageReference node) throws IOException;

    /**
     * Used during acknowledgment to remove the message.
     *
     * @throws IOException
     */
    protected void acknowledge(ConnectionContext context, final MessageAck ack, final MessageReference node) throws IOException {
    }

    public boolean isOptimizedDispatch() {
        return optimizedDispatch;
    }

    public void setOptimizedDispatch(boolean optimizedDispatch) {
        this.optimizedDispatch = optimizedDispatch;
    }

    public int getMaxProducersToAudit() {
        return maxProducersToAudit;
    }

    public void setMaxProducersToAudit(int maxProducersToAudit) {
        this.maxProducersToAudit = maxProducersToAudit;
    }

    public int getMaxAuditDepth() {
        return maxAuditDepth;
    }

    public void setMaxAuditDepth(int maxAuditDepth) {
        this.maxAuditDepth = maxAuditDepth;
    }

}
TOP

Related Classes of org.apache.activemq.broker.region.PrefetchSubscription

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.