001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.concurrent.ExecutorService; 029import java.util.concurrent.Executors; 030import java.util.concurrent.atomic.AtomicBoolean; 031import java.util.concurrent.atomic.AtomicInteger; 032import java.util.concurrent.atomic.AtomicReference; 033 034import javax.jms.IllegalStateException; 035import javax.jms.InvalidDestinationException; 036import javax.jms.JMSException; 037import javax.jms.Message; 038import javax.jms.MessageConsumer; 039import javax.jms.MessageListener; 040import javax.jms.TransactionRolledBackException; 041 042import org.apache.activemq.blob.BlobDownloader; 043import org.apache.activemq.command.*; 044import org.apache.activemq.management.JMSConsumerStatsImpl; 045import org.apache.activemq.management.StatsCapable; 046import org.apache.activemq.management.StatsImpl; 047import org.apache.activemq.selector.SelectorParser; 048import org.apache.activemq.transaction.Synchronization; 049import org.apache.activemq.util.Callback; 050import org.apache.activemq.util.IntrospectionSupport; 051import org.apache.activemq.util.JMSExceptionSupport; 052import org.apache.activemq.util.ThreadPoolUtils; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056/** 057 * A client uses a <CODE>MessageConsumer</CODE> object to receive messages 058 * from a destination. A <CODE> MessageConsumer</CODE> object is created by 059 * passing a <CODE>Destination</CODE> object to a message-consumer creation 060 * method supplied by a session. 061 * <P> 062 * <CODE>MessageConsumer</CODE> is the parent interface for all message 063 * consumers. 064 * <P> 065 * A message consumer can be created with a message selector. A message selector 066 * allows the client to restrict the messages delivered to the message consumer 067 * to those that match the selector. 068 * <P> 069 * A client may either synchronously receive a message consumer's messages or 070 * have the consumer asynchronously deliver them as they arrive. 071 * <P> 072 * For synchronous receipt, a client can request the next message from a message 073 * consumer using one of its <CODE> receive</CODE> methods. There are several 074 * variations of <CODE>receive</CODE> that allow a client to poll or wait for 075 * the next message. 076 * <P> 077 * For asynchronous delivery, a client can register a 078 * <CODE>MessageListener</CODE> object with a message consumer. As messages 079 * arrive at the message consumer, it delivers them by calling the 080 * <CODE>MessageListener</CODE>'s<CODE> 081 * onMessage</CODE> method. 082 * <P> 083 * It is a client programming error for a <CODE>MessageListener</CODE> to 084 * throw an exception. 085 * 086 * 087 * @see javax.jms.MessageConsumer 088 * @see javax.jms.QueueReceiver 089 * @see javax.jms.TopicSubscriber 090 * @see javax.jms.Session 091 */ 092public class ActiveMQMessageConsumer implements MessageAvailableConsumer, StatsCapable, ActiveMQDispatcher { 093 094 @SuppressWarnings("serial") 095 class PreviouslyDeliveredMap<K, V> extends HashMap<K, V> { 096 final TransactionId transactionId; 097 public PreviouslyDeliveredMap(TransactionId transactionId) { 098 this.transactionId = transactionId; 099 } 100 } 101 102 private static final Logger LOG = LoggerFactory.getLogger(ActiveMQMessageConsumer.class); 103 protected final ActiveMQSession session; 104 protected final ConsumerInfo info; 105 106 // These are the messages waiting to be delivered to the client 107 protected final MessageDispatchChannel unconsumedMessages; 108 109 // The are the messages that were delivered to the consumer but that have 110 // not been acknowledged. It's kept in reverse order since we 111 // Always walk list in reverse order. 112 protected final LinkedList<MessageDispatch> deliveredMessages = new LinkedList<MessageDispatch>(); 113 // track duplicate deliveries in a transaction such that the tx integrity can be validated 114 private PreviouslyDeliveredMap<MessageId, Boolean> previouslyDeliveredMessages; 115 private int deliveredCounter; 116 private int additionalWindowSize; 117 private long redeliveryDelay; 118 private int ackCounter; 119 private int dispatchedCount; 120 private final AtomicReference<MessageListener> messageListener = new AtomicReference<MessageListener>(); 121 private final JMSConsumerStatsImpl stats; 122 123 private final String selector; 124 private boolean synchronizationRegistered; 125 private final AtomicBoolean started = new AtomicBoolean(false); 126 127 private MessageAvailableListener availableListener; 128 129 private RedeliveryPolicy redeliveryPolicy; 130 private boolean optimizeAcknowledge; 131 private final AtomicBoolean deliveryingAcknowledgements = new AtomicBoolean(); 132 private ExecutorService executorService; 133 private MessageTransformer transformer; 134 private boolean clearDeliveredList; 135 AtomicInteger inProgressClearRequiredFlag = new AtomicInteger(0); 136 137 private MessageAck pendingAck; 138 private long lastDeliveredSequenceId = -1; 139 140 private IOException failureError; 141 142 private long optimizeAckTimestamp = System.currentTimeMillis(); 143 private long optimizeAcknowledgeTimeOut = 0; 144 private long optimizedAckScheduledAckInterval = 0; 145 private Runnable optimizedAckTask; 146 private long failoverRedeliveryWaitPeriod = 0; 147 private boolean transactedIndividualAck = false; 148 private boolean nonBlockingRedelivery = false; 149 private boolean consumerExpiryCheckEnabled = true; 150 151 /** 152 * Create a MessageConsumer 153 * 154 * @param session 155 * @param dest 156 * @param name 157 * @param selector 158 * @param prefetch 159 * @param maximumPendingMessageCount 160 * @param noLocal 161 * @param browser 162 * @param dispatchAsync 163 * @param messageListener 164 * @throws JMSException 165 */ 166 public ActiveMQMessageConsumer(ActiveMQSession session, ConsumerId consumerId, ActiveMQDestination dest, 167 String name, String selector, int prefetch, 168 int maximumPendingMessageCount, boolean noLocal, boolean browser, 169 boolean dispatchAsync, MessageListener messageListener) throws JMSException { 170 if (dest == null) { 171 throw new InvalidDestinationException("Don't understand null destinations"); 172 } else if (dest.getPhysicalName() == null) { 173 throw new InvalidDestinationException("The destination object was not given a physical name."); 174 } else if (dest.isTemporary()) { 175 String physicalName = dest.getPhysicalName(); 176 177 if (physicalName == null) { 178 throw new IllegalArgumentException("Physical name of Destination should be valid: " + dest); 179 } 180 181 String connectionID = session.connection.getConnectionInfo().getConnectionId().getValue(); 182 183 if (physicalName.indexOf(connectionID) < 0) { 184 throw new InvalidDestinationException("Cannot use a Temporary destination from another Connection"); 185 } 186 187 if (session.connection.isDeleted(dest)) { 188 throw new InvalidDestinationException("Cannot use a Temporary destination that has been deleted"); 189 } 190 if (prefetch < 0) { 191 throw new JMSException("Cannot have a prefetch size less than zero"); 192 } 193 } 194 if (session.connection.isMessagePrioritySupported()) { 195 this.unconsumedMessages = new SimplePriorityMessageDispatchChannel(); 196 }else { 197 this.unconsumedMessages = new FifoMessageDispatchChannel(); 198 } 199 200 this.session = session; 201 this.redeliveryPolicy = session.connection.getRedeliveryPolicyMap().getEntryFor(dest); 202 setTransformer(session.getTransformer()); 203 204 this.info = new ConsumerInfo(consumerId); 205 this.info.setExclusive(this.session.connection.isExclusiveConsumer()); 206 this.info.setClientId(this.session.connection.getClientID()); 207 this.info.setSubscriptionName(name); 208 this.info.setPrefetchSize(prefetch); 209 this.info.setCurrentPrefetchSize(prefetch); 210 this.info.setMaximumPendingMessageLimit(maximumPendingMessageCount); 211 this.info.setNoLocal(noLocal); 212 this.info.setDispatchAsync(dispatchAsync); 213 this.info.setRetroactive(this.session.connection.isUseRetroactiveConsumer()); 214 this.info.setSelector(null); 215 216 // Allows the options on the destination to configure the consumerInfo 217 if (dest.getOptions() != null) { 218 Map<String, Object> options = IntrospectionSupport.extractProperties( 219 new HashMap<String, Object>(dest.getOptions()), "consumer."); 220 IntrospectionSupport.setProperties(this.info, options); 221 if (options.size() > 0) { 222 String msg = "There are " + options.size() 223 + " consumer options that couldn't be set on the consumer." 224 + " Check the options are spelled correctly." 225 + " Unknown parameters=[" + options + "]." 226 + " This consumer cannot be started."; 227 LOG.warn(msg); 228 throw new ConfigurationException(msg); 229 } 230 } 231 232 this.info.setDestination(dest); 233 this.info.setBrowser(browser); 234 if (selector != null && selector.trim().length() != 0) { 235 // Validate the selector 236 SelectorParser.parse(selector); 237 this.info.setSelector(selector); 238 this.selector = selector; 239 } else if (info.getSelector() != null) { 240 // Validate the selector 241 SelectorParser.parse(this.info.getSelector()); 242 this.selector = this.info.getSelector(); 243 } else { 244 this.selector = null; 245 } 246 247 this.stats = new JMSConsumerStatsImpl(session.getSessionStats(), dest); 248 this.optimizeAcknowledge = session.connection.isOptimizeAcknowledge() && session.isAutoAcknowledge() 249 && !info.isBrowser(); 250 if (this.optimizeAcknowledge) { 251 this.optimizeAcknowledgeTimeOut = session.connection.getOptimizeAcknowledgeTimeOut(); 252 setOptimizedAckScheduledAckInterval(session.connection.getOptimizedAckScheduledAckInterval()); 253 } 254 255 this.info.setOptimizedAcknowledge(this.optimizeAcknowledge); 256 this.failoverRedeliveryWaitPeriod = session.connection.getConsumerFailoverRedeliveryWaitPeriod(); 257 this.nonBlockingRedelivery = session.connection.isNonBlockingRedelivery(); 258 this.transactedIndividualAck = session.connection.isTransactedIndividualAck() 259 || this.nonBlockingRedelivery 260 || session.connection.isMessagePrioritySupported(); 261 this.consumerExpiryCheckEnabled = session.connection.isConsumerExpiryCheckEnabled(); 262 if (messageListener != null) { 263 setMessageListener(messageListener); 264 } 265 try { 266 this.session.addConsumer(this); 267 this.session.syncSendPacket(info); 268 } catch (JMSException e) { 269 this.session.removeConsumer(this); 270 throw e; 271 } 272 273 if (session.connection.isStarted()) { 274 start(); 275 } 276 } 277 278 private boolean isAutoAcknowledgeEach() { 279 return session.isAutoAcknowledge() || ( session.isDupsOkAcknowledge() && getDestination().isQueue() ); 280 } 281 282 private boolean isAutoAcknowledgeBatch() { 283 return session.isDupsOkAcknowledge() && !getDestination().isQueue() ; 284 } 285 286 @Override 287 public StatsImpl getStats() { 288 return stats; 289 } 290 291 public JMSConsumerStatsImpl getConsumerStats() { 292 return stats; 293 } 294 295 public RedeliveryPolicy getRedeliveryPolicy() { 296 return redeliveryPolicy; 297 } 298 299 /** 300 * Sets the redelivery policy used when messages are redelivered 301 */ 302 public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) { 303 this.redeliveryPolicy = redeliveryPolicy; 304 } 305 306 public MessageTransformer getTransformer() { 307 return transformer; 308 } 309 310 /** 311 * Sets the transformer used to transform messages before they are sent on 312 * to the JMS bus 313 */ 314 public void setTransformer(MessageTransformer transformer) { 315 this.transformer = transformer; 316 } 317 318 /** 319 * @return Returns the value. 320 */ 321 public ConsumerId getConsumerId() { 322 return info.getConsumerId(); 323 } 324 325 /** 326 * @return the consumer name - used for durable consumers 327 */ 328 public String getConsumerName() { 329 return this.info.getSubscriptionName(); 330 } 331 332 /** 333 * @return true if this consumer does not accept locally produced messages 334 */ 335 protected boolean isNoLocal() { 336 return info.isNoLocal(); 337 } 338 339 /** 340 * Retrieve is a browser 341 * 342 * @return true if a browser 343 */ 344 protected boolean isBrowser() { 345 return info.isBrowser(); 346 } 347 348 /** 349 * @return ActiveMQDestination 350 */ 351 protected ActiveMQDestination getDestination() { 352 return info.getDestination(); 353 } 354 355 /** 356 * @return Returns the prefetchNumber. 357 */ 358 public int getPrefetchNumber() { 359 return info.getPrefetchSize(); 360 } 361 362 /** 363 * @return true if this is a durable topic subscriber 364 */ 365 public boolean isDurableSubscriber() { 366 return info.getSubscriptionName() != null && info.getDestination().isTopic(); 367 } 368 369 /** 370 * Gets this message consumer's message selector expression. 371 * 372 * @return this message consumer's message selector, or null if no message 373 * selector exists for the message consumer (that is, if the message 374 * selector was not set or was set to null or the empty string) 375 * @throws JMSException if the JMS provider fails to receive the next 376 * message due to some internal error. 377 */ 378 @Override 379 public String getMessageSelector() throws JMSException { 380 checkClosed(); 381 return selector; 382 } 383 384 /** 385 * Gets the message consumer's <CODE>MessageListener</CODE>. 386 * 387 * @return the listener for the message consumer, or null if no listener is 388 * set 389 * @throws JMSException if the JMS provider fails to get the message 390 * listener due to some internal error. 391 * @see javax.jms.MessageConsumer#setMessageListener(javax.jms.MessageListener) 392 */ 393 @Override 394 public MessageListener getMessageListener() throws JMSException { 395 checkClosed(); 396 return this.messageListener.get(); 397 } 398 399 /** 400 * Sets the message consumer's <CODE>MessageListener</CODE>. 401 * <P> 402 * Setting the message listener to null is the equivalent of unsetting the 403 * message listener for the message consumer. 404 * <P> 405 * The effect of calling <CODE>MessageConsumer.setMessageListener</CODE> 406 * while messages are being consumed by an existing listener or the consumer 407 * is being used to consume messages synchronously is undefined. 408 * 409 * @param listener the listener to which the messages are to be delivered 410 * @throws JMSException if the JMS provider fails to receive the next 411 * message due to some internal error. 412 * @see javax.jms.MessageConsumer#getMessageListener 413 */ 414 @Override 415 public void setMessageListener(MessageListener listener) throws JMSException { 416 checkClosed(); 417 if (info.getPrefetchSize() == 0) { 418 throw new JMSException("Illegal prefetch size of zero. This setting is not supported for asynchronous consumers please set a value of at least 1"); 419 } 420 if (listener != null) { 421 boolean wasRunning = session.isRunning(); 422 if (wasRunning) { 423 session.stop(); 424 } 425 426 this.messageListener.set(listener); 427 session.redispatch(this, unconsumedMessages); 428 429 if (wasRunning) { 430 session.start(); 431 } 432 } else { 433 this.messageListener.set(null); 434 } 435 } 436 437 @Override 438 public MessageAvailableListener getAvailableListener() { 439 return availableListener; 440 } 441 442 /** 443 * Sets the listener used to notify synchronous consumers that there is a 444 * message available so that the {@link MessageConsumer#receiveNoWait()} can 445 * be called. 446 */ 447 @Override 448 public void setAvailableListener(MessageAvailableListener availableListener) { 449 this.availableListener = availableListener; 450 } 451 452 /** 453 * Used to get an enqueued message from the unconsumedMessages list. The 454 * amount of time this method blocks is based on the timeout value. - if 455 * timeout==-1 then it blocks until a message is received. - if timeout==0 456 * then it it tries to not block at all, it returns a message if it is 457 * available - if timeout>0 then it blocks up to timeout amount of time. 458 * Expired messages will consumed by this method. 459 * 460 * @throws JMSException 461 * @return null if we timeout or if the consumer is closed. 462 */ 463 private MessageDispatch dequeue(long timeout) throws JMSException { 464 try { 465 long deadline = 0; 466 if (timeout > 0) { 467 deadline = System.currentTimeMillis() + timeout; 468 } 469 while (true) { 470 MessageDispatch md = unconsumedMessages.dequeue(timeout); 471 if (md == null) { 472 if (timeout > 0 && !unconsumedMessages.isClosed()) { 473 timeout = Math.max(deadline - System.currentTimeMillis(), 0); 474 } else { 475 if (failureError != null) { 476 throw JMSExceptionSupport.create(failureError); 477 } else { 478 return null; 479 } 480 } 481 } else if (md.getMessage() == null) { 482 return null; 483 } else if (isConsumerExpiryCheckEnabled() && md.getMessage().isExpired()) { 484 if (LOG.isDebugEnabled()) { 485 LOG.debug(getConsumerId() + " received expired message: " + md); 486 } 487 beforeMessageIsConsumed(md); 488 afterMessageIsConsumed(md, true); 489 if (timeout > 0) { 490 timeout = Math.max(deadline - System.currentTimeMillis(), 0); 491 } 492 } else if (redeliveryExceeded(md)) { 493 if (LOG.isDebugEnabled()) { 494 LOG.debug(getConsumerId() + " received with excessive redelivered: " + md); 495 } 496 posionAck(md, "dispatch to " + getConsumerId() + " exceeds redelivery policy limit:" + redeliveryPolicy); 497 if (timeout > 0) { 498 timeout = Math.max(deadline - System.currentTimeMillis(), 0); 499 } 500 sendPullCommand(timeout); 501 } else { 502 if (LOG.isTraceEnabled()) { 503 LOG.trace(getConsumerId() + " received message: " + md); 504 } 505 return md; 506 } 507 } 508 } catch (InterruptedException e) { 509 Thread.currentThread().interrupt(); 510 throw JMSExceptionSupport.create(e); 511 } 512 } 513 514 private void posionAck(MessageDispatch md, String cause) throws JMSException { 515 MessageAck posionAck = new MessageAck(md, MessageAck.POSION_ACK_TYPE, 1); 516 posionAck.setFirstMessageId(md.getMessage().getMessageId()); 517 posionAck.setPoisonCause(new Throwable(cause)); 518 session.sendAck(posionAck); 519 } 520 521 private boolean redeliveryExceeded(MessageDispatch md) { 522 try { 523 return session.getTransacted() 524 && redeliveryPolicy != null 525 && redeliveryPolicy.getMaximumRedeliveries() != RedeliveryPolicy.NO_MAXIMUM_REDELIVERIES 526 && md.getRedeliveryCounter() > redeliveryPolicy.getMaximumRedeliveries() 527 // redeliveryCounter > x expected after resend via brokerRedeliveryPlugin 528 && md.getMessage().getProperty("redeliveryDelay") == null; 529 } catch (Exception ignored) { 530 return false; 531 } 532 } 533 534 /** 535 * Receives the next message produced for this message consumer. 536 * <P> 537 * This call blocks indefinitely until a message is produced or until this 538 * message consumer is closed. 539 * <P> 540 * If this <CODE>receive</CODE> is done within a transaction, the consumer 541 * retains the message until the transaction commits. 542 * 543 * @return the next message produced for this message consumer, or null if 544 * this message consumer is concurrently closed 545 */ 546 @Override 547 public Message receive() throws JMSException { 548 checkClosed(); 549 checkMessageListener(); 550 551 sendPullCommand(0); 552 MessageDispatch md = dequeue(-1); 553 if (md == null) { 554 return null; 555 } 556 557 beforeMessageIsConsumed(md); 558 afterMessageIsConsumed(md, false); 559 560 return createActiveMQMessage(md); 561 } 562 563 /** 564 * @param md 565 * @return 566 */ 567 private ActiveMQMessage createActiveMQMessage(final MessageDispatch md) throws JMSException { 568 ActiveMQMessage m = (ActiveMQMessage)md.getMessage().copy(); 569 if (m.getDataStructureType()==CommandTypes.ACTIVEMQ_BLOB_MESSAGE) { 570 ((ActiveMQBlobMessage)m).setBlobDownloader(new BlobDownloader(session.getBlobTransferPolicy())); 571 } 572 if (m.getDataStructureType() == CommandTypes.ACTIVEMQ_OBJECT_MESSAGE) { 573 ((ActiveMQObjectMessage)m).setTrustAllPackages(session.getConnection().isTrustAllPackages()); 574 ((ActiveMQObjectMessage)m).setTrustedPackages(session.getConnection().getTrustedPackages()); 575 } 576 if (transformer != null) { 577 Message transformedMessage = transformer.consumerTransform(session, this, m); 578 if (transformedMessage != null) { 579 m = ActiveMQMessageTransformation.transformMessage(transformedMessage, session.connection); 580 } 581 } 582 if (session.isClientAcknowledge()) { 583 m.setAcknowledgeCallback(new Callback() { 584 @Override 585 public void execute() throws Exception { 586 session.checkClosed(); 587 session.acknowledge(); 588 } 589 }); 590 } else if (session.isIndividualAcknowledge()) { 591 m.setAcknowledgeCallback(new Callback() { 592 @Override 593 public void execute() throws Exception { 594 session.checkClosed(); 595 acknowledge(md); 596 } 597 }); 598 } 599 return m; 600 } 601 602 /** 603 * Receives the next message that arrives within the specified timeout 604 * interval. 605 * <P> 606 * This call blocks until a message arrives, the timeout expires, or this 607 * message consumer is closed. A <CODE>timeout</CODE> of zero never 608 * expires, and the call blocks indefinitely. 609 * 610 * @param timeout the timeout value (in milliseconds), a time out of zero 611 * never expires. 612 * @return the next message produced for this message consumer, or null if 613 * the timeout expires or this message consumer is concurrently 614 * closed 615 */ 616 @Override 617 public Message receive(long timeout) throws JMSException { 618 checkClosed(); 619 checkMessageListener(); 620 if (timeout == 0) { 621 return this.receive(); 622 } 623 624 sendPullCommand(timeout); 625 while (timeout > 0) { 626 627 MessageDispatch md; 628 if (info.getPrefetchSize() == 0) { 629 md = dequeue(-1); // We let the broker let us know when we timeout. 630 } else { 631 md = dequeue(timeout); 632 } 633 634 if (md == null) { 635 return null; 636 } 637 638 beforeMessageIsConsumed(md); 639 afterMessageIsConsumed(md, false); 640 return createActiveMQMessage(md); 641 } 642 return null; 643 } 644 645 /** 646 * Receives the next message if one is immediately available. 647 * 648 * @return the next message produced for this message consumer, or null if 649 * one is not available 650 * @throws JMSException if the JMS provider fails to receive the next 651 * message due to some internal error. 652 */ 653 @Override 654 public Message receiveNoWait() throws JMSException { 655 checkClosed(); 656 checkMessageListener(); 657 sendPullCommand(-1); 658 659 MessageDispatch md; 660 if (info.getPrefetchSize() == 0) { 661 md = dequeue(-1); // We let the broker let us know when we 662 // timeout. 663 } else { 664 md = dequeue(0); 665 } 666 667 if (md == null) { 668 return null; 669 } 670 671 beforeMessageIsConsumed(md); 672 afterMessageIsConsumed(md, false); 673 return createActiveMQMessage(md); 674 } 675 676 /** 677 * Closes the message consumer. 678 * <P> 679 * Since a provider may allocate some resources on behalf of a <CODE> 680 * MessageConsumer</CODE> 681 * outside the Java virtual machine, clients should close them when they are 682 * not needed. Relying on garbage collection to eventually reclaim these 683 * resources may not be timely enough. 684 * <P> 685 * This call blocks until a <CODE>receive</CODE> or message listener in 686 * progress has completed. A blocked message consumer <CODE>receive </CODE> 687 * call returns null when this message consumer is closed. 688 * 689 * @throws JMSException if the JMS provider fails to close the consumer due 690 * to some internal error. 691 */ 692 @Override 693 public void close() throws JMSException { 694 if (!unconsumedMessages.isClosed()) { 695 if (!deliveredMessages.isEmpty() && session.getTransactionContext().isInTransaction()) { 696 session.getTransactionContext().addSynchronization(new Synchronization() { 697 @Override 698 public void afterCommit() throws Exception { 699 doClose(); 700 } 701 702 @Override 703 public void afterRollback() throws Exception { 704 doClose(); 705 } 706 }); 707 } else { 708 doClose(); 709 } 710 } 711 } 712 713 void doClose() throws JMSException { 714 // Store interrupted state and clear so that Transport operations don't 715 // throw InterruptedException and we ensure that resources are cleaned up. 716 boolean interrupted = Thread.interrupted(); 717 dispose(); 718 RemoveInfo removeCommand = info.createRemoveCommand(); 719 if (LOG.isDebugEnabled()) { 720 LOG.debug("remove: " + this.getConsumerId() + ", lastDeliveredSequenceId:" + lastDeliveredSequenceId); 721 } 722 removeCommand.setLastDeliveredSequenceId(lastDeliveredSequenceId); 723 this.session.asyncSendPacket(removeCommand); 724 if (interrupted) { 725 Thread.currentThread().interrupt(); 726 } 727 } 728 729 void inProgressClearRequired() { 730 inProgressClearRequiredFlag.incrementAndGet(); 731 // deal with delivered messages async to avoid lock contention with in progress acks 732 clearDeliveredList = true; 733 // force a rollback if we will be acking in a transaction after/during failover 734 // bc acks are async they may not get there reliably on reconnect and the consumer 735 // may not be aware of the reconnect in a timely fashion if in onMessage 736 if (!deliveredMessages.isEmpty() && session.getTransactionContext().isInTransaction()) { 737 session.getTransactionContext().setRollbackOnly(true); 738 } 739 } 740 741 void clearMessagesInProgress() { 742 if (inProgressClearRequiredFlag.get() > 0) { 743 synchronized (unconsumedMessages.getMutex()) { 744 if (inProgressClearRequiredFlag.get() > 0) { 745 if (LOG.isDebugEnabled()) { 746 LOG.debug(getConsumerId() + " clearing unconsumed list (" + unconsumedMessages.size() + ") on transport interrupt"); 747 } 748 // ensure unconsumed are rolledback up front as they may get redelivered to another consumer 749 List<MessageDispatch> list = unconsumedMessages.removeAll(); 750 if (!this.info.isBrowser()) { 751 for (MessageDispatch old : list) { 752 session.connection.rollbackDuplicate(this, old.getMessage()); 753 } 754 } 755 // allow dispatch on this connection to resume 756 session.connection.transportInterruptionProcessingComplete(); 757 inProgressClearRequiredFlag.decrementAndGet(); 758 759 // Wake up any blockers and allow them to recheck state. 760 unconsumedMessages.getMutex().notifyAll(); 761 } 762 } 763 } 764 clearDeliveredList(); 765 } 766 767 void deliverAcks() { 768 MessageAck ack = null; 769 if (deliveryingAcknowledgements.compareAndSet(false, true)) { 770 if (isAutoAcknowledgeEach()) { 771 synchronized(deliveredMessages) { 772 ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE); 773 if (ack != null) { 774 deliveredMessages.clear(); 775 ackCounter = 0; 776 } else { 777 ack = pendingAck; 778 pendingAck = null; 779 } 780 } 781 } else if (pendingAck != null && pendingAck.isStandardAck()) { 782 ack = pendingAck; 783 pendingAck = null; 784 } 785 if (ack != null) { 786 final MessageAck ackToSend = ack; 787 788 if (executorService == null) { 789 executorService = Executors.newSingleThreadExecutor(); 790 } 791 executorService.submit(new Runnable() { 792 @Override 793 public void run() { 794 try { 795 session.sendAck(ackToSend,true); 796 } catch (JMSException e) { 797 LOG.error(getConsumerId() + " failed to delivered acknowledgements", e); 798 } finally { 799 deliveryingAcknowledgements.set(false); 800 } 801 } 802 }); 803 } else { 804 deliveryingAcknowledgements.set(false); 805 } 806 } 807 } 808 809 public void dispose() throws JMSException { 810 if (!unconsumedMessages.isClosed()) { 811 812 // Do we have any acks we need to send out before closing? 813 // Ack any delivered messages now. 814 if (!session.getTransacted()) { 815 deliverAcks(); 816 if (isAutoAcknowledgeBatch()) { 817 acknowledge(); 818 } 819 } 820 if (executorService != null) { 821 ThreadPoolUtils.shutdownGraceful(executorService, 60000L); 822 executorService = null; 823 } 824 if (optimizedAckTask != null) { 825 this.session.connection.getScheduler().cancel(optimizedAckTask); 826 optimizedAckTask = null; 827 } 828 829 if (session.isClientAcknowledge()) { 830 if (!this.info.isBrowser()) { 831 // rollback duplicates that aren't acknowledged 832 List<MessageDispatch> tmp = null; 833 synchronized (this.deliveredMessages) { 834 tmp = new ArrayList<MessageDispatch>(this.deliveredMessages); 835 } 836 for (MessageDispatch old : tmp) { 837 this.session.connection.rollbackDuplicate(this, old.getMessage()); 838 } 839 tmp.clear(); 840 } 841 } 842 if (!session.isTransacted()) { 843 synchronized(deliveredMessages) { 844 deliveredMessages.clear(); 845 } 846 } 847 unconsumedMessages.close(); 848 this.session.removeConsumer(this); 849 List<MessageDispatch> list = unconsumedMessages.removeAll(); 850 if (!this.info.isBrowser()) { 851 for (MessageDispatch old : list) { 852 // ensure we don't filter this as a duplicate 853 if (LOG.isDebugEnabled()) { 854 LOG.debug("on close, rollback duplicate: " + old.getMessage().getMessageId()); 855 } 856 session.connection.rollbackDuplicate(this, old.getMessage()); 857 } 858 } 859 } 860 } 861 862 /** 863 * @throws IllegalStateException 864 */ 865 protected void checkClosed() throws IllegalStateException { 866 if (unconsumedMessages.isClosed()) { 867 throw new IllegalStateException("The Consumer is closed"); 868 } 869 } 870 871 /** 872 * If we have a zero prefetch specified then send a pull command to the 873 * broker to pull a message we are about to receive 874 */ 875 protected void sendPullCommand(long timeout) throws JMSException { 876 clearDeliveredList(); 877 if (info.getCurrentPrefetchSize() == 0 && unconsumedMessages.isEmpty()) { 878 MessagePull messagePull = new MessagePull(); 879 messagePull.configure(info); 880 messagePull.setTimeout(timeout); 881 session.asyncSendPacket(messagePull); 882 } 883 } 884 885 protected void checkMessageListener() throws JMSException { 886 session.checkMessageListener(); 887 } 888 889 protected void setOptimizeAcknowledge(boolean value) { 890 if (optimizeAcknowledge && !value) { 891 deliverAcks(); 892 } 893 optimizeAcknowledge = value; 894 } 895 896 protected void setPrefetchSize(int prefetch) { 897 deliverAcks(); 898 this.info.setCurrentPrefetchSize(prefetch); 899 } 900 901 private void beforeMessageIsConsumed(MessageDispatch md) throws JMSException { 902 md.setDeliverySequenceId(session.getNextDeliveryId()); 903 lastDeliveredSequenceId = md.getMessage().getMessageId().getBrokerSequenceId(); 904 if (!isAutoAcknowledgeBatch()) { 905 synchronized(deliveredMessages) { 906 deliveredMessages.addFirst(md); 907 } 908 if (session.getTransacted()) { 909 if (transactedIndividualAck) { 910 immediateIndividualTransactedAck(md); 911 } else { 912 ackLater(md, MessageAck.DELIVERED_ACK_TYPE); 913 } 914 } 915 } 916 } 917 918 private void immediateIndividualTransactedAck(MessageDispatch md) throws JMSException { 919 // acks accumulate on the broker pending transaction completion to indicate 920 // delivery status 921 registerSync(); 922 MessageAck ack = new MessageAck(md, MessageAck.INDIVIDUAL_ACK_TYPE, 1); 923 ack.setTransactionId(session.getTransactionContext().getTransactionId()); 924 session.sendAck(ack); 925 } 926 927 private void afterMessageIsConsumed(MessageDispatch md, boolean messageExpired) throws JMSException { 928 if (unconsumedMessages.isClosed()) { 929 return; 930 } 931 if (messageExpired) { 932 acknowledge(md, MessageAck.EXPIRED_ACK_TYPE); 933 stats.getExpiredMessageCount().increment(); 934 } else { 935 stats.onMessage(); 936 if (session.getTransacted()) { 937 // Do nothing. 938 } else if (isAutoAcknowledgeEach()) { 939 if (deliveryingAcknowledgements.compareAndSet(false, true)) { 940 synchronized (deliveredMessages) { 941 if (!deliveredMessages.isEmpty()) { 942 if (optimizeAcknowledge) { 943 ackCounter++; 944 945 // AMQ-3956 evaluate both expired and normal msgs as 946 // otherwise consumer may get stalled 947 if (ackCounter + deliveredCounter >= (info.getPrefetchSize() * .65) || (optimizeAcknowledgeTimeOut > 0 && System.currentTimeMillis() >= (optimizeAckTimestamp + optimizeAcknowledgeTimeOut))) { 948 MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE); 949 if (ack != null) { 950 deliveredMessages.clear(); 951 ackCounter = 0; 952 session.sendAck(ack); 953 optimizeAckTimestamp = System.currentTimeMillis(); 954 } 955 // AMQ-3956 - as further optimization send 956 // ack for expired msgs when there are any. 957 // This resets the deliveredCounter to 0 so that 958 // we won't sent standard acks with every msg just 959 // because the deliveredCounter just below 960 // 0.5 * prefetch as used in ackLater() 961 if (pendingAck != null && deliveredCounter > 0) { 962 session.sendAck(pendingAck); 963 pendingAck = null; 964 deliveredCounter = 0; 965 } 966 } 967 } else { 968 MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE); 969 if (ack!=null) { 970 deliveredMessages.clear(); 971 session.sendAck(ack); 972 } 973 } 974 } 975 } 976 deliveryingAcknowledgements.set(false); 977 } 978 } else if (isAutoAcknowledgeBatch()) { 979 ackLater(md, MessageAck.STANDARD_ACK_TYPE); 980 } else if (session.isClientAcknowledge()||session.isIndividualAcknowledge()) { 981 boolean messageUnackedByConsumer = false; 982 synchronized (deliveredMessages) { 983 messageUnackedByConsumer = deliveredMessages.contains(md); 984 } 985 if (messageUnackedByConsumer) { 986 ackLater(md, MessageAck.DELIVERED_ACK_TYPE); 987 } 988 } 989 else { 990 throw new IllegalStateException("Invalid session state."); 991 } 992 } 993 } 994 995 /** 996 * Creates a MessageAck for all messages contained in deliveredMessages. 997 * Caller should hold the lock for deliveredMessages. 998 * 999 * @param type Ack-Type (i.e. MessageAck.STANDARD_ACK_TYPE) 1000 * @return <code>null</code> if nothing to ack. 1001 */ 1002 private MessageAck makeAckForAllDeliveredMessages(byte type) { 1003 synchronized (deliveredMessages) { 1004 if (deliveredMessages.isEmpty()) 1005 return null; 1006 1007 MessageDispatch md = deliveredMessages.getFirst(); 1008 MessageAck ack = new MessageAck(md, type, deliveredMessages.size()); 1009 ack.setFirstMessageId(deliveredMessages.getLast().getMessage().getMessageId()); 1010 return ack; 1011 } 1012 } 1013 1014 private void ackLater(MessageDispatch md, byte ackType) throws JMSException { 1015 1016 // Don't acknowledge now, but we may need to let the broker know the 1017 // consumer got the message to expand the pre-fetch window 1018 if (session.getTransacted()) { 1019 registerSync(); 1020 } 1021 1022 deliveredCounter++; 1023 1024 MessageAck oldPendingAck = pendingAck; 1025 pendingAck = new MessageAck(md, ackType, deliveredCounter); 1026 pendingAck.setTransactionId(session.getTransactionContext().getTransactionId()); 1027 if( oldPendingAck==null ) { 1028 pendingAck.setFirstMessageId(pendingAck.getLastMessageId()); 1029 } else if ( oldPendingAck.getAckType() == pendingAck.getAckType() ) { 1030 pendingAck.setFirstMessageId(oldPendingAck.getFirstMessageId()); 1031 } else { 1032 // old pending ack being superseded by ack of another type, if is is not a delivered 1033 // ack and hence important, send it now so it is not lost. 1034 if ( !oldPendingAck.isDeliveredAck()) { 1035 if (LOG.isDebugEnabled()) { 1036 LOG.debug("Sending old pending ack " + oldPendingAck + ", new pending: " + pendingAck); 1037 } 1038 session.sendAck(oldPendingAck); 1039 } else { 1040 if (LOG.isDebugEnabled()) { 1041 LOG.debug("dropping old pending ack " + oldPendingAck + ", new pending: " + pendingAck); 1042 } 1043 } 1044 } 1045 // AMQ-3956 evaluate both expired and normal msgs as 1046 // otherwise consumer may get stalled 1047 if ((0.5 * info.getPrefetchSize()) <= (deliveredCounter + ackCounter - additionalWindowSize)) { 1048 if (LOG.isDebugEnabled()) { 1049 LOG.debug("ackLater: sending: " + pendingAck); 1050 } 1051 session.sendAck(pendingAck); 1052 pendingAck=null; 1053 deliveredCounter = 0; 1054 additionalWindowSize = 0; 1055 } 1056 } 1057 1058 private void registerSync() throws JMSException { 1059 session.doStartTransaction(); 1060 if (!synchronizationRegistered) { 1061 synchronizationRegistered = true; 1062 session.getTransactionContext().addSynchronization(new Synchronization() { 1063 @Override 1064 public void beforeEnd() throws Exception { 1065 if (transactedIndividualAck) { 1066 clearDeliveredList(); 1067 waitForRedeliveries(); 1068 synchronized(deliveredMessages) { 1069 rollbackOnFailedRecoveryRedelivery(); 1070 } 1071 } else { 1072 acknowledge(); 1073 } 1074 synchronizationRegistered = false; 1075 } 1076 1077 @Override 1078 public void afterCommit() throws Exception { 1079 commit(); 1080 synchronizationRegistered = false; 1081 } 1082 1083 @Override 1084 public void afterRollback() throws Exception { 1085 rollback(); 1086 synchronizationRegistered = false; 1087 } 1088 }); 1089 } 1090 } 1091 1092 /** 1093 * Acknowledge all the messages that have been delivered to the client up to 1094 * this point. 1095 * 1096 * @throws JMSException 1097 */ 1098 public void acknowledge() throws JMSException { 1099 clearDeliveredList(); 1100 waitForRedeliveries(); 1101 synchronized(deliveredMessages) { 1102 // Acknowledge all messages so far. 1103 MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE); 1104 if (ack == null) 1105 return; // no msgs 1106 1107 if (session.getTransacted()) { 1108 rollbackOnFailedRecoveryRedelivery(); 1109 session.doStartTransaction(); 1110 ack.setTransactionId(session.getTransactionContext().getTransactionId()); 1111 } 1112 1113 pendingAck = null; 1114 session.sendAck(ack); 1115 1116 // Adjust the counters 1117 deliveredCounter = Math.max(0, deliveredCounter - deliveredMessages.size()); 1118 additionalWindowSize = Math.max(0, additionalWindowSize - deliveredMessages.size()); 1119 1120 if (!session.getTransacted()) { 1121 deliveredMessages.clear(); 1122 } 1123 } 1124 } 1125 1126 private void waitForRedeliveries() { 1127 if (failoverRedeliveryWaitPeriod > 0 && previouslyDeliveredMessages != null) { 1128 long expiry = System.currentTimeMillis() + failoverRedeliveryWaitPeriod; 1129 int numberNotReplayed; 1130 do { 1131 numberNotReplayed = 0; 1132 synchronized(deliveredMessages) { 1133 if (previouslyDeliveredMessages != null) { 1134 for (Entry<MessageId, Boolean> entry: previouslyDeliveredMessages.entrySet()) { 1135 if (!entry.getValue()) { 1136 numberNotReplayed++; 1137 } 1138 } 1139 } 1140 } 1141 if (numberNotReplayed > 0) { 1142 LOG.info("waiting for redelivery of " + numberNotReplayed + " in transaction: " 1143 + previouslyDeliveredMessages.transactionId + ", to consumer :" + this.getConsumerId()); 1144 try { 1145 Thread.sleep(Math.max(500, failoverRedeliveryWaitPeriod/4)); 1146 } catch (InterruptedException outOfhere) { 1147 break; 1148 } 1149 } 1150 } while (numberNotReplayed > 0 && expiry < System.currentTimeMillis()); 1151 } 1152 } 1153 1154 /* 1155 * called with deliveredMessages locked 1156 */ 1157 private void rollbackOnFailedRecoveryRedelivery() throws JMSException { 1158 if (previouslyDeliveredMessages != null) { 1159 // if any previously delivered messages was not re-delivered, transaction is invalid and must rollback 1160 // as messages have been dispatched else where. 1161 int numberNotReplayed = 0; 1162 for (Entry<MessageId, Boolean> entry: previouslyDeliveredMessages.entrySet()) { 1163 if (!entry.getValue()) { 1164 numberNotReplayed++; 1165 if (LOG.isDebugEnabled()) { 1166 LOG.debug("previously delivered message has not been replayed in transaction: " 1167 + previouslyDeliveredMessages.transactionId 1168 + " , messageId: " + entry.getKey()); 1169 } 1170 } 1171 } 1172 if (numberNotReplayed > 0) { 1173 String message = "rolling back transaction (" 1174 + previouslyDeliveredMessages.transactionId + ") post failover recovery. " + numberNotReplayed 1175 + " previously delivered message(s) not replayed to consumer: " + this.getConsumerId(); 1176 LOG.warn(message); 1177 throw new TransactionRolledBackException(message); 1178 } 1179 } 1180 } 1181 1182 void acknowledge(MessageDispatch md) throws JMSException { 1183 acknowledge(md, MessageAck.INDIVIDUAL_ACK_TYPE); 1184 } 1185 1186 void acknowledge(MessageDispatch md, byte ackType) throws JMSException { 1187 MessageAck ack = new MessageAck(md, ackType, 1); 1188 session.sendAck(ack); 1189 synchronized(deliveredMessages){ 1190 deliveredMessages.remove(md); 1191 } 1192 } 1193 1194 public void commit() throws JMSException { 1195 synchronized (deliveredMessages) { 1196 deliveredMessages.clear(); 1197 clearPreviouslyDelivered(); 1198 } 1199 redeliveryDelay = 0; 1200 } 1201 1202 public void rollback() throws JMSException { 1203 clearDeliveredList(); 1204 synchronized (unconsumedMessages.getMutex()) { 1205 if (optimizeAcknowledge) { 1206 // remove messages read but not acked at the broker yet through 1207 // optimizeAcknowledge 1208 if (!this.info.isBrowser()) { 1209 synchronized(deliveredMessages) { 1210 for (int i = 0; (i < deliveredMessages.size()) && (i < ackCounter); i++) { 1211 // ensure we don't filter this as a duplicate 1212 MessageDispatch md = deliveredMessages.removeLast(); 1213 session.connection.rollbackDuplicate(this, md.getMessage()); 1214 } 1215 } 1216 } 1217 } 1218 synchronized(deliveredMessages) { 1219 rollbackPreviouslyDeliveredAndNotRedelivered(); 1220 if (deliveredMessages.isEmpty()) { 1221 return; 1222 } 1223 1224 // use initial delay for first redelivery 1225 MessageDispatch lastMd = deliveredMessages.getFirst(); 1226 final int currentRedeliveryCount = lastMd.getMessage().getRedeliveryCounter(); 1227 if (currentRedeliveryCount > 0) { 1228 redeliveryDelay = redeliveryPolicy.getNextRedeliveryDelay(redeliveryDelay); 1229 } else { 1230 redeliveryDelay = redeliveryPolicy.getInitialRedeliveryDelay(); 1231 } 1232 MessageId firstMsgId = deliveredMessages.getLast().getMessage().getMessageId(); 1233 1234 for (Iterator<MessageDispatch> iter = deliveredMessages.iterator(); iter.hasNext();) { 1235 MessageDispatch md = iter.next(); 1236 md.getMessage().onMessageRolledBack(); 1237 // ensure we don't filter this as a duplicate 1238 session.connection.rollbackDuplicate(this, md.getMessage()); 1239 } 1240 1241 if (redeliveryPolicy.getMaximumRedeliveries() != RedeliveryPolicy.NO_MAXIMUM_REDELIVERIES 1242 && lastMd.getMessage().getRedeliveryCounter() > redeliveryPolicy.getMaximumRedeliveries()) { 1243 // We need to NACK the messages so that they get sent to the 1244 // DLQ. 1245 // Acknowledge the last message. 1246 1247 MessageAck ack = new MessageAck(lastMd, MessageAck.POSION_ACK_TYPE, deliveredMessages.size()); 1248 ack.setFirstMessageId(firstMsgId); 1249 ack.setPoisonCause(new Throwable("Exceeded redelivery policy limit:" + redeliveryPolicy 1250 + ", cause:" + lastMd.getRollbackCause(), lastMd.getRollbackCause())); 1251 session.sendAck(ack,true); 1252 // Adjust the window size. 1253 additionalWindowSize = Math.max(0, additionalWindowSize - deliveredMessages.size()); 1254 redeliveryDelay = 0; 1255 1256 deliveredCounter -= deliveredMessages.size(); 1257 deliveredMessages.clear(); 1258 1259 } else { 1260 1261 // only redelivery_ack after first delivery 1262 if (currentRedeliveryCount > 0) { 1263 MessageAck ack = new MessageAck(lastMd, MessageAck.REDELIVERED_ACK_TYPE, deliveredMessages.size()); 1264 ack.setFirstMessageId(firstMsgId); 1265 session.sendAck(ack,true); 1266 } 1267 1268 // stop the delivery of messages. 1269 if (nonBlockingRedelivery) { 1270 if (!unconsumedMessages.isClosed()) { 1271 1272 final LinkedList<MessageDispatch> pendingRedeliveries = 1273 new LinkedList<MessageDispatch>(deliveredMessages); 1274 1275 Collections.reverse(pendingRedeliveries); 1276 1277 deliveredCounter -= deliveredMessages.size(); 1278 deliveredMessages.clear(); 1279 1280 // Start up the delivery again a little later. 1281 session.getScheduler().executeAfterDelay(new Runnable() { 1282 @Override 1283 public void run() { 1284 try { 1285 if (!unconsumedMessages.isClosed()) { 1286 for(MessageDispatch dispatch : pendingRedeliveries) { 1287 session.dispatch(dispatch); 1288 } 1289 } 1290 } catch (Exception e) { 1291 session.connection.onAsyncException(e); 1292 } 1293 } 1294 }, redeliveryDelay); 1295 } 1296 1297 } else { 1298 unconsumedMessages.stop(); 1299 1300 for (MessageDispatch md : deliveredMessages) { 1301 unconsumedMessages.enqueueFirst(md); 1302 } 1303 1304 deliveredCounter -= deliveredMessages.size(); 1305 deliveredMessages.clear(); 1306 1307 if (redeliveryDelay > 0 && !unconsumedMessages.isClosed()) { 1308 // Start up the delivery again a little later. 1309 session.getScheduler().executeAfterDelay(new Runnable() { 1310 @Override 1311 public void run() { 1312 try { 1313 if (started.get()) { 1314 start(); 1315 } 1316 } catch (JMSException e) { 1317 session.connection.onAsyncException(e); 1318 } 1319 } 1320 }, redeliveryDelay); 1321 } else { 1322 start(); 1323 } 1324 } 1325 } 1326 } 1327 } 1328 if (messageListener.get() != null) { 1329 session.redispatch(this, unconsumedMessages); 1330 } 1331 } 1332 1333 /* 1334 * called with unconsumedMessages && deliveredMessages locked 1335 * remove any message not re-delivered as they can't be replayed to this 1336 * consumer on rollback 1337 */ 1338 private void rollbackPreviouslyDeliveredAndNotRedelivered() { 1339 if (previouslyDeliveredMessages != null) { 1340 for (Entry<MessageId, Boolean> entry: previouslyDeliveredMessages.entrySet()) { 1341 if (!entry.getValue()) { 1342 if (LOG.isTraceEnabled()) { 1343 LOG.trace("rollback non redelivered: " + entry.getKey()); 1344 } 1345 removeFromDeliveredMessages(entry.getKey()); 1346 } 1347 } 1348 clearPreviouslyDelivered(); 1349 } 1350 } 1351 1352 /* 1353 * called with deliveredMessages locked 1354 */ 1355 private void removeFromDeliveredMessages(MessageId key) { 1356 Iterator<MessageDispatch> iterator = deliveredMessages.iterator(); 1357 while (iterator.hasNext()) { 1358 MessageDispatch candidate = iterator.next(); 1359 if (key.equals(candidate.getMessage().getMessageId())) { 1360 session.connection.rollbackDuplicate(this, candidate.getMessage()); 1361 iterator.remove(); 1362 break; 1363 } 1364 } 1365 } 1366 1367 /* 1368 * called with deliveredMessages locked 1369 */ 1370 private void clearPreviouslyDelivered() { 1371 if (previouslyDeliveredMessages != null) { 1372 previouslyDeliveredMessages.clear(); 1373 previouslyDeliveredMessages = null; 1374 } 1375 } 1376 1377 @Override 1378 public void dispatch(MessageDispatch md) { 1379 MessageListener listener = this.messageListener.get(); 1380 try { 1381 clearMessagesInProgress(); 1382 clearDeliveredList(); 1383 synchronized (unconsumedMessages.getMutex()) { 1384 if (!unconsumedMessages.isClosed()) { 1385 if (this.info.isBrowser() || !session.connection.isDuplicate(this, md.getMessage())) { 1386 if (listener != null && unconsumedMessages.isRunning()) { 1387 if (redeliveryExceeded(md)) { 1388 posionAck(md, "dispatch to " + getConsumerId() + " exceeds redelivery policy limit:" + redeliveryPolicy); 1389 return; 1390 } 1391 ActiveMQMessage message = createActiveMQMessage(md); 1392 beforeMessageIsConsumed(md); 1393 try { 1394 boolean expired = isConsumerExpiryCheckEnabled() && message.isExpired(); 1395 if (!expired) { 1396 listener.onMessage(message); 1397 } 1398 afterMessageIsConsumed(md, expired); 1399 } catch (RuntimeException e) { 1400 LOG.error(getConsumerId() + " Exception while processing message: " + md.getMessage().getMessageId(), e); 1401 if (isAutoAcknowledgeBatch() || isAutoAcknowledgeEach() || session.isIndividualAcknowledge()) { 1402 // schedual redelivery and possible dlq processing 1403 md.setRollbackCause(e); 1404 rollback(); 1405 } else { 1406 // Transacted or Client ack: Deliver the 1407 // next message. 1408 afterMessageIsConsumed(md, false); 1409 } 1410 } 1411 } else { 1412 if (!unconsumedMessages.isRunning()) { 1413 // delayed redelivery, ensure it can be re delivered 1414 session.connection.rollbackDuplicate(this, md.getMessage()); 1415 } 1416 if (!(md.getMessage() != null && md.getMessage().isExpired())) { 1417 unconsumedMessages.enqueue(md); 1418 if (availableListener != null) { 1419 availableListener.onMessageAvailable(this); 1420 } 1421 } else { 1422 beforeMessageIsConsumed(md); 1423 afterMessageIsConsumed(md, false); 1424 } 1425 } 1426 } else { 1427 // deal with duplicate delivery 1428 ConsumerId consumerWithPendingTransaction; 1429 if (redeliveryExpectedInCurrentTransaction(md, true)) { 1430 if (LOG.isDebugEnabled()) { 1431 LOG.debug("{} tracking transacted redelivery {}", getConsumerId(), md.getMessage()); 1432 } 1433 if (transactedIndividualAck) { 1434 immediateIndividualTransactedAck(md); 1435 } else { 1436 session.sendAck(new MessageAck(md, MessageAck.DELIVERED_ACK_TYPE, 1)); 1437 } 1438 } else if ((consumerWithPendingTransaction = redeliveryPendingInCompetingTransaction(md)) != null) { 1439 LOG.warn("{} delivering duplicate {}, pending transaction completion on {} will rollback", getConsumerId(), md.getMessage(), consumerWithPendingTransaction); 1440 session.getConnection().rollbackDuplicate(this, md.getMessage()); 1441 dispatch(md); 1442 } else { 1443 LOG.warn("{} suppressing duplicate delivery on connection, poison acking: {}", getConsumerId(), md); 1444 posionAck(md, "Suppressing duplicate delivery on connection, consumer " + getConsumerId()); 1445 } 1446 } 1447 } 1448 } 1449 if (++dispatchedCount % 1000 == 0) { 1450 dispatchedCount = 0; 1451 Thread.yield(); 1452 } 1453 } catch (Exception e) { 1454 session.connection.onClientInternalException(e); 1455 } 1456 } 1457 1458 private boolean redeliveryExpectedInCurrentTransaction(MessageDispatch md, boolean markReceipt) { 1459 if (session.isTransacted()) { 1460 synchronized (deliveredMessages) { 1461 if (previouslyDeliveredMessages != null) { 1462 if (previouslyDeliveredMessages.containsKey(md.getMessage().getMessageId())) { 1463 if (markReceipt) { 1464 previouslyDeliveredMessages.put(md.getMessage().getMessageId(), true); 1465 } 1466 return true; 1467 } 1468 } 1469 } 1470 } 1471 return false; 1472 } 1473 1474 private ConsumerId redeliveryPendingInCompetingTransaction(MessageDispatch md) { 1475 for (ActiveMQSession activeMQSession: session.connection.getSessions()) { 1476 for (ActiveMQMessageConsumer activeMQMessageConsumer : activeMQSession.consumers) { 1477 if (activeMQMessageConsumer.redeliveryExpectedInCurrentTransaction(md, false)) { 1478 return activeMQMessageConsumer.getConsumerId(); 1479 } 1480 } 1481 } 1482 return null; 1483 } 1484 1485 // async (on next call) clear or track delivered as they may be flagged as duplicates if they arrive again 1486 private void clearDeliveredList() { 1487 if (clearDeliveredList) { 1488 synchronized (deliveredMessages) { 1489 if (clearDeliveredList) { 1490 if (!deliveredMessages.isEmpty()) { 1491 if (session.isTransacted()) { 1492 1493 if (previouslyDeliveredMessages == null) { 1494 previouslyDeliveredMessages = new PreviouslyDeliveredMap<MessageId, Boolean>(session.getTransactionContext().getTransactionId()); 1495 } 1496 for (MessageDispatch delivered : deliveredMessages) { 1497 previouslyDeliveredMessages.put(delivered.getMessage().getMessageId(), false); 1498 } 1499 if (LOG.isDebugEnabled()) { 1500 LOG.debug(getConsumerId() + " tracking existing transacted " + previouslyDeliveredMessages.transactionId + 1501 " delivered list (" + deliveredMessages.size() + ") on transport interrupt"); 1502 } 1503 } else { 1504 if (session.isClientAcknowledge()) { 1505 if (LOG.isDebugEnabled()) { 1506 LOG.debug(getConsumerId() + " rolling back delivered list (" + deliveredMessages.size() + ") on transport interrupt"); 1507 } 1508 // allow redelivery 1509 if (!this.info.isBrowser()) { 1510 for (MessageDispatch md: deliveredMessages) { 1511 this.session.connection.rollbackDuplicate(this, md.getMessage()); 1512 } 1513 } 1514 } 1515 if (LOG.isDebugEnabled()) { 1516 LOG.debug(getConsumerId() + " clearing delivered list (" + deliveredMessages.size() + ") on transport interrupt"); 1517 } 1518 deliveredMessages.clear(); 1519 pendingAck = null; 1520 } 1521 } 1522 clearDeliveredList = false; 1523 } 1524 } 1525 } 1526 } 1527 1528 public int getMessageSize() { 1529 return unconsumedMessages.size(); 1530 } 1531 1532 public void start() throws JMSException { 1533 if (unconsumedMessages.isClosed()) { 1534 return; 1535 } 1536 started.set(true); 1537 unconsumedMessages.start(); 1538 session.executor.wakeup(); 1539 } 1540 1541 public void stop() { 1542 started.set(false); 1543 unconsumedMessages.stop(); 1544 } 1545 1546 @Override 1547 public String toString() { 1548 return "ActiveMQMessageConsumer { value=" + info.getConsumerId() + ", started=" + started.get() 1549 + " }"; 1550 } 1551 1552 /** 1553 * Delivers a message to the message listener. 1554 * 1555 * @return 1556 * @throws JMSException 1557 */ 1558 public boolean iterate() { 1559 MessageListener listener = this.messageListener.get(); 1560 if (listener != null) { 1561 MessageDispatch md = unconsumedMessages.dequeueNoWait(); 1562 if (md != null) { 1563 dispatch(md); 1564 return true; 1565 } 1566 } 1567 return false; 1568 } 1569 1570 public boolean isInUse(ActiveMQTempDestination destination) { 1571 return info.getDestination().equals(destination); 1572 } 1573 1574 public long getLastDeliveredSequenceId() { 1575 return lastDeliveredSequenceId; 1576 } 1577 1578 public IOException getFailureError() { 1579 return failureError; 1580 } 1581 1582 public void setFailureError(IOException failureError) { 1583 this.failureError = failureError; 1584 } 1585 1586 /** 1587 * @return the optimizedAckScheduledAckInterval 1588 */ 1589 public long getOptimizedAckScheduledAckInterval() { 1590 return optimizedAckScheduledAckInterval; 1591 } 1592 1593 /** 1594 * @param optimizedAckScheduledAckInterval the optimizedAckScheduledAckInterval to set 1595 */ 1596 public void setOptimizedAckScheduledAckInterval(long optimizedAckScheduledAckInterval) throws JMSException { 1597 this.optimizedAckScheduledAckInterval = optimizedAckScheduledAckInterval; 1598 1599 if (this.optimizedAckTask != null) { 1600 try { 1601 this.session.connection.getScheduler().cancel(optimizedAckTask); 1602 } catch (JMSException e) { 1603 LOG.debug("Caught exception while cancelling old optimized ack task", e); 1604 throw e; 1605 } 1606 this.optimizedAckTask = null; 1607 } 1608 1609 // Should we periodically send out all outstanding acks. 1610 if (this.optimizeAcknowledge && this.optimizedAckScheduledAckInterval > 0) { 1611 this.optimizedAckTask = new Runnable() { 1612 1613 @Override 1614 public void run() { 1615 try { 1616 if (optimizeAcknowledge && !unconsumedMessages.isClosed()) { 1617 if (LOG.isInfoEnabled()) { 1618 LOG.info("Consumer:{} is performing scheduled delivery of outstanding optimized Acks", info.getConsumerId()); 1619 } 1620 deliverAcks(); 1621 } 1622 } catch (Exception e) { 1623 LOG.debug("Optimized Ack Task caught exception during ack", e); 1624 } 1625 } 1626 }; 1627 1628 try { 1629 this.session.connection.getScheduler().executePeriodically(optimizedAckTask, optimizedAckScheduledAckInterval); 1630 } catch (JMSException e) { 1631 LOG.debug("Caught exception while scheduling new optimized ack task", e); 1632 throw e; 1633 } 1634 } 1635 } 1636 1637 public boolean hasMessageListener() { 1638 return messageListener.get() != null; 1639 } 1640 1641 public boolean isConsumerExpiryCheckEnabled() { 1642 return consumerExpiryCheckEnabled; 1643 } 1644 1645 public void setConsumerExpiryCheckEnabled(boolean consumerExpiryCheckEnabled) { 1646 this.consumerExpiryCheckEnabled = consumerExpiryCheckEnabled; 1647 } 1648}