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