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