org.apache.activemq.ActiveMQMessageConsumer Maven / Gradle / Ivy
Show all versions of activemq-client Show documentation
/**
* 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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.jms.IllegalStateException;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.TransactionRolledBackException;
import org.apache.activemq.blob.BlobDownloader;
import org.apache.activemq.command.ActiveMQBlobMessage;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQObjectMessage;
import org.apache.activemq.command.ActiveMQTempDestination;
import org.apache.activemq.command.CommandTypes;
import org.apache.activemq.command.ConsumerId;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.MessageDispatch;
import org.apache.activemq.command.MessageId;
import org.apache.activemq.command.MessagePull;
import org.apache.activemq.command.RemoveInfo;
import org.apache.activemq.command.TransactionId;
import org.apache.activemq.management.JMSConsumerStatsImpl;
import org.apache.activemq.management.StatsCapable;
import org.apache.activemq.management.StatsImpl;
import org.apache.activemq.selector.SelectorParser;
import org.apache.activemq.transaction.Synchronization;
import org.apache.activemq.util.Callback;
import org.apache.activemq.util.IntrospectionSupport;
import org.apache.activemq.util.JMSExceptionSupport;
import org.apache.activemq.util.ThreadPoolUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A client uses a MessageConsumer
object to receive messages
* from a destination. A MessageConsumer
object is created by
* passing a Destination
object to a message-consumer creation
* method supplied by a session.
*
* MessageConsumer
is the parent interface for all message
* consumers.
*
* A message consumer can be created with a message selector. A message selector
* allows the client to restrict the messages delivered to the message consumer
* to those that match the selector.
*
* A client may either synchronously receive a message consumer's messages or
* have the consumer asynchronously deliver them as they arrive.
*
* For synchronous receipt, a client can request the next message from a message
* consumer using one of its receive
methods. There are several
* variations of receive
that allow a client to poll or wait for
* the next message.
*
* For asynchronous delivery, a client can register a
* MessageListener
object with a message consumer. As messages
* arrive at the message consumer, it delivers them by calling the
* MessageListener
's
* onMessage
method.
*
* It is a client programming error for a MessageListener
to
* throw an exception.
*
*
* @see javax.jms.MessageConsumer
* @see javax.jms.QueueReceiver
* @see javax.jms.TopicSubscriber
* @see javax.jms.Session
*/
public class ActiveMQMessageConsumer implements MessageAvailableConsumer, StatsCapable, ActiveMQDispatcher {
@SuppressWarnings("serial")
class PreviouslyDeliveredMap extends HashMap {
final TransactionId transactionId;
public PreviouslyDeliveredMap(TransactionId transactionId) {
this.transactionId = transactionId;
}
}
private static final Logger LOG = LoggerFactory.getLogger(ActiveMQMessageConsumer.class);
protected final ActiveMQSession session;
protected final ConsumerInfo info;
// These are the messages waiting to be delivered to the client
protected final MessageDispatchChannel unconsumedMessages;
// The are the messages that were delivered to the consumer but that have
// not been acknowledged. It's kept in reverse order since we
// Always walk list in reverse order.
protected final LinkedList deliveredMessages = new LinkedList();
// track duplicate deliveries in a transaction such that the tx integrity can be validated
private PreviouslyDeliveredMap previouslyDeliveredMessages;
private int deliveredCounter;
private int additionalWindowSize;
private long redeliveryDelay;
private int ackCounter;
private int dispatchedCount;
private final AtomicReference messageListener = new AtomicReference();
private final JMSConsumerStatsImpl stats;
private final String selector;
private boolean synchronizationRegistered;
private final AtomicBoolean started = new AtomicBoolean(false);
private MessageAvailableListener availableListener;
private RedeliveryPolicy redeliveryPolicy;
private boolean optimizeAcknowledge;
private final AtomicBoolean deliveryingAcknowledgements = new AtomicBoolean();
private ExecutorService executorService;
private MessageTransformer transformer;
private boolean clearDeliveredList;
AtomicInteger inProgressClearRequiredFlag = new AtomicInteger(0);
private MessageAck pendingAck;
private long lastDeliveredSequenceId = -1;
private IOException failureError;
private long optimizeAckTimestamp = System.currentTimeMillis();
private long optimizeAcknowledgeTimeOut = 0;
private long optimizedAckScheduledAckInterval = 0;
private Runnable optimizedAckTask;
private long failoverRedeliveryWaitPeriod = 0;
private boolean transactedIndividualAck = false;
private boolean nonBlockingRedelivery = false;
private boolean consumerExpiryCheckEnabled = true;
/**
* Create a MessageConsumer
*
* @param session
* @param dest
* @param name
* @param selector
* @param prefetch
* @param maximumPendingMessageCount
* @param noLocal
* @param browser
* @param dispatchAsync
* @param messageListener
* @throws JMSException
*/
public ActiveMQMessageConsumer(ActiveMQSession session, ConsumerId consumerId, ActiveMQDestination dest,
String name, String selector, int prefetch,
int maximumPendingMessageCount, boolean noLocal, boolean browser,
boolean dispatchAsync, MessageListener messageListener) throws JMSException {
if (dest == null) {
throw new InvalidDestinationException("Don't understand null destinations");
} else if (dest.getPhysicalName() == null) {
throw new InvalidDestinationException("The destination object was not given a physical name.");
} else if (dest.isTemporary()) {
String physicalName = dest.getPhysicalName();
if (physicalName == null) {
throw new IllegalArgumentException("Physical name of Destination should be valid: " + dest);
}
String connectionID = session.connection.getConnectionInfo().getConnectionId().getValue();
if (physicalName.indexOf(connectionID) < 0) {
throw new InvalidDestinationException("Cannot use a Temporary destination from another Connection");
}
if (session.connection.isDeleted(dest)) {
throw new InvalidDestinationException("Cannot use a Temporary destination that has been deleted");
}
if (prefetch < 0) {
throw new JMSException("Cannot have a prefetch size less than zero");
}
}
if (session.connection.isMessagePrioritySupported()) {
this.unconsumedMessages = new SimplePriorityMessageDispatchChannel();
}else {
this.unconsumedMessages = new FifoMessageDispatchChannel();
}
this.session = session;
this.redeliveryPolicy = session.connection.getRedeliveryPolicyMap().getEntryFor(dest);
if (this.redeliveryPolicy == null) {
this.redeliveryPolicy = new RedeliveryPolicy();
}
setTransformer(session.getTransformer());
this.info = new ConsumerInfo(consumerId);
this.info.setExclusive(this.session.connection.isExclusiveConsumer());
this.info.setClientId(this.session.connection.getClientID());
this.info.setSubscriptionName(name);
this.info.setPrefetchSize(prefetch);
this.info.setCurrentPrefetchSize(prefetch);
this.info.setMaximumPendingMessageLimit(maximumPendingMessageCount);
this.info.setNoLocal(noLocal);
this.info.setDispatchAsync(dispatchAsync);
this.info.setRetroactive(this.session.connection.isUseRetroactiveConsumer());
this.info.setSelector(null);
// Allows the options on the destination to configure the consumerInfo
if (dest.getOptions() != null) {
Map options = IntrospectionSupport.extractProperties(
new HashMap(dest.getOptions()), "consumer.");
IntrospectionSupport.setProperties(this.info, options);
if (options.size() > 0) {
String msg = "There are " + options.size()
+ " consumer options that couldn't be set on the consumer."
+ " Check the options are spelled correctly."
+ " Unknown parameters=[" + options + "]."
+ " This consumer cannot be started.";
LOG.warn(msg);
throw new ConfigurationException(msg);
}
}
this.info.setDestination(dest);
this.info.setBrowser(browser);
if (selector != null && selector.trim().length() != 0) {
// Validate the selector
SelectorParser.parse(selector);
this.info.setSelector(selector);
this.selector = selector;
} else if (info.getSelector() != null) {
// Validate the selector
SelectorParser.parse(this.info.getSelector());
this.selector = this.info.getSelector();
} else {
this.selector = null;
}
this.stats = new JMSConsumerStatsImpl(session.getSessionStats(), dest);
this.optimizeAcknowledge = session.connection.isOptimizeAcknowledge() && session.isAutoAcknowledge()
&& !info.isBrowser();
if (this.optimizeAcknowledge) {
this.optimizeAcknowledgeTimeOut = session.connection.getOptimizeAcknowledgeTimeOut();
setOptimizedAckScheduledAckInterval(session.connection.getOptimizedAckScheduledAckInterval());
}
this.info.setOptimizedAcknowledge(this.optimizeAcknowledge);
this.failoverRedeliveryWaitPeriod = session.connection.getConsumerFailoverRedeliveryWaitPeriod();
this.nonBlockingRedelivery = session.connection.isNonBlockingRedelivery();
this.transactedIndividualAck = session.connection.isTransactedIndividualAck()
|| this.nonBlockingRedelivery
|| session.connection.isMessagePrioritySupported();
this.consumerExpiryCheckEnabled = session.connection.isConsumerExpiryCheckEnabled();
if (messageListener != null) {
setMessageListener(messageListener);
}
try {
this.session.addConsumer(this);
this.session.syncSendPacket(info);
} catch (JMSException e) {
this.session.removeConsumer(this);
throw e;
}
if (session.connection.isStarted()) {
start();
}
}
private boolean isAutoAcknowledgeEach() {
return session.isAutoAcknowledge() || ( session.isDupsOkAcknowledge() && getDestination().isQueue() );
}
private boolean isAutoAcknowledgeBatch() {
return session.isDupsOkAcknowledge() && !getDestination().isQueue() ;
}
@Override
public StatsImpl getStats() {
return stats;
}
public JMSConsumerStatsImpl getConsumerStats() {
return stats;
}
public RedeliveryPolicy getRedeliveryPolicy() {
return redeliveryPolicy;
}
/**
* Sets the redelivery policy used when messages are redelivered
*/
public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
this.redeliveryPolicy = redeliveryPolicy;
}
public MessageTransformer getTransformer() {
return transformer;
}
/**
* Sets the transformer used to transform messages before they are sent on
* to the JMS bus
*/
public void setTransformer(MessageTransformer transformer) {
this.transformer = transformer;
}
/**
* @return Returns the value.
*/
public ConsumerId getConsumerId() {
return info.getConsumerId();
}
/**
* @return the consumer name - used for durable consumers
*/
public String getConsumerName() {
return this.info.getSubscriptionName();
}
/**
* @return true if this consumer does not accept locally produced messages
*/
protected boolean isNoLocal() {
return info.isNoLocal();
}
/**
* Retrieve is a browser
*
* @return true if a browser
*/
protected boolean isBrowser() {
return info.isBrowser();
}
/**
* @return ActiveMQDestination
*/
protected ActiveMQDestination getDestination() {
return info.getDestination();
}
/**
* @return Returns the prefetchNumber.
*/
public int getPrefetchNumber() {
return info.getPrefetchSize();
}
/**
* @return true if this is a durable topic subscriber
*/
public boolean isDurableSubscriber() {
return info.getSubscriptionName() != null && info.getDestination().isTopic();
}
/**
* Gets this message consumer's message selector expression.
*
* @return this message consumer's message selector, or null if no message
* selector exists for the message consumer (that is, if the message
* selector was not set or was set to null or the empty string)
* @throws JMSException if the JMS provider fails to receive the next
* message due to some internal error.
*/
@Override
public String getMessageSelector() throws JMSException {
checkClosed();
return selector;
}
/**
* Gets the message consumer's MessageListener
.
*
* @return the listener for the message consumer, or null if no listener is
* set
* @throws JMSException if the JMS provider fails to get the message
* listener due to some internal error.
* @see javax.jms.MessageConsumer#setMessageListener(javax.jms.MessageListener)
*/
@Override
public MessageListener getMessageListener() throws JMSException {
checkClosed();
return this.messageListener.get();
}
/**
* Sets the message consumer's MessageListener
.
*
* Setting the message listener to null is the equivalent of unsetting the
* message listener for the message consumer.
*
* The effect of calling MessageConsumer.setMessageListener
* while messages are being consumed by an existing listener or the consumer
* is being used to consume messages synchronously is undefined.
*
* @param listener the listener to which the messages are to be delivered
* @throws JMSException if the JMS provider fails to receive the next
* message due to some internal error.
* @see javax.jms.MessageConsumer#getMessageListener
*/
@Override
public void setMessageListener(MessageListener listener) throws JMSException {
checkClosed();
if (info.getPrefetchSize() == 0) {
throw new JMSException("Illegal prefetch size of zero. This setting is not supported for asynchronous consumers please set a value of at least 1");
}
if (listener != null) {
boolean wasRunning = session.isRunning();
if (wasRunning) {
session.stop();
}
this.messageListener.set(listener);
session.redispatch(this, unconsumedMessages);
if (wasRunning) {
session.start();
}
} else {
this.messageListener.set(null);
}
}
@Override
public MessageAvailableListener getAvailableListener() {
return availableListener;
}
/**
* Sets the listener used to notify synchronous consumers that there is a
* message available so that the {@link MessageConsumer#receiveNoWait()} can
* be called.
*/
@Override
public void setAvailableListener(MessageAvailableListener availableListener) {
this.availableListener = availableListener;
}
/**
* Used to get an enqueued message from the unconsumedMessages list. The
* amount of time this method blocks is based on the timeout value. - if
* timeout==-1 then it blocks until a message is received. - if timeout==0
* then it it tries to not block at all, it returns a message if it is
* available - if timeout>0 then it blocks up to timeout amount of time.
* Expired messages will consumed by this method.
*
* @throws JMSException
* @return null if we timeout or if the consumer is closed.
*/
private MessageDispatch dequeue(long timeout) throws JMSException {
try {
long deadline = 0;
if (timeout > 0) {
deadline = System.currentTimeMillis() + timeout;
}
while (true) {
MessageDispatch md = unconsumedMessages.dequeue(timeout);
if (md == null) {
if (timeout > 0 && !unconsumedMessages.isClosed()) {
timeout = Math.max(deadline - System.currentTimeMillis(), 0);
} else {
if (failureError != null) {
throw JMSExceptionSupport.create(failureError);
} else {
return null;
}
}
} else if (md.getMessage() == null) {
return null;
} else if (consumeExpiredMessage(md)) {
LOG.debug("{} received expired message: {}", getConsumerId(), md);
beforeMessageIsConsumed(md);
afterMessageIsConsumed(md, true);
if (timeout > 0) {
timeout = Math.max(deadline - System.currentTimeMillis(), 0);
}
sendPullCommand(timeout);
} else if (redeliveryExceeded(md)) {
LOG.debug("{} received with excessive redelivered: {}", getConsumerId(), md);
posionAck(md, "dispatch to " + getConsumerId() + " exceeds redelivery policy limit:" + redeliveryPolicy);
if (timeout > 0) {
timeout = Math.max(deadline - System.currentTimeMillis(), 0);
}
sendPullCommand(timeout);
} else {
if (LOG.isTraceEnabled()) {
LOG.trace(getConsumerId() + " received message: " + md);
}
return md;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw JMSExceptionSupport.create(e);
}
}
private boolean consumeExpiredMessage(MessageDispatch dispatch) {
return isConsumerExpiryCheckEnabled() && dispatch.getMessage().isExpired();
}
private void posionAck(MessageDispatch md, String cause) throws JMSException {
MessageAck posionAck = new MessageAck(md, MessageAck.POSION_ACK_TYPE, 1);
posionAck.setFirstMessageId(md.getMessage().getMessageId());
posionAck.setPoisonCause(new Throwable(cause));
session.sendAck(posionAck);
}
private boolean redeliveryExceeded(MessageDispatch md) {
try {
return session.getTransacted()
&& redeliveryPolicy != null
&& redeliveryPolicy.getMaximumRedeliveries() != RedeliveryPolicy.NO_MAXIMUM_REDELIVERIES
&& md.getRedeliveryCounter() > redeliveryPolicy.getMaximumRedeliveries()
// redeliveryCounter > x expected after resend via brokerRedeliveryPlugin
&& md.getMessage().getProperty("redeliveryDelay") == null;
} catch (Exception ignored) {
return false;
}
}
/**
* Receives the next message produced for this message consumer.
*
* This call blocks indefinitely until a message is produced or until this
* message consumer is closed.
*
* If this receive
is done within a transaction, the consumer
* retains the message until the transaction commits.
*
* @return the next message produced for this message consumer, or null if
* this message consumer is concurrently closed
*/
@Override
public Message receive() throws JMSException {
checkClosed();
checkMessageListener();
sendPullCommand(0);
MessageDispatch md = dequeue(-1);
if (md == null) {
return null;
}
beforeMessageIsConsumed(md);
afterMessageIsConsumed(md, false);
return createActiveMQMessage(md);
}
/**
* @param md
* the MessageDispatch that arrived from the Broker.
*
* @return an ActiveMQMessage initialized from the Message in the dispatch.
*/
private ActiveMQMessage createActiveMQMessage(final MessageDispatch md) throws JMSException {
ActiveMQMessage m = (ActiveMQMessage)md.getMessage().copy();
if (m.getDataStructureType()==CommandTypes.ACTIVEMQ_BLOB_MESSAGE) {
((ActiveMQBlobMessage)m).setBlobDownloader(new BlobDownloader(session.getBlobTransferPolicy()));
}
if (m.getDataStructureType() == CommandTypes.ACTIVEMQ_OBJECT_MESSAGE) {
((ActiveMQObjectMessage)m).setTrustAllPackages(session.getConnection().isTrustAllPackages());
((ActiveMQObjectMessage)m).setTrustedPackages(session.getConnection().getTrustedPackages());
}
if (transformer != null) {
Message transformedMessage = transformer.consumerTransform(session, this, m);
if (transformedMessage != null) {
m = ActiveMQMessageTransformation.transformMessage(transformedMessage, session.connection);
}
}
if (session.isClientAcknowledge()) {
m.setAcknowledgeCallback(new Callback() {
@Override
public void execute() throws Exception {
session.checkClosed();
session.acknowledge();
}
});
} else if (session.isIndividualAcknowledge()) {
m.setAcknowledgeCallback(new Callback() {
@Override
public void execute() throws Exception {
session.checkClosed();
acknowledge(md);
}
});
}
return m;
}
/**
* Receives the next message that arrives within the specified timeout
* interval.
*
* This call blocks until a message arrives, the timeout expires, or this
* message consumer is closed. A timeout
of zero never
* expires, and the call blocks indefinitely.
*
* @param timeout the timeout value (in milliseconds), a time out of zero
* never expires.
* @return the next message produced for this message consumer, or null if
* the timeout expires or this message consumer is concurrently
* closed
*/
@Override
public Message receive(long timeout) throws JMSException {
checkClosed();
checkMessageListener();
if (timeout == 0) {
return this.receive();
}
sendPullCommand(timeout);
while (timeout > 0) {
MessageDispatch md;
if (info.getPrefetchSize() == 0) {
md = dequeue(-1); // We let the broker let us know when we timeout.
} else {
md = dequeue(timeout);
}
if (md == null) {
return null;
}
beforeMessageIsConsumed(md);
afterMessageIsConsumed(md, false);
return createActiveMQMessage(md);
}
return null;
}
/**
* Receives the next message if one is immediately available.
*
* @return the next message produced for this message consumer, or null if
* one is not available
* @throws JMSException if the JMS provider fails to receive the next
* message due to some internal error.
*/
@Override
public Message receiveNoWait() throws JMSException {
checkClosed();
checkMessageListener();
sendPullCommand(-1);
MessageDispatch md;
if (info.getPrefetchSize() == 0) {
md = dequeue(-1); // We let the broker let us know when we
// timeout.
} else {
md = dequeue(0);
}
if (md == null) {
return null;
}
beforeMessageIsConsumed(md);
afterMessageIsConsumed(md, false);
return createActiveMQMessage(md);
}
/**
* Closes the message consumer.
*
* Since a provider may allocate some resources on behalf of a
* MessageConsumer
* outside the Java virtual machine, clients should close them when they are
* not needed. Relying on garbage collection to eventually reclaim these
* resources may not be timely enough.
*
* This call blocks until a receive
or message listener in
* progress has completed. A blocked message consumer receive
* call returns null when this message consumer is closed.
*
* @throws JMSException if the JMS provider fails to close the consumer due
* to some internal error.
*/
@Override
public void close() throws JMSException {
if (!unconsumedMessages.isClosed()) {
if (!deliveredMessages.isEmpty() && session.getTransactionContext().isInTransaction()) {
session.getTransactionContext().addSynchronization(new Synchronization() {
@Override
public void afterCommit() throws Exception {
doClose();
}
@Override
public void afterRollback() throws Exception {
doClose();
}
});
} else {
doClose();
}
}
}
void doClose() throws JMSException {
dispose();
RemoveInfo removeCommand = info.createRemoveCommand();
LOG.debug("remove: {}, lastDeliveredSequenceId: {}", getConsumerId(), lastDeliveredSequenceId);
removeCommand.setLastDeliveredSequenceId(lastDeliveredSequenceId);
this.session.asyncSendPacket(removeCommand);
}
void inProgressClearRequired() {
inProgressClearRequiredFlag.incrementAndGet();
// deal with delivered messages async to avoid lock contention with in progress acks
clearDeliveredList = true;
// force a rollback if we will be acking in a transaction after/during failover
// bc acks are async they may not get there reliably on reconnect and the consumer
// may not be aware of the reconnect in a timely fashion if in onMessage
if (!deliveredMessages.isEmpty() && session.getTransactionContext().isInTransaction()) {
session.getTransactionContext().setRollbackOnly(true);
}
}
void clearMessagesInProgress() {
if (inProgressClearRequiredFlag.get() > 0) {
synchronized (unconsumedMessages.getMutex()) {
if (inProgressClearRequiredFlag.get() > 0) {
LOG.debug("{} clearing unconsumed list ({}) on transport interrupt", getConsumerId(), unconsumedMessages.size());
// ensure unconsumed are rolledback up front as they may get redelivered to another consumer
List list = unconsumedMessages.removeAll();
if (!this.info.isBrowser()) {
for (MessageDispatch old : list) {
session.connection.rollbackDuplicate(this, old.getMessage());
}
}
// allow dispatch on this connection to resume
session.connection.transportInterruptionProcessingComplete();
inProgressClearRequiredFlag.decrementAndGet();
// Wake up any blockers and allow them to recheck state.
unconsumedMessages.getMutex().notifyAll();
}
}
}
clearDeliveredList();
}
void deliverAcks() {
MessageAck ack = null;
if (deliveryingAcknowledgements.compareAndSet(false, true)) {
synchronized(deliveredMessages) {
if (isAutoAcknowledgeEach()) {
ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
if (ack != null) {
deliveredMessages.clear();
ackCounter = 0;
} else {
ack = pendingAck;
pendingAck = null;
}
} else if (pendingAck != null && pendingAck.isStandardAck()) {
ack = pendingAck;
pendingAck = null;
}
}
if (ack != null) {
final MessageAck ackToSend = ack;
if (executorService == null) {
executorService = Executors.newSingleThreadExecutor();
}
executorService.submit(new Runnable() {
@Override
public void run() {
try {
session.sendAck(ackToSend,true);
} catch (JMSException e) {
LOG.error(getConsumerId() + " failed to delivered acknowledgements", e);
} finally {
deliveryingAcknowledgements.set(false);
}
}
});
} else {
deliveryingAcknowledgements.set(false);
}
}
}
public void dispose() throws JMSException {
if (!unconsumedMessages.isClosed()) {
// Do we have any acks we need to send out before closing?
// Ack any delivered messages now.
if (!session.getTransacted()) {
deliverAcks();
if (isAutoAcknowledgeBatch()) {
acknowledge();
}
}
if (executorService != null) {
ThreadPoolUtils.shutdownGraceful(executorService, 60000L);
executorService = null;
}
if (optimizedAckTask != null) {
this.session.connection.getScheduler().cancel(optimizedAckTask);
optimizedAckTask = null;
}
if (session.isClientAcknowledge() || session.isIndividualAcknowledge()) {
if (!this.info.isBrowser()) {
// rollback duplicates that aren't acknowledged
List tmp = null;
synchronized (this.deliveredMessages) {
tmp = new ArrayList(this.deliveredMessages);
}
for (MessageDispatch old : tmp) {
this.session.connection.rollbackDuplicate(this, old.getMessage());
}
tmp.clear();
}
}
if (!session.isTransacted()) {
synchronized(deliveredMessages) {
deliveredMessages.clear();
}
}
unconsumedMessages.close();
this.session.removeConsumer(this);
List list = unconsumedMessages.removeAll();
if (!this.info.isBrowser()) {
for (MessageDispatch old : list) {
// ensure we don't filter this as a duplicate
if (old.getMessage() != null) {
LOG.debug("on close, rollback duplicate: {}", old.getMessage().getMessageId());
}
session.connection.rollbackDuplicate(this, old.getMessage());
}
}
}
}
/**
* @throws IllegalStateException
*/
protected void checkClosed() throws IllegalStateException {
if (unconsumedMessages.isClosed()) {
throw new IllegalStateException("The Consumer is closed");
}
}
/**
* If we have a zero prefetch specified then send a pull command to the
* broker to pull a message we are about to receive
*/
protected void sendPullCommand(long timeout) throws JMSException {
clearDeliveredList();
if (info.getCurrentPrefetchSize() == 0 && unconsumedMessages.isEmpty()) {
MessagePull messagePull = new MessagePull();
messagePull.configure(info);
messagePull.setTimeout(timeout);
session.asyncSendPacket(messagePull);
}
}
protected void checkMessageListener() throws JMSException {
session.checkMessageListener();
}
protected void setOptimizeAcknowledge(boolean value) {
if (optimizeAcknowledge && !value) {
deliverAcks();
}
optimizeAcknowledge = value;
}
protected void setPrefetchSize(int prefetch) {
deliverAcks();
this.info.setCurrentPrefetchSize(prefetch);
}
private void beforeMessageIsConsumed(MessageDispatch md) throws JMSException {
md.setDeliverySequenceId(session.getNextDeliveryId());
lastDeliveredSequenceId = md.getMessage().getMessageId().getBrokerSequenceId();
if (!isAutoAcknowledgeBatch()) {
synchronized(deliveredMessages) {
deliveredMessages.addFirst(md);
}
if (session.getTransacted()) {
if (transactedIndividualAck) {
immediateIndividualTransactedAck(md);
} else {
ackLater(md, MessageAck.DELIVERED_ACK_TYPE);
}
}
}
}
private void immediateIndividualTransactedAck(MessageDispatch md) throws JMSException {
// acks accumulate on the broker pending transaction completion to indicate
// delivery status
registerSync();
MessageAck ack = new MessageAck(md, MessageAck.INDIVIDUAL_ACK_TYPE, 1);
ack.setTransactionId(session.getTransactionContext().getTransactionId());
session.sendAck(ack);
}
private void afterMessageIsConsumed(MessageDispatch md, boolean messageExpired) throws JMSException {
if (unconsumedMessages.isClosed()) {
return;
}
if (messageExpired) {
acknowledge(md, MessageAck.EXPIRED_ACK_TYPE);
stats.getExpiredMessageCount().increment();
} else {
stats.onMessage();
if (session.getTransacted()) {
// Do nothing.
} else if (isAutoAcknowledgeEach()) {
if (deliveryingAcknowledgements.compareAndSet(false, true)) {
synchronized (deliveredMessages) {
if (!deliveredMessages.isEmpty()) {
if (optimizeAcknowledge) {
ackCounter++;
// AMQ-3956 evaluate both expired and normal msgs as
// otherwise consumer may get stalled
if (ackCounter + deliveredCounter >= (info.getPrefetchSize() * .65) || (optimizeAcknowledgeTimeOut > 0 && System.currentTimeMillis() >= (optimizeAckTimestamp + optimizeAcknowledgeTimeOut))) {
MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
if (ack != null) {
deliveredMessages.clear();
ackCounter = 0;
session.sendAck(ack);
optimizeAckTimestamp = System.currentTimeMillis();
}
// AMQ-3956 - as further optimization send
// ack for expired msgs when there are any.
// This resets the deliveredCounter to 0 so that
// we won't sent standard acks with every msg just
// because the deliveredCounter just below
// 0.5 * prefetch as used in ackLater()
if (pendingAck != null && deliveredCounter > 0) {
session.sendAck(pendingAck);
pendingAck = null;
deliveredCounter = 0;
}
}
} else {
MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
if (ack!=null) {
deliveredMessages.clear();
session.sendAck(ack);
}
}
}
}
deliveryingAcknowledgements.set(false);
}
} else if (isAutoAcknowledgeBatch()) {
ackLater(md, MessageAck.STANDARD_ACK_TYPE);
} else if (session.isClientAcknowledge()||session.isIndividualAcknowledge()) {
boolean messageUnackedByConsumer = false;
synchronized (deliveredMessages) {
messageUnackedByConsumer = deliveredMessages.contains(md);
}
if (messageUnackedByConsumer) {
ackLater(md, MessageAck.DELIVERED_ACK_TYPE);
}
}
else {
throw new IllegalStateException("Invalid session state.");
}
}
}
/**
* Creates a MessageAck for all messages contained in deliveredMessages.
* Caller should hold the lock for deliveredMessages.
*
* @param type Ack-Type (i.e. MessageAck.STANDARD_ACK_TYPE)
* @return null
if nothing to ack.
*/
private MessageAck makeAckForAllDeliveredMessages(byte type) {
synchronized (deliveredMessages) {
if (deliveredMessages.isEmpty()) {
return null;
}
MessageDispatch md = deliveredMessages.getFirst();
MessageAck ack = new MessageAck(md, type, deliveredMessages.size());
ack.setFirstMessageId(deliveredMessages.getLast().getMessage().getMessageId());
return ack;
}
}
private void ackLater(MessageDispatch md, byte ackType) throws JMSException {
// Don't acknowledge now, but we may need to let the broker know the
// consumer got the message to expand the pre-fetch window
if (session.getTransacted()) {
registerSync();
}
deliveredCounter++;
synchronized(deliveredMessages) {
MessageAck oldPendingAck = pendingAck;
pendingAck = new MessageAck(md, ackType, deliveredCounter);
pendingAck.setTransactionId(session.getTransactionContext().getTransactionId());
if( oldPendingAck==null ) {
pendingAck.setFirstMessageId(pendingAck.getLastMessageId());
} else if ( oldPendingAck.getAckType() == pendingAck.getAckType() ) {
pendingAck.setFirstMessageId(oldPendingAck.getFirstMessageId());
} else {
// old pending ack being superseded by ack of another type, if is is not a delivered
// ack and hence important, send it now so it is not lost.
if (!oldPendingAck.isDeliveredAck()) {
LOG.debug("Sending old pending ack {}, new pending: {}", oldPendingAck, pendingAck);
session.sendAck(oldPendingAck);
} else {
LOG.debug("dropping old pending ack {}, new pending: {}", oldPendingAck, pendingAck);
}
}
// AMQ-3956 evaluate both expired and normal msgs as
// otherwise consumer may get stalled
if ((0.5 * info.getPrefetchSize()) <= (deliveredCounter + ackCounter - additionalWindowSize)) {
LOG.debug("ackLater: sending: {}", pendingAck);
session.sendAck(pendingAck);
pendingAck=null;
deliveredCounter = 0;
additionalWindowSize = 0;
}
}
}
private void registerSync() throws JMSException {
session.doStartTransaction();
if (!synchronizationRegistered) {
synchronizationRegistered = true;
session.getTransactionContext().addSynchronization(new Synchronization() {
@Override
public void beforeEnd() throws Exception {
if (transactedIndividualAck) {
clearDeliveredList();
waitForRedeliveries();
synchronized(deliveredMessages) {
rollbackOnFailedRecoveryRedelivery();
}
} else {
acknowledge();
}
synchronizationRegistered = false;
}
@Override
public void afterCommit() throws Exception {
commit();
synchronizationRegistered = false;
}
@Override
public void afterRollback() throws Exception {
rollback();
synchronizationRegistered = false;
}
});
}
}
/**
* Acknowledge all the messages that have been delivered to the client up to
* this point.
*
* @throws JMSException
*/
public void acknowledge() throws JMSException {
clearDeliveredList();
waitForRedeliveries();
synchronized(deliveredMessages) {
// Acknowledge all messages so far.
MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
if (ack == null) {
return; // no msgs
}
if (session.getTransacted()) {
rollbackOnFailedRecoveryRedelivery();
session.doStartTransaction();
ack.setTransactionId(session.getTransactionContext().getTransactionId());
}
pendingAck = null;
session.sendAck(ack);
// Adjust the counters
deliveredCounter = Math.max(0, deliveredCounter - deliveredMessages.size());
additionalWindowSize = Math.max(0, additionalWindowSize - deliveredMessages.size());
if (!session.getTransacted()) {
deliveredMessages.clear();
}
}
}
private void waitForRedeliveries() {
if (failoverRedeliveryWaitPeriod > 0 && previouslyDeliveredMessages != null) {
long expiry = System.currentTimeMillis() + failoverRedeliveryWaitPeriod;
int numberNotReplayed;
do {
numberNotReplayed = 0;
synchronized(deliveredMessages) {
if (previouslyDeliveredMessages != null) {
for (Entry entry: previouslyDeliveredMessages.entrySet()) {
if (!entry.getValue()) {
numberNotReplayed++;
}
}
}
}
if (numberNotReplayed > 0) {
LOG.info("waiting for redelivery of {} in transaction: {}, to consumer: {}",
numberNotReplayed, this.getConsumerId(), previouslyDeliveredMessages.transactionId);
try {
Thread.sleep(Math.max(500, failoverRedeliveryWaitPeriod/4));
} catch (InterruptedException outOfhere) {
break;
}
}
} while (numberNotReplayed > 0 && expiry < System.currentTimeMillis());
}
}
/*
* called with deliveredMessages locked
*/
private void rollbackOnFailedRecoveryRedelivery() throws JMSException {
if (previouslyDeliveredMessages != null) {
// if any previously delivered messages was not re-delivered, transaction is invalid and must rollback
// as messages have been dispatched else where.
int numberNotReplayed = 0;
for (Entry entry: previouslyDeliveredMessages.entrySet()) {
if (!entry.getValue()) {
numberNotReplayed++;
LOG.debug("previously delivered message has not been replayed in transaction: {}, messageId: {}",
previouslyDeliveredMessages.transactionId, entry.getKey());
}
}
if (numberNotReplayed > 0) {
String message = "rolling back transaction ("
+ previouslyDeliveredMessages.transactionId + ") post failover recovery. " + numberNotReplayed
+ " previously delivered message(s) not replayed to consumer: " + this.getConsumerId();
LOG.warn(message);
throw new TransactionRolledBackException(message);
}
}
}
void acknowledge(MessageDispatch md) throws JMSException {
acknowledge(md, MessageAck.INDIVIDUAL_ACK_TYPE);
}
void acknowledge(MessageDispatch md, byte ackType) throws JMSException {
MessageAck ack = new MessageAck(md, ackType, 1);
if (ack.isExpiredAck()) {
ack.setFirstMessageId(ack.getLastMessageId());
}
session.sendAck(ack);
synchronized(deliveredMessages){
deliveredMessages.remove(md);
}
}
public void commit() throws JMSException {
synchronized (deliveredMessages) {
deliveredMessages.clear();
clearPreviouslyDelivered();
}
redeliveryDelay = 0;
}
public void rollback() throws JMSException {
clearDeliveredList();
synchronized (unconsumedMessages.getMutex()) {
if (optimizeAcknowledge) {
// remove messages read but not acked at the broker yet through
// optimizeAcknowledge
if (!this.info.isBrowser()) {
synchronized(deliveredMessages) {
for (int i = 0; (i < deliveredMessages.size()) && (i < ackCounter); i++) {
// ensure we don't filter this as a duplicate
MessageDispatch md = deliveredMessages.removeLast();
session.connection.rollbackDuplicate(this, md.getMessage());
}
}
}
}
synchronized(deliveredMessages) {
rollbackPreviouslyDeliveredAndNotRedelivered();
if (deliveredMessages.isEmpty()) {
return;
}
// use initial delay for first redelivery
MessageDispatch lastMd = deliveredMessages.getFirst();
final int currentRedeliveryCount = lastMd.getMessage().getRedeliveryCounter();
if (currentRedeliveryCount > 0) {
redeliveryDelay = redeliveryPolicy.getNextRedeliveryDelay(redeliveryDelay);
} else {
redeliveryDelay = redeliveryPolicy.getInitialRedeliveryDelay();
}
MessageId firstMsgId = deliveredMessages.getLast().getMessage().getMessageId();
for (Iterator iter = deliveredMessages.iterator(); iter.hasNext();) {
MessageDispatch md = iter.next();
md.getMessage().onMessageRolledBack();
// ensure we don't filter this as a duplicate
session.connection.rollbackDuplicate(this, md.getMessage());
}
if (redeliveryPolicy.getMaximumRedeliveries() != RedeliveryPolicy.NO_MAXIMUM_REDELIVERIES
&& lastMd.getMessage().getRedeliveryCounter() > redeliveryPolicy.getMaximumRedeliveries()) {
// We need to NACK the messages so that they get sent to the
// DLQ.
// Acknowledge the last message.
MessageAck ack = new MessageAck(lastMd, MessageAck.POSION_ACK_TYPE, deliveredMessages.size());
ack.setFirstMessageId(firstMsgId);
ack.setPoisonCause(new Throwable("Exceeded redelivery policy limit:" + redeliveryPolicy
+ ", cause:" + lastMd.getRollbackCause(), lastMd.getRollbackCause()));
session.sendAck(ack,true);
// Adjust the window size.
additionalWindowSize = Math.max(0, additionalWindowSize - deliveredMessages.size());
redeliveryDelay = 0;
deliveredCounter -= deliveredMessages.size();
deliveredMessages.clear();
} else {
// only redelivery_ack after first delivery
if (currentRedeliveryCount > 0) {
MessageAck ack = new MessageAck(lastMd, MessageAck.REDELIVERED_ACK_TYPE, deliveredMessages.size());
ack.setFirstMessageId(firstMsgId);
session.sendAck(ack,true);
}
// stop the delivery of messages.
if (nonBlockingRedelivery) {
if (!unconsumedMessages.isClosed()) {
final LinkedList pendingRedeliveries =
new LinkedList(deliveredMessages);
Collections.reverse(pendingRedeliveries);
deliveredCounter -= deliveredMessages.size();
deliveredMessages.clear();
// Start up the delivery again a little later.
session.getScheduler().executeAfterDelay(new Runnable() {
@Override
public void run() {
try {
if (!unconsumedMessages.isClosed()) {
for(MessageDispatch dispatch : pendingRedeliveries) {
session.dispatch(dispatch);
}
}
} catch (Exception e) {
session.connection.onAsyncException(e);
}
}
}, redeliveryDelay);
}
} else {
unconsumedMessages.stop();
for (MessageDispatch md : deliveredMessages) {
unconsumedMessages.enqueueFirst(md);
}
deliveredCounter -= deliveredMessages.size();
deliveredMessages.clear();
if (redeliveryDelay > 0 && !unconsumedMessages.isClosed()) {
// Start up the delivery again a little later.
session.getScheduler().executeAfterDelay(new Runnable() {
@Override
public void run() {
try {
if (started.get()) {
start();
}
} catch (JMSException e) {
session.connection.onAsyncException(e);
}
}
}, redeliveryDelay);
} else {
start();
}
}
}
}
}
if (messageListener.get() != null) {
session.redispatch(this, unconsumedMessages);
}
}
/*
* called with unconsumedMessages && deliveredMessages locked
* remove any message not re-delivered as they can't be replayed to this
* consumer on rollback
*/
private void rollbackPreviouslyDeliveredAndNotRedelivered() {
if (previouslyDeliveredMessages != null) {
for (Entry entry: previouslyDeliveredMessages.entrySet()) {
if (!entry.getValue()) {
LOG.trace("rollback non redelivered: {}" + entry.getKey());
removeFromDeliveredMessages(entry.getKey());
}
}
clearPreviouslyDelivered();
}
}
/*
* called with deliveredMessages locked
*/
private void removeFromDeliveredMessages(MessageId key) {
Iterator iterator = deliveredMessages.iterator();
while (iterator.hasNext()) {
MessageDispatch candidate = iterator.next();
if (key.equals(candidate.getMessage().getMessageId())) {
session.connection.rollbackDuplicate(this, candidate.getMessage());
iterator.remove();
break;
}
}
}
/*
* called with deliveredMessages locked
*/
private void clearPreviouslyDelivered() {
if (previouslyDeliveredMessages != null) {
previouslyDeliveredMessages.clear();
previouslyDeliveredMessages = null;
}
}
@Override
public void dispatch(MessageDispatch md) {
MessageListener listener = this.messageListener.get();
try {
clearMessagesInProgress();
clearDeliveredList();
synchronized (unconsumedMessages.getMutex()) {
if (!unconsumedMessages.isClosed()) {
if (this.info.isBrowser() || !session.connection.isDuplicate(this, md.getMessage())) {
if (listener != null && unconsumedMessages.isRunning()) {
if (redeliveryExceeded(md)) {
posionAck(md, "dispatch to " + getConsumerId() + " exceeds redelivery policy limit:" + redeliveryPolicy);
return;
}
ActiveMQMessage message = createActiveMQMessage(md);
beforeMessageIsConsumed(md);
try {
boolean expired = isConsumerExpiryCheckEnabled() && message.isExpired();
if (!expired) {
listener.onMessage(message);
}
afterMessageIsConsumed(md, expired);
} catch (RuntimeException e) {
LOG.error("{} Exception while processing message: {}", getConsumerId(), md.getMessage().getMessageId(), e);
md.setRollbackCause(e);
if (isAutoAcknowledgeBatch() || isAutoAcknowledgeEach() || session.isIndividualAcknowledge()) {
// schedual redelivery and possible dlq processing
rollback();
} else {
// Transacted or Client ack: Deliver the next message.
afterMessageIsConsumed(md, false);
}
}
} else {
if (!unconsumedMessages.isRunning()) {
// delayed redelivery, ensure it can be re delivered
session.connection.rollbackDuplicate(this, md.getMessage());
}
if (md.getMessage() == null) {
// End of browse or pull request timeout.
unconsumedMessages.enqueue(md);
} else {
if (!consumeExpiredMessage(md)) {
unconsumedMessages.enqueue(md);
if (availableListener != null) {
availableListener.onMessageAvailable(this);
}
} else {
beforeMessageIsConsumed(md);
afterMessageIsConsumed(md, true);
// Pull consumer needs to check if pull timed out and send
// a new pull command if not.
if (info.getCurrentPrefetchSize() == 0) {
unconsumedMessages.enqueue(null);
}
}
}
}
} else {
// deal with duplicate delivery
ConsumerId consumerWithPendingTransaction;
if (redeliveryExpectedInCurrentTransaction(md, true)) {
LOG.debug("{} tracking transacted redelivery {}", getConsumerId(), md.getMessage());
if (transactedIndividualAck) {
immediateIndividualTransactedAck(md);
} else {
session.sendAck(new MessageAck(md, MessageAck.DELIVERED_ACK_TYPE, 1));
}
} else if ((consumerWithPendingTransaction = redeliveryPendingInCompetingTransaction(md)) != null) {
LOG.warn("{} delivering duplicate {}, pending transaction completion on {} will rollback", getConsumerId(), md.getMessage(), consumerWithPendingTransaction);
session.getConnection().rollbackDuplicate(this, md.getMessage());
dispatch(md);
} else {
LOG.warn("{} suppressing duplicate delivery on connection, poison acking: {}", getConsumerId(), md);
posionAck(md, "Suppressing duplicate delivery on connection, consumer " + getConsumerId());
}
}
}
}
if (++dispatchedCount % 1000 == 0) {
dispatchedCount = 0;
Thread.yield();
}
} catch (Exception e) {
session.connection.onClientInternalException(e);
}
}
private boolean redeliveryExpectedInCurrentTransaction(MessageDispatch md, boolean markReceipt) {
if (session.isTransacted()) {
synchronized (deliveredMessages) {
if (previouslyDeliveredMessages != null) {
if (previouslyDeliveredMessages.containsKey(md.getMessage().getMessageId())) {
if (markReceipt) {
previouslyDeliveredMessages.put(md.getMessage().getMessageId(), true);
}
return true;
}
}
}
}
return false;
}
private ConsumerId redeliveryPendingInCompetingTransaction(MessageDispatch md) {
for (ActiveMQSession activeMQSession: session.connection.getSessions()) {
for (ActiveMQMessageConsumer activeMQMessageConsumer : activeMQSession.consumers) {
if (activeMQMessageConsumer.redeliveryExpectedInCurrentTransaction(md, false)) {
return activeMQMessageConsumer.getConsumerId();
}
}
}
return null;
}
// async (on next call) clear or track delivered as they may be flagged as duplicates if they arrive again
private void clearDeliveredList() {
if (clearDeliveredList) {
synchronized (deliveredMessages) {
if (clearDeliveredList) {
if (!deliveredMessages.isEmpty()) {
if (session.isTransacted()) {
if (previouslyDeliveredMessages == null) {
previouslyDeliveredMessages = new PreviouslyDeliveredMap(session.getTransactionContext().getTransactionId());
}
for (MessageDispatch delivered : deliveredMessages) {
previouslyDeliveredMessages.put(delivered.getMessage().getMessageId(), false);
}
LOG.debug("{} tracking existing transacted {} delivered list ({}) on transport interrupt",
getConsumerId(), previouslyDeliveredMessages.transactionId, deliveredMessages.size());
} else {
if (session.isClientAcknowledge()) {
LOG.debug("{} rolling back delivered list ({}) on transport interrupt", getConsumerId(), deliveredMessages.size());
// allow redelivery
if (!this.info.isBrowser()) {
for (MessageDispatch md: deliveredMessages) {
this.session.connection.rollbackDuplicate(this, md.getMessage());
}
}
}
LOG.debug("{} clearing delivered list ({}) on transport interrupt", getConsumerId(), deliveredMessages.size());
deliveredMessages.clear();
pendingAck = null;
}
}
clearDeliveredList = false;
}
}
}
}
public int getMessageSize() {
return unconsumedMessages.size();
}
public void start() throws JMSException {
if (unconsumedMessages.isClosed()) {
return;
}
started.set(true);
unconsumedMessages.start();
session.executor.wakeup();
}
public void stop() {
started.set(false);
unconsumedMessages.stop();
}
@Override
public String toString() {
return "ActiveMQMessageConsumer { value=" + info.getConsumerId() + ", started=" + started.get()
+ " }";
}
/**
* Delivers a message to the message listener.
*
* @return true if another execution is needed.
*
* @throws JMSException
*/
public boolean iterate() {
MessageListener listener = this.messageListener.get();
if (listener != null) {
MessageDispatch md = unconsumedMessages.dequeueNoWait();
if (md != null) {
dispatch(md);
return true;
}
}
return false;
}
public boolean isInUse(ActiveMQTempDestination destination) {
return info.getDestination().equals(destination);
}
public long getLastDeliveredSequenceId() {
return lastDeliveredSequenceId;
}
public IOException getFailureError() {
return failureError;
}
public void setFailureError(IOException failureError) {
this.failureError = failureError;
}
/**
* @return the optimizedAckScheduledAckInterval
*/
public long getOptimizedAckScheduledAckInterval() {
return optimizedAckScheduledAckInterval;
}
/**
* @param optimizedAckScheduledAckInterval the optimizedAckScheduledAckInterval to set
*/
public void setOptimizedAckScheduledAckInterval(long optimizedAckScheduledAckInterval) throws JMSException {
this.optimizedAckScheduledAckInterval = optimizedAckScheduledAckInterval;
if (this.optimizedAckTask != null) {
try {
this.session.connection.getScheduler().cancel(optimizedAckTask);
} catch (JMSException e) {
LOG.debug("Caught exception while cancelling old optimized ack task", e);
throw e;
}
this.optimizedAckTask = null;
}
// Should we periodically send out all outstanding acks.
if (this.optimizeAcknowledge && this.optimizedAckScheduledAckInterval > 0) {
this.optimizedAckTask = new Runnable() {
@Override
public void run() {
try {
if (optimizeAcknowledge && !unconsumedMessages.isClosed()) {
LOG.info("Consumer:{} is performing scheduled delivery of outstanding optimized Acks", info.getConsumerId());
deliverAcks();
}
} catch (Exception e) {
LOG.debug("Optimized Ack Task caught exception during ack", e);
}
}
};
try {
this.session.connection.getScheduler().executePeriodically(optimizedAckTask, optimizedAckScheduledAckInterval);
} catch (JMSException e) {
LOG.debug("Caught exception while scheduling new optimized ack task", e);
throw e;
}
}
}
public boolean hasMessageListener() {
return messageListener.get() != null;
}
public boolean isConsumerExpiryCheckEnabled() {
return consumerExpiryCheckEnabled;
}
public void setConsumerExpiryCheckEnabled(boolean consumerExpiryCheckEnabled) {
this.consumerExpiryCheckEnabled = consumerExpiryCheckEnabled;
}
}