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