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