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.broker.region; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Iterator; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.concurrent.CountDownLatch; 025import java.util.concurrent.TimeUnit; 026 027import javax.jms.JMSException; 028 029import org.apache.activemq.broker.Broker; 030import org.apache.activemq.broker.ConnectionContext; 031import org.apache.activemq.broker.region.cursors.PendingMessageCursor; 032import org.apache.activemq.broker.region.cursors.VMPendingMessageCursor; 033import org.apache.activemq.command.ConsumerControl; 034import org.apache.activemq.command.ConsumerInfo; 035import org.apache.activemq.command.Message; 036import org.apache.activemq.command.MessageAck; 037import org.apache.activemq.command.MessageDispatch; 038import org.apache.activemq.command.MessageDispatchNotification; 039import org.apache.activemq.command.MessageId; 040import org.apache.activemq.command.MessagePull; 041import org.apache.activemq.command.Response; 042import org.apache.activemq.thread.Scheduler; 043import org.apache.activemq.transaction.Synchronization; 044import org.apache.activemq.transport.TransmitCallback; 045import org.apache.activemq.usage.SystemUsage; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048 049/** 050 * A subscription that honors the pre-fetch option of the ConsumerInfo. 051 */ 052public abstract class PrefetchSubscription extends AbstractSubscription { 053 054 private static final Logger LOG = LoggerFactory.getLogger(PrefetchSubscription.class); 055 protected final Scheduler scheduler; 056 057 protected PendingMessageCursor pending; 058 protected final List<MessageReference> dispatched = new ArrayList<MessageReference>(); 059 private int maxProducersToAudit=32; 060 private int maxAuditDepth=2048; 061 protected final SystemUsage usageManager; 062 protected final Object pendingLock = new Object(); 063 protected final Object dispatchLock = new Object(); 064 private final CountDownLatch okForAckAsDispatchDone = new CountDownLatch(1); 065 066 public PrefetchSubscription(Broker broker, SystemUsage usageManager, ConnectionContext context, ConsumerInfo info, PendingMessageCursor cursor) throws JMSException { 067 super(broker,context, info); 068 this.usageManager=usageManager; 069 pending = cursor; 070 try { 071 pending.start(); 072 } catch (Exception e) { 073 throw new JMSException(e.getMessage()); 074 } 075 this.scheduler = broker.getScheduler(); 076 } 077 078 public PrefetchSubscription(Broker broker,SystemUsage usageManager, ConnectionContext context, ConsumerInfo info) throws JMSException { 079 this(broker,usageManager,context, info, new VMPendingMessageCursor(false)); 080 } 081 082 /** 083 * Allows a message to be pulled on demand by a client 084 */ 085 @Override 086 public Response pullMessage(ConnectionContext context, final MessagePull pull) throws Exception { 087 // The slave should not deliver pull messages. 088 // TODO: when the slave becomes a master, He should send a NULL message to all the 089 // consumers to 'wake them up' in case they were waiting for a message. 090 if (getPrefetchSize() == 0) { 091 prefetchExtension.set(pull.getQuantity()); 092 final long dispatchCounterBeforePull = getSubscriptionStatistics().getDispatched().getCount(); 093 094 // Have the destination push us some messages. 095 for (Destination dest : destinations) { 096 dest.iterate(); 097 } 098 dispatchPending(); 099 100 synchronized(this) { 101 // If there was nothing dispatched.. we may need to setup a timeout. 102 if (dispatchCounterBeforePull == getSubscriptionStatistics().getDispatched().getCount() || pull.isAlwaysSignalDone()) { 103 // immediate timeout used by receiveNoWait() 104 if (pull.getTimeout() == -1) { 105 // Null message indicates the pull is done or did not have pending. 106 prefetchExtension.set(1); 107 add(QueueMessageReference.NULL_MESSAGE); 108 dispatchPending(); 109 } 110 if (pull.getTimeout() > 0) { 111 scheduler.executeAfterDelay(new Runnable() { 112 @Override 113 public void run() { 114 pullTimeout(dispatchCounterBeforePull, pull.isAlwaysSignalDone()); 115 } 116 }, pull.getTimeout()); 117 } 118 } 119 } 120 } 121 return null; 122 } 123 124 /** 125 * Occurs when a pull times out. If nothing has been dispatched since the 126 * timeout was setup, then send the NULL message. 127 */ 128 final void pullTimeout(long dispatchCounterBeforePull, boolean alwaysSignalDone) { 129 synchronized (pendingLock) { 130 if (dispatchCounterBeforePull == getSubscriptionStatistics().getDispatched().getCount() || alwaysSignalDone) { 131 try { 132 prefetchExtension.set(1); 133 add(QueueMessageReference.NULL_MESSAGE); 134 dispatchPending(); 135 } catch (Exception e) { 136 context.getConnection().serviceException(e); 137 } finally { 138 prefetchExtension.set(0); 139 } 140 } 141 } 142 } 143 144 @Override 145 public void add(MessageReference node) throws Exception { 146 synchronized (pendingLock) { 147 // The destination may have just been removed... 148 if (!destinations.contains(node.getRegionDestination()) && node != QueueMessageReference.NULL_MESSAGE) { 149 // perhaps we should inform the caller that we are no longer valid to dispatch to? 150 return; 151 } 152 153 // Don't increment for the pullTimeout control message. 154 if (!node.equals(QueueMessageReference.NULL_MESSAGE)) { 155 getSubscriptionStatistics().getEnqueues().increment(); 156 } 157 pending.addMessageLast(node); 158 } 159 dispatchPending(); 160 } 161 162 @Override 163 public void processMessageDispatchNotification(MessageDispatchNotification mdn) throws Exception { 164 synchronized(pendingLock) { 165 try { 166 pending.reset(); 167 while (pending.hasNext()) { 168 MessageReference node = pending.next(); 169 node.decrementReferenceCount(); 170 if (node.getMessageId().equals(mdn.getMessageId())) { 171 // Synchronize between dispatched list and removal of messages from pending list 172 // related to remove subscription action 173 synchronized(dispatchLock) { 174 pending.remove(); 175 createMessageDispatch(node, node.getMessage()); 176 dispatched.add(node); 177 onDispatch(node, node.getMessage()); 178 } 179 return; 180 } 181 } 182 } finally { 183 pending.release(); 184 } 185 } 186 throw new JMSException( 187 "Slave broker out of sync with master: Dispatched message (" 188 + mdn.getMessageId() + ") was not in the pending list for " 189 + mdn.getConsumerId() + " on " + mdn.getDestination().getPhysicalName()); 190 } 191 192 @Override 193 public final void acknowledge(final ConnectionContext context,final MessageAck ack) throws Exception { 194 // Handle the standard acknowledgment case. 195 boolean callDispatchMatched = false; 196 Destination destination = null; 197 198 if (!okForAckAsDispatchDone.await(0l, TimeUnit.MILLISECONDS)) { 199 // suppress unexpected ack exception in this expected case 200 LOG.warn("Ignoring ack received before dispatch; result of failover with an outstanding ack. Acked messages will be replayed if present on this broker. Ignored ack: {}", ack); 201 return; 202 } 203 204 LOG.trace("ack: {}", ack); 205 206 synchronized(dispatchLock) { 207 if (ack.isStandardAck()) { 208 // First check if the ack matches the dispatched. When using failover this might 209 // not be the case. We don't ever want to ack the wrong messages. 210 assertAckMatchesDispatched(ack); 211 212 // Acknowledge all dispatched messages up till the message id of 213 // the acknowledgment. 214 boolean inAckRange = false; 215 List<MessageReference> removeList = new ArrayList<MessageReference>(); 216 for (final MessageReference node : dispatched) { 217 MessageId messageId = node.getMessageId(); 218 if (ack.getFirstMessageId() == null 219 || ack.getFirstMessageId().equals(messageId)) { 220 inAckRange = true; 221 } 222 if (inAckRange) { 223 // Don't remove the nodes until we are committed. 224 if (!context.isInTransaction()) { 225 getSubscriptionStatistics().getDequeues().increment(); 226 removeList.add(node); 227 contractPrefetchExtension(1); 228 } else { 229 registerRemoveSync(context, node); 230 } 231 acknowledge(context, ack, node); 232 if (ack.getLastMessageId().equals(messageId)) { 233 destination = (Destination) node.getRegionDestination(); 234 callDispatchMatched = true; 235 break; 236 } 237 } 238 } 239 for (final MessageReference node : removeList) { 240 dispatched.remove(node); 241 decrementPrefetchCounter(node); 242 } 243 // this only happens after a reconnect - get an ack which is not 244 // valid 245 if (!callDispatchMatched) { 246 LOG.warn("Could not correlate acknowledgment with dispatched message: {}", ack); 247 } 248 } else if (ack.isIndividualAck()) { 249 // Message was delivered and acknowledge - but only delete the 250 // individual message 251 for (final MessageReference node : dispatched) { 252 MessageId messageId = node.getMessageId(); 253 if (ack.getLastMessageId().equals(messageId)) { 254 // Don't remove the nodes until we are committed - immediateAck option 255 if (!context.isInTransaction()) { 256 getSubscriptionStatistics().getDequeues().increment(); 257 dispatched.remove(node); 258 decrementPrefetchCounter(node); 259 contractPrefetchExtension(1); 260 } else { 261 registerRemoveSync(context, node); 262 expandPrefetchExtension(1); 263 } 264 acknowledge(context, ack, node); 265 destination = (Destination) node.getRegionDestination(); 266 callDispatchMatched = true; 267 break; 268 } 269 } 270 } else if (ack.isDeliveredAck()) { 271 // Message was delivered but not acknowledged: update pre-fetch 272 // counters. 273 int index = 0; 274 for (Iterator<MessageReference> iter = dispatched.iterator(); iter.hasNext(); index++) { 275 final MessageReference node = iter.next(); 276 Destination nodeDest = (Destination) node.getRegionDestination(); 277 if (ack.getLastMessageId().equals(node.getMessageId())) { 278 expandPrefetchExtension(ack.getMessageCount()); 279 destination = nodeDest; 280 callDispatchMatched = true; 281 break; 282 } 283 } 284 if (!callDispatchMatched) { 285 throw new JMSException( 286 "Could not correlate acknowledgment with dispatched message: " 287 + ack); 288 } 289 } else if (ack.isExpiredAck()) { 290 // Message was expired 291 int index = 0; 292 boolean inAckRange = false; 293 for (Iterator<MessageReference> iter = dispatched.iterator(); iter.hasNext(); index++) { 294 final MessageReference node = iter.next(); 295 Destination nodeDest = (Destination) node.getRegionDestination(); 296 MessageId messageId = node.getMessageId(); 297 if (ack.getFirstMessageId() == null || ack.getFirstMessageId().equals(messageId)) { 298 inAckRange = true; 299 } 300 if (inAckRange) { 301 Destination regionDestination = nodeDest; 302 if (broker.isExpired(node)) { 303 regionDestination.messageExpired(context, this, node); 304 } 305 iter.remove(); 306 decrementPrefetchCounter(node); 307 308 if (ack.getLastMessageId().equals(messageId)) { 309 contractPrefetchExtension(1); 310 destination = (Destination) node.getRegionDestination(); 311 callDispatchMatched = true; 312 break; 313 } 314 } 315 } 316 if (!callDispatchMatched) { 317 throw new JMSException( 318 "Could not correlate expiration acknowledgment with dispatched message: " 319 + ack); 320 } 321 } else if (ack.isRedeliveredAck()) { 322 // Message was re-delivered but it was not yet considered to be 323 // a DLQ message. 324 boolean inAckRange = false; 325 for (final MessageReference node : dispatched) { 326 MessageId messageId = node.getMessageId(); 327 if (ack.getFirstMessageId() == null 328 || ack.getFirstMessageId().equals(messageId)) { 329 inAckRange = true; 330 } 331 if (inAckRange) { 332 if (ack.getLastMessageId().equals(messageId)) { 333 destination = (Destination) node.getRegionDestination(); 334 callDispatchMatched = true; 335 break; 336 } 337 } 338 } 339 if (!callDispatchMatched) { 340 throw new JMSException( 341 "Could not correlate acknowledgment with dispatched message: " 342 + ack); 343 } 344 } else if (ack.isPoisonAck()) { 345 // TODO: what if the message is already in a DLQ??? 346 // Handle the poison ACK case: we need to send the message to a 347 // DLQ 348 if (ack.isInTransaction()) { 349 throw new JMSException("Poison ack cannot be transacted: " 350 + ack); 351 } 352 int index = 0; 353 boolean inAckRange = false; 354 List<MessageReference> removeList = new ArrayList<MessageReference>(); 355 for (final MessageReference node : dispatched) { 356 MessageId messageId = node.getMessageId(); 357 if (ack.getFirstMessageId() == null 358 || ack.getFirstMessageId().equals(messageId)) { 359 inAckRange = true; 360 } 361 if (inAckRange) { 362 sendToDLQ(context, node, ack.getPoisonCause()); 363 Destination nodeDest = (Destination) node.getRegionDestination(); 364 removeList.add(node); 365 getSubscriptionStatistics().getDequeues().increment(); 366 index++; 367 acknowledge(context, ack, node); 368 if (ack.getLastMessageId().equals(messageId)) { 369 contractPrefetchExtension(1); 370 destination = nodeDest; 371 callDispatchMatched = true; 372 break; 373 } 374 } 375 } 376 for (final MessageReference node : removeList) { 377 dispatched.remove(node); 378 decrementPrefetchCounter(node); 379 } 380 if (!callDispatchMatched) { 381 throw new JMSException( 382 "Could not correlate acknowledgment with dispatched message: " 383 + ack); 384 } 385 } 386 } 387 if (callDispatchMatched && destination != null) { 388 destination.wakeup(); 389 dispatchPending(); 390 391 if (pending.isEmpty()) { 392 wakeupDestinationsForDispatch(); 393 } 394 } else { 395 LOG.debug("Acknowledgment out of sync (Normally occurs when failover connection reconnects): {}", ack); 396 } 397 } 398 399 private void registerRemoveSync(ConnectionContext context, final MessageReference node) { 400 // setup a Synchronization to remove nodes from the 401 // dispatched list. 402 context.getTransaction().addSynchronization( 403 new Synchronization() { 404 405 @Override 406 public void afterCommit() 407 throws Exception { 408 Destination nodeDest = (Destination) node.getRegionDestination(); 409 synchronized (dispatchLock) { 410 getSubscriptionStatistics().getDequeues().increment(); 411 if (dispatched.remove(node)) { 412 // if consumer is removed, dispatched will be empty and inflight will 413 // already have been adjusted 414 decrementPrefetchCounter(node); 415 } 416 } 417 contractPrefetchExtension(1); 418 nodeDest.wakeup(); 419 dispatchPending(); 420 } 421 422 @Override 423 public void afterRollback() throws Exception { 424 contractPrefetchExtension(1); 425 } 426 }); 427 } 428 429 /** 430 * Checks an ack versus the contents of the dispatched list. 431 * called with dispatchLock held 432 * @param ack 433 * @throws JMSException if it does not match 434 */ 435 protected void assertAckMatchesDispatched(MessageAck ack) throws JMSException { 436 MessageId firstAckedMsg = ack.getFirstMessageId(); 437 MessageId lastAckedMsg = ack.getLastMessageId(); 438 int checkCount = 0; 439 boolean checkFoundStart = false; 440 boolean checkFoundEnd = false; 441 for (MessageReference node : dispatched) { 442 443 if (firstAckedMsg == null) { 444 checkFoundStart = true; 445 } else if (!checkFoundStart && firstAckedMsg.equals(node.getMessageId())) { 446 checkFoundStart = true; 447 } 448 449 if (checkFoundStart) { 450 checkCount++; 451 } 452 453 if (lastAckedMsg != null && lastAckedMsg.equals(node.getMessageId())) { 454 checkFoundEnd = true; 455 break; 456 } 457 } 458 if (!checkFoundStart && firstAckedMsg != null) 459 throw new JMSException("Unmatched acknowledge: " + ack 460 + "; Could not find Message-ID " + firstAckedMsg 461 + " in dispatched-list (start of ack)"); 462 if (!checkFoundEnd && lastAckedMsg != null) 463 throw new JMSException("Unmatched acknowledge: " + ack 464 + "; Could not find Message-ID " + lastAckedMsg 465 + " in dispatched-list (end of ack)"); 466 if (ack.getMessageCount() != checkCount && !ack.isInTransaction()) { 467 throw new JMSException("Unmatched acknowledge: " + ack 468 + "; Expected message count (" + ack.getMessageCount() 469 + ") differs from count in dispatched-list (" + checkCount 470 + ")"); 471 } 472 } 473 474 /** 475 * 476 * @param context 477 * @param node 478 * @param poisonCause 479 * @throws IOException 480 * @throws Exception 481 */ 482 protected void sendToDLQ(final ConnectionContext context, final MessageReference node, Throwable poisonCause) throws IOException, Exception { 483 broker.getRoot().sendToDeadLetterQueue(context, node, this, poisonCause); 484 } 485 486 @Override 487 public int getInFlightSize() { 488 return dispatched.size(); 489 } 490 491 /** 492 * Used to determine if the broker can dispatch to the consumer. 493 * 494 * @return true if the subscription is full 495 */ 496 @Override 497 public boolean isFull() { 498 return getPrefetchSize() == 0 ? prefetchExtension.get() == 0 : dispatched.size() - prefetchExtension.get() >= info.getPrefetchSize(); 499 } 500 501 /** 502 * @return true when 60% or more room is left for dispatching messages 503 */ 504 @Override 505 public boolean isLowWaterMark() { 506 return (dispatched.size() - prefetchExtension.get()) <= (info.getPrefetchSize() * .4); 507 } 508 509 /** 510 * @return true when 10% or less room is left for dispatching messages 511 */ 512 @Override 513 public boolean isHighWaterMark() { 514 return (dispatched.size() - prefetchExtension.get()) >= (info.getPrefetchSize() * .9); 515 } 516 517 @Override 518 public int countBeforeFull() { 519 return getPrefetchSize() == 0 ? prefetchExtension.get() : info.getPrefetchSize() + prefetchExtension.get() - dispatched.size(); 520 } 521 522 @Override 523 public int getPendingQueueSize() { 524 return pending.size(); 525 } 526 527 @Override 528 public long getPendingMessageSize() { 529 return pending.messageSize(); 530 } 531 532 @Override 533 public int getDispatchedQueueSize() { 534 return dispatched.size(); 535 } 536 537 @Override 538 public long getDequeueCounter() { 539 return getSubscriptionStatistics().getDequeues().getCount(); 540 } 541 542 @Override 543 public long getDispatchedCounter() { 544 return getSubscriptionStatistics().getDispatched().getCount(); 545 } 546 547 @Override 548 public long getEnqueueCounter() { 549 return getSubscriptionStatistics().getEnqueues().getCount(); 550 } 551 552 @Override 553 public boolean isRecoveryRequired() { 554 return pending.isRecoveryRequired(); 555 } 556 557 public PendingMessageCursor getPending() { 558 return this.pending; 559 } 560 561 public void setPending(PendingMessageCursor pending) { 562 this.pending = pending; 563 if (this.pending!=null) { 564 this.pending.setSystemUsage(usageManager); 565 this.pending.setMemoryUsageHighWaterMark(getCursorMemoryHighWaterMark()); 566 } 567 } 568 569 @Override 570 public void add(ConnectionContext context, Destination destination) throws Exception { 571 synchronized(pendingLock) { 572 super.add(context, destination); 573 pending.add(context, destination); 574 } 575 } 576 577 @Override 578 public List<MessageReference> remove(ConnectionContext context, Destination destination) throws Exception { 579 return remove(context, destination, dispatched); 580 } 581 582 public List<MessageReference> remove(ConnectionContext context, Destination destination, List<MessageReference> dispatched) throws Exception { 583 LinkedList<MessageReference> redispatch = new LinkedList<MessageReference>(); 584 synchronized(pendingLock) { 585 super.remove(context, destination); 586 // Here is a potential problem concerning Inflight stat: 587 // Messages not already committed or rolled back may not be removed from dispatched list at the moment 588 // Except if each commit or rollback callback action comes before remove of subscriber. 589 redispatch.addAll(pending.remove(context, destination)); 590 591 if (dispatched == null) { 592 return redispatch; 593 } 594 595 // Synchronized to DispatchLock if necessary 596 if (dispatched == this.dispatched) { 597 synchronized(dispatchLock) { 598 addReferencesAndUpdateRedispatch(redispatch, destination, dispatched); 599 } 600 } else { 601 addReferencesAndUpdateRedispatch(redispatch, destination, dispatched); 602 } 603 } 604 605 return redispatch; 606 } 607 608 private void addReferencesAndUpdateRedispatch(LinkedList<MessageReference> redispatch, Destination destination, List<MessageReference> dispatched) { 609 ArrayList<MessageReference> references = new ArrayList<MessageReference>(); 610 for (MessageReference r : dispatched) { 611 if (r.getRegionDestination() == destination) { 612 references.add(r); 613 getSubscriptionStatistics().getInflightMessageSize().addSize(-r.getSize()); 614 } 615 } 616 redispatch.addAll(0, references); 617 destination.getDestinationStatistics().getInflight().subtract(references.size()); 618 dispatched.removeAll(references); 619 } 620 621 // made public so it can be used in MQTTProtocolConverter 622 public void dispatchPending() throws IOException { 623 List<Destination> slowConsumerTargets = null; 624 625 synchronized(pendingLock) { 626 try { 627 int numberToDispatch = countBeforeFull(); 628 if (numberToDispatch > 0) { 629 setSlowConsumer(false); 630 setPendingBatchSize(pending, numberToDispatch); 631 int count = 0; 632 pending.reset(); 633 while (count < numberToDispatch && !isFull() && pending.hasNext()) { 634 MessageReference node = pending.next(); 635 if (node == null) { 636 break; 637 } 638 if (trackedInPendingTransaction(node)) { 639 node.decrementReferenceCount(); 640 continue; 641 } 642 643 // Synchronize between dispatched list and remove of message from pending list 644 // related to remove subscription action 645 synchronized(dispatchLock) { 646 pending.remove(); 647 if (!isDropped(node) && canDispatch(node)) { 648 649 // Message may have been sitting in the pending 650 // list a while waiting for the consumer to ak the message. 651 if (node != QueueMessageReference.NULL_MESSAGE && node.isExpired()) { 652 //increment number to dispatch 653 numberToDispatch++; 654 if (broker.isExpired(node)) { 655 ((Destination)node.getRegionDestination()).messageExpired(context, this, node); 656 } 657 658 if (!isBrowser()) { 659 node.decrementReferenceCount(); 660 continue; 661 } 662 } 663 dispatch(node); 664 count++; 665 } 666 } 667 // decrement after dispatch has taken ownership to avoid usage jitter 668 node.decrementReferenceCount(); 669 } 670 } else if (!pending.isEmpty() && !isSlowConsumer()) { 671 setSlowConsumer(true); 672 slowConsumerTargets = destinations; 673 } 674 } finally { 675 pending.release(); 676 } 677 } 678 679 if (slowConsumerTargets != null) { 680 for (Destination dest : slowConsumerTargets) { 681 dest.slowConsumer(context, this); 682 } 683 } 684 } 685 686 protected boolean trackedInPendingTransaction(MessageReference node) { 687 return false; 688 } 689 690 protected void setPendingBatchSize(PendingMessageCursor pending, int numberToDispatch) { 691 pending.setMaxBatchSize(numberToDispatch); 692 } 693 694 // called with dispatchLock held 695 protected boolean dispatch(final MessageReference node) throws IOException { 696 final Message message = node.getMessage(); 697 if (message == null) { 698 return false; 699 } 700 701 okForAckAsDispatchDone.countDown(); 702 703 MessageDispatch md = createMessageDispatch(node, message); 704 if (node != QueueMessageReference.NULL_MESSAGE) { 705 dispatched.add(node); 706 getSubscriptionStatistics().getDispatched().increment(); 707 } 708 if (getPrefetchSize() == 0) { 709 while (true) { 710 int currentExtension = prefetchExtension.get(); 711 int newExtension = Math.max(0, currentExtension - 1); 712 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 713 break; 714 } 715 } 716 } 717 if (info.isDispatchAsync()) { 718 md.setTransmitCallback(new TransmitCallback() { 719 720 @Override 721 public void onSuccess() { 722 // Since the message gets queued up in async dispatch, we don't want to 723 // decrease the reference count until it gets put on the wire. 724 onDispatch(node, message); 725 } 726 727 @Override 728 public void onFailure() { 729 Destination nodeDest = (Destination) node.getRegionDestination(); 730 if (nodeDest != null) { 731 if (node != QueueMessageReference.NULL_MESSAGE) { 732 nodeDest.getDestinationStatistics().getDispatched().increment(); 733 incrementPrefetchCounter(node); 734 LOG.trace("{} failed to dispatch: {} - {}, dispatched: {}, inflight: {}", new Object[]{ info.getConsumerId(), message.getMessageId(), message.getDestination(), getSubscriptionStatistics().getDispatched().getCount(), dispatched.size() }); 735 } 736 } 737 if (node instanceof QueueMessageReference) { 738 ((QueueMessageReference) node).unlock(); 739 } 740 } 741 }); 742 context.getConnection().dispatchAsync(md); 743 } else { 744 context.getConnection().dispatchSync(md); 745 onDispatch(node, message); 746 } 747 return true; 748 } 749 750 protected void onDispatch(final MessageReference node, final Message message) { 751 Destination nodeDest = (Destination) node.getRegionDestination(); 752 if (nodeDest != null) { 753 if (node != QueueMessageReference.NULL_MESSAGE) { 754 nodeDest.getDestinationStatistics().getDispatched().increment(); 755 incrementPrefetchCounter(node); 756 LOG.trace("{} dispatched: {} - {}, dispatched: {}, inflight: {}", new Object[]{ info.getConsumerId(), message.getMessageId(), message.getDestination(), getSubscriptionStatistics().getDispatched().getCount(), dispatched.size() }); 757 } 758 } 759 760 if (info.isDispatchAsync()) { 761 try { 762 dispatchPending(); 763 } catch (IOException e) { 764 context.getConnection().serviceExceptionAsync(e); 765 } 766 } 767 } 768 769 /** 770 * inform the MessageConsumer on the client to change it's prefetch 771 * 772 * @param newPrefetch 773 */ 774 @Override 775 public void updateConsumerPrefetch(int newPrefetch) { 776 if (context != null && context.getConnection() != null && context.getConnection().isManageable()) { 777 ConsumerControl cc = new ConsumerControl(); 778 cc.setConsumerId(info.getConsumerId()); 779 cc.setPrefetch(newPrefetch); 780 context.getConnection().dispatchAsync(cc); 781 } 782 } 783 784 /** 785 * @param node 786 * @param message 787 * @return MessageDispatch 788 */ 789 protected MessageDispatch createMessageDispatch(MessageReference node, Message message) { 790 MessageDispatch md = new MessageDispatch(); 791 md.setConsumerId(info.getConsumerId()); 792 793 if (node == QueueMessageReference.NULL_MESSAGE) { 794 md.setMessage(null); 795 md.setDestination(null); 796 } else { 797 Destination regionDestination = (Destination) node.getRegionDestination(); 798 md.setDestination(regionDestination.getActiveMQDestination()); 799 md.setMessage(message); 800 md.setRedeliveryCounter(node.getRedeliveryCounter()); 801 } 802 803 return md; 804 } 805 806 /** 807 * Use when a matched message is about to be dispatched to the client. 808 * 809 * @param node 810 * @return false if the message should not be dispatched to the client 811 * (another sub may have already dispatched it for example). 812 * @throws IOException 813 */ 814 protected abstract boolean canDispatch(MessageReference node) throws IOException; 815 816 protected abstract boolean isDropped(MessageReference node); 817 818 /** 819 * Used during acknowledgment to remove the message. 820 * 821 * @throws IOException 822 */ 823 protected abstract void acknowledge(ConnectionContext context, final MessageAck ack, final MessageReference node) throws IOException; 824 825 826 public int getMaxProducersToAudit() { 827 return maxProducersToAudit; 828 } 829 830 public void setMaxProducersToAudit(int maxProducersToAudit) { 831 this.maxProducersToAudit = maxProducersToAudit; 832 if (this.pending != null) { 833 this.pending.setMaxProducersToAudit(maxProducersToAudit); 834 } 835 } 836 837 public int getMaxAuditDepth() { 838 return maxAuditDepth; 839 } 840 841 public void setMaxAuditDepth(int maxAuditDepth) { 842 this.maxAuditDepth = maxAuditDepth; 843 if (this.pending != null) { 844 this.pending.setMaxAuditDepth(maxAuditDepth); 845 } 846 } 847 848 @Override 849 public void setPrefetchSize(int prefetchSize) { 850 this.info.setPrefetchSize(prefetchSize); 851 try { 852 this.dispatchPending(); 853 } catch (Exception e) { 854 LOG.trace("Caught exception during dispatch after prefetch change.", e); 855 } 856 } 857 858 private void incrementPrefetchCounter(final MessageReference node) { 859 ((Destination)node.getRegionDestination()).getDestinationStatistics().getInflight().increment(); 860 getSubscriptionStatistics().getInflightMessageSize().addSize(node.getSize()); 861 } 862 863 private void decrementPrefetchCounter(final MessageReference node) { 864 ((Destination)node.getRegionDestination()).getDestinationStatistics().getInflight().decrement(); 865 getSubscriptionStatistics().getInflightMessageSize().addSize(-node.getSize()); 866 } 867}