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 getSubscriptionStatistics().getInflightMessageSize().addSize(node.getSize()); 178 onDispatch(node, node.getMessage()); 179 } 180 return; 181 } 182 } 183 } finally { 184 pending.release(); 185 } 186 } 187 throw new JMSException( 188 "Slave broker out of sync with master: Dispatched message (" 189 + mdn.getMessageId() + ") was not in the pending list for " 190 + mdn.getConsumerId() + " on " + mdn.getDestination().getPhysicalName()); 191 } 192 193 @Override 194 public final void acknowledge(final ConnectionContext context,final MessageAck ack) throws Exception { 195 // Handle the standard acknowledgment case. 196 boolean callDispatchMatched = false; 197 Destination destination = null; 198 199 if (!okForAckAsDispatchDone.await(0l, TimeUnit.MILLISECONDS)) { 200 // suppress unexpected ack exception in this expected case 201 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); 202 return; 203 } 204 205 LOG.trace("ack: {}", ack); 206 207 synchronized(dispatchLock) { 208 if (ack.isStandardAck()) { 209 // First check if the ack matches the dispatched. When using failover this might 210 // not be the case. We don't ever want to ack the wrong messages. 211 assertAckMatchesDispatched(ack); 212 213 // Acknowledge all dispatched messages up till the message id of 214 // the acknowledgment. 215 boolean inAckRange = false; 216 List<MessageReference> removeList = new ArrayList<MessageReference>(); 217 for (final MessageReference node : dispatched) { 218 MessageId messageId = node.getMessageId(); 219 if (ack.getFirstMessageId() == null 220 || ack.getFirstMessageId().equals(messageId)) { 221 inAckRange = true; 222 } 223 if (inAckRange) { 224 // Don't remove the nodes until we are committed. 225 if (!context.isInTransaction()) { 226 getSubscriptionStatistics().getDequeues().increment(); 227 ((Destination)node.getRegionDestination()).getDestinationStatistics().getInflight().decrement(); 228 removeList.add(node); 229 } else { 230 registerRemoveSync(context, node); 231 } 232 acknowledge(context, ack, node); 233 if (ack.getLastMessageId().equals(messageId)) { 234 destination = (Destination) node.getRegionDestination(); 235 callDispatchMatched = true; 236 break; 237 } 238 } 239 } 240 for (final MessageReference node : removeList) { 241 dispatched.remove(node); 242 getSubscriptionStatistics().getInflightMessageSize().addSize(-node.getSize()); 243 } 244 // this only happens after a reconnect - get an ack which is not 245 // valid 246 if (!callDispatchMatched) { 247 LOG.warn("Could not correlate acknowledgment with dispatched message: {}", ack); 248 } 249 } else if (ack.isIndividualAck()) { 250 // Message was delivered and acknowledge - but only delete the 251 // individual message 252 for (final MessageReference node : dispatched) { 253 MessageId messageId = node.getMessageId(); 254 if (ack.getLastMessageId().equals(messageId)) { 255 // Don't remove the nodes until we are committed - immediateAck option 256 if (!context.isInTransaction()) { 257 getSubscriptionStatistics().getDequeues().increment(); 258 ((Destination)node.getRegionDestination()).getDestinationStatistics().getInflight().decrement(); 259 dispatched.remove(node); 260 getSubscriptionStatistics().getInflightMessageSize().addSize(-node.getSize()); 261 } else { 262 registerRemoveSync(context, node); 263 } 264 265 if (isUsePrefetchExtension() && getPrefetchSize() != 0 && ack.isInTransaction()) { 266 // allow transaction batch to exceed prefetch 267 while (true) { 268 int currentExtension = prefetchExtension.get(); 269 int newExtension = Math.max(currentExtension, currentExtension + 1); 270 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 271 break; 272 } 273 } 274 } 275 276 acknowledge(context, ack, node); 277 destination = (Destination) node.getRegionDestination(); 278 callDispatchMatched = true; 279 break; 280 } 281 } 282 }else if (ack.isDeliveredAck()) { 283 // Message was delivered but not acknowledged: update pre-fetch 284 // counters. 285 int index = 0; 286 for (Iterator<MessageReference> iter = dispatched.iterator(); iter.hasNext(); index++) { 287 final MessageReference node = iter.next(); 288 Destination nodeDest = (Destination) node.getRegionDestination(); 289 if (ack.getLastMessageId().equals(node.getMessageId())) { 290 if (isUsePrefetchExtension() && getPrefetchSize() != 0) { 291 // allow batch to exceed prefetch 292 while (true) { 293 int currentExtension = prefetchExtension.get(); 294 int newExtension = Math.max(currentExtension, index + 1); 295 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 296 break; 297 } 298 } 299 } 300 destination = nodeDest; 301 callDispatchMatched = true; 302 break; 303 } 304 } 305 if (!callDispatchMatched) { 306 throw new JMSException( 307 "Could not correlate acknowledgment with dispatched message: " 308 + ack); 309 } 310 } else if (ack.isExpiredAck()) { 311 // Message was expired 312 int index = 0; 313 boolean inAckRange = false; 314 for (Iterator<MessageReference> iter = dispatched.iterator(); iter.hasNext(); index++) { 315 final MessageReference node = iter.next(); 316 Destination nodeDest = (Destination) node.getRegionDestination(); 317 MessageId messageId = node.getMessageId(); 318 if (ack.getFirstMessageId() == null || ack.getFirstMessageId().equals(messageId)) { 319 inAckRange = true; 320 } 321 if (inAckRange) { 322 Destination regionDestination = nodeDest; 323 if (broker.isExpired(node)) { 324 regionDestination.messageExpired(context, this, node); 325 } 326 iter.remove(); 327 nodeDest.getDestinationStatistics().getInflight().decrement(); 328 329 if (ack.getLastMessageId().equals(messageId)) { 330 if (isUsePrefetchExtension() && getPrefetchSize() != 0) { 331 // allow batch to exceed prefetch 332 while (true) { 333 int currentExtension = prefetchExtension.get(); 334 int newExtension = Math.max(currentExtension, index + 1); 335 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 336 break; 337 } 338 } 339 } 340 341 destination = (Destination) node.getRegionDestination(); 342 callDispatchMatched = true; 343 break; 344 } 345 } 346 } 347 if (!callDispatchMatched) { 348 throw new JMSException( 349 "Could not correlate expiration acknowledgment with dispatched message: " 350 + ack); 351 } 352 } else if (ack.isRedeliveredAck()) { 353 // Message was re-delivered but it was not yet considered to be 354 // a DLQ message. 355 boolean inAckRange = false; 356 for (final MessageReference node : dispatched) { 357 MessageId messageId = node.getMessageId(); 358 if (ack.getFirstMessageId() == null 359 || ack.getFirstMessageId().equals(messageId)) { 360 inAckRange = true; 361 } 362 if (inAckRange) { 363 if (ack.getLastMessageId().equals(messageId)) { 364 destination = (Destination) node.getRegionDestination(); 365 callDispatchMatched = true; 366 break; 367 } 368 } 369 } 370 if (!callDispatchMatched) { 371 throw new JMSException( 372 "Could not correlate acknowledgment with dispatched message: " 373 + ack); 374 } 375 } else if (ack.isPoisonAck()) { 376 // TODO: what if the message is already in a DLQ??? 377 // Handle the poison ACK case: we need to send the message to a 378 // DLQ 379 if (ack.isInTransaction()) { 380 throw new JMSException("Poison ack cannot be transacted: " 381 + ack); 382 } 383 int index = 0; 384 boolean inAckRange = false; 385 List<MessageReference> removeList = new ArrayList<MessageReference>(); 386 for (final MessageReference node : dispatched) { 387 MessageId messageId = node.getMessageId(); 388 if (ack.getFirstMessageId() == null 389 || ack.getFirstMessageId().equals(messageId)) { 390 inAckRange = true; 391 } 392 if (inAckRange) { 393 sendToDLQ(context, node, ack.getPoisonCause()); 394 Destination nodeDest = (Destination) node.getRegionDestination(); 395 nodeDest.getDestinationStatistics() 396 .getInflight().decrement(); 397 removeList.add(node); 398 getSubscriptionStatistics().getDequeues().increment(); 399 index++; 400 acknowledge(context, ack, node); 401 if (ack.getLastMessageId().equals(messageId)) { 402 while (true) { 403 int currentExtension = prefetchExtension.get(); 404 int newExtension = Math.max(0, currentExtension - (index + 1)); 405 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 406 break; 407 } 408 } 409 destination = nodeDest; 410 callDispatchMatched = true; 411 break; 412 } 413 } 414 } 415 for (final MessageReference node : removeList) { 416 dispatched.remove(node); 417 getSubscriptionStatistics().getInflightMessageSize().addSize(-node.getSize()); 418 } 419 if (!callDispatchMatched) { 420 throw new JMSException( 421 "Could not correlate acknowledgment with dispatched message: " 422 + ack); 423 } 424 } 425 } 426 if (callDispatchMatched && destination != null) { 427 destination.wakeup(); 428 dispatchPending(); 429 430 if (pending.isEmpty()) { 431 wakeupDestinationsForDispatch(); 432 } 433 } else { 434 LOG.debug("Acknowledgment out of sync (Normally occurs when failover connection reconnects): {}", ack); 435 } 436 } 437 438 private void registerRemoveSync(ConnectionContext context, final MessageReference node) { 439 // setup a Synchronization to remove nodes from the 440 // dispatched list. 441 context.getTransaction().addSynchronization( 442 new Synchronization() { 443 444 @Override 445 public void beforeEnd() { 446 if (isUsePrefetchExtension() && getPrefetchSize() != 0) { 447 while (true) { 448 int currentExtension = prefetchExtension.get(); 449 int newExtension = Math.max(0, currentExtension - 1); 450 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 451 break; 452 } 453 } 454 } 455 } 456 457 @Override 458 public void afterCommit() 459 throws Exception { 460 Destination nodeDest = (Destination) node.getRegionDestination(); 461 synchronized(dispatchLock) { 462 getSubscriptionStatistics().getDequeues().increment(); 463 dispatched.remove(node); 464 getSubscriptionStatistics().getInflightMessageSize().addSize(-node.getSize()); 465 nodeDest.getDestinationStatistics().getInflight().decrement(); 466 } 467 nodeDest.wakeup(); 468 dispatchPending(); 469 } 470 471 @Override 472 public void afterRollback() throws Exception { 473 synchronized(dispatchLock) { 474 // poisionAck will decrement - otherwise still inflight on client 475 } 476 } 477 }); 478 } 479 480 /** 481 * Checks an ack versus the contents of the dispatched list. 482 * called with dispatchLock held 483 * @param ack 484 * @throws JMSException if it does not match 485 */ 486 protected void assertAckMatchesDispatched(MessageAck ack) throws JMSException { 487 MessageId firstAckedMsg = ack.getFirstMessageId(); 488 MessageId lastAckedMsg = ack.getLastMessageId(); 489 int checkCount = 0; 490 boolean checkFoundStart = false; 491 boolean checkFoundEnd = false; 492 for (MessageReference node : dispatched) { 493 494 if (firstAckedMsg == null) { 495 checkFoundStart = true; 496 } else if (!checkFoundStart && firstAckedMsg.equals(node.getMessageId())) { 497 checkFoundStart = true; 498 } 499 500 if (checkFoundStart) { 501 checkCount++; 502 } 503 504 if (lastAckedMsg != null && lastAckedMsg.equals(node.getMessageId())) { 505 checkFoundEnd = true; 506 break; 507 } 508 } 509 if (!checkFoundStart && firstAckedMsg != null) 510 throw new JMSException("Unmatched acknowledge: " + ack 511 + "; Could not find Message-ID " + firstAckedMsg 512 + " in dispatched-list (start of ack)"); 513 if (!checkFoundEnd && lastAckedMsg != null) 514 throw new JMSException("Unmatched acknowledge: " + ack 515 + "; Could not find Message-ID " + lastAckedMsg 516 + " in dispatched-list (end of ack)"); 517 if (ack.getMessageCount() != checkCount && !ack.isInTransaction()) { 518 throw new JMSException("Unmatched acknowledge: " + ack 519 + "; Expected message count (" + ack.getMessageCount() 520 + ") differs from count in dispatched-list (" + checkCount 521 + ")"); 522 } 523 } 524 525 /** 526 * 527 * @param context 528 * @param node 529 * @param poisonCause 530 * @throws IOException 531 * @throws Exception 532 */ 533 protected void sendToDLQ(final ConnectionContext context, final MessageReference node, Throwable poisonCause) throws IOException, Exception { 534 broker.getRoot().sendToDeadLetterQueue(context, node, this, poisonCause); 535 } 536 537 @Override 538 public int getInFlightSize() { 539 return dispatched.size(); 540 } 541 542 /** 543 * Used to determine if the broker can dispatch to the consumer. 544 * 545 * @return true if the subscription is full 546 */ 547 @Override 548 public boolean isFull() { 549 return getPrefetchSize() == 0 ? prefetchExtension.get() == 0 : dispatched.size() - prefetchExtension.get() >= info.getPrefetchSize(); 550 } 551 552 /** 553 * @return true when 60% or more room is left for dispatching messages 554 */ 555 @Override 556 public boolean isLowWaterMark() { 557 return (dispatched.size() - prefetchExtension.get()) <= (info.getPrefetchSize() * .4); 558 } 559 560 /** 561 * @return true when 10% or less room is left for dispatching messages 562 */ 563 @Override 564 public boolean isHighWaterMark() { 565 return (dispatched.size() - prefetchExtension.get()) >= (info.getPrefetchSize() * .9); 566 } 567 568 @Override 569 public int countBeforeFull() { 570 return getPrefetchSize() == 0 ? prefetchExtension.get() : info.getPrefetchSize() + prefetchExtension.get() - dispatched.size(); 571 } 572 573 @Override 574 public int getPendingQueueSize() { 575 return pending.size(); 576 } 577 578 @Override 579 public long getPendingMessageSize() { 580 synchronized (pendingLock) { 581 return pending.messageSize(); 582 } 583 } 584 585 @Override 586 public int getDispatchedQueueSize() { 587 return dispatched.size(); 588 } 589 590 @Override 591 public long getDequeueCounter() { 592 return getSubscriptionStatistics().getDequeues().getCount(); 593 } 594 595 @Override 596 public long getDispatchedCounter() { 597 return getSubscriptionStatistics().getDispatched().getCount(); 598 } 599 600 @Override 601 public long getEnqueueCounter() { 602 return getSubscriptionStatistics().getEnqueues().getCount(); 603 } 604 605 @Override 606 public boolean isRecoveryRequired() { 607 return pending.isRecoveryRequired(); 608 } 609 610 public PendingMessageCursor getPending() { 611 return this.pending; 612 } 613 614 public void setPending(PendingMessageCursor pending) { 615 this.pending = pending; 616 if (this.pending!=null) { 617 this.pending.setSystemUsage(usageManager); 618 this.pending.setMemoryUsageHighWaterMark(getCursorMemoryHighWaterMark()); 619 } 620 } 621 622 @Override 623 public void add(ConnectionContext context, Destination destination) throws Exception { 624 synchronized(pendingLock) { 625 super.add(context, destination); 626 pending.add(context, destination); 627 } 628 } 629 630 @Override 631 public List<MessageReference> remove(ConnectionContext context, Destination destination) throws Exception { 632 return remove(context, destination, dispatched); 633 } 634 635 public List<MessageReference> remove(ConnectionContext context, Destination destination, List<MessageReference> dispatched) throws Exception { 636 LinkedList<MessageReference> redispatch = new LinkedList<MessageReference>(); 637 synchronized(pendingLock) { 638 super.remove(context, destination); 639 // Here is a potential problem concerning Inflight stat: 640 // Messages not already committed or rolled back may not be removed from dispatched list at the moment 641 // Except if each commit or rollback callback action comes before remove of subscriber. 642 redispatch.addAll(pending.remove(context, destination)); 643 644 if (dispatched == null) { 645 return redispatch; 646 } 647 648 // Synchronized to DispatchLock if necessary 649 if (dispatched == this.dispatched) { 650 synchronized(dispatchLock) { 651 addReferencesAndUpdateRedispatch(redispatch, destination, dispatched); 652 } 653 } else { 654 addReferencesAndUpdateRedispatch(redispatch, destination, dispatched); 655 } 656 } 657 658 return redispatch; 659 } 660 661 private void addReferencesAndUpdateRedispatch(LinkedList<MessageReference> redispatch, Destination destination, List<MessageReference> dispatched) { 662 ArrayList<MessageReference> references = new ArrayList<MessageReference>(); 663 for (MessageReference r : dispatched) { 664 if (r.getRegionDestination() == destination) { 665 references.add(r); 666 getSubscriptionStatistics().getInflightMessageSize().addSize(-r.getSize()); 667 } 668 } 669 redispatch.addAll(0, references); 670 destination.getDestinationStatistics().getInflight().subtract(references.size()); 671 dispatched.removeAll(references); 672 } 673 674 // made public so it can be used in MQTTProtocolConverter 675 public void dispatchPending() throws IOException { 676 List<Destination> slowConsumerTargets = null; 677 678 synchronized(pendingLock) { 679 try { 680 int numberToDispatch = countBeforeFull(); 681 if (numberToDispatch > 0) { 682 setSlowConsumer(false); 683 setPendingBatchSize(pending, numberToDispatch); 684 int count = 0; 685 pending.reset(); 686 while (count < numberToDispatch && !isFull() && pending.hasNext()) { 687 MessageReference node = pending.next(); 688 if (node == null) { 689 break; 690 } 691 692 // Synchronize between dispatched list and remove of message from pending list 693 // related to remove subscription action 694 synchronized(dispatchLock) { 695 pending.remove(); 696 if (!isDropped(node) && canDispatch(node)) { 697 698 // Message may have been sitting in the pending 699 // list a while waiting for the consumer to ak the message. 700 if (node != QueueMessageReference.NULL_MESSAGE && node.isExpired()) { 701 //increment number to dispatch 702 numberToDispatch++; 703 if (broker.isExpired(node)) { 704 ((Destination)node.getRegionDestination()).messageExpired(context, this, node); 705 } 706 707 if (!isBrowser()) { 708 node.decrementReferenceCount(); 709 continue; 710 } 711 } 712 dispatch(node); 713 count++; 714 } 715 } 716 // decrement after dispatch has taken ownership to avoid usage jitter 717 node.decrementReferenceCount(); 718 } 719 } else if (!isSlowConsumer()) { 720 setSlowConsumer(true); 721 slowConsumerTargets = destinations; 722 } 723 } finally { 724 pending.release(); 725 } 726 } 727 728 if (slowConsumerTargets != null) { 729 for (Destination dest : slowConsumerTargets) { 730 dest.slowConsumer(context, this); 731 } 732 } 733 } 734 735 protected void setPendingBatchSize(PendingMessageCursor pending, int numberToDispatch) { 736 pending.setMaxBatchSize(numberToDispatch); 737 } 738 739 // called with dispatchLock held 740 protected boolean dispatch(final MessageReference node) throws IOException { 741 final Message message = node.getMessage(); 742 if (message == null) { 743 return false; 744 } 745 746 okForAckAsDispatchDone.countDown(); 747 748 MessageDispatch md = createMessageDispatch(node, message); 749 if (node != QueueMessageReference.NULL_MESSAGE) { 750 getSubscriptionStatistics().getDispatched().increment(); 751 dispatched.add(node); 752 getSubscriptionStatistics().getInflightMessageSize().addSize(node.getSize()); 753 } 754 if (getPrefetchSize() == 0) { 755 while (true) { 756 int currentExtension = prefetchExtension.get(); 757 int newExtension = Math.max(0, currentExtension - 1); 758 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 759 break; 760 } 761 } 762 } 763 if (info.isDispatchAsync()) { 764 md.setTransmitCallback(new TransmitCallback() { 765 766 @Override 767 public void onSuccess() { 768 // Since the message gets queued up in async dispatch, we don't want to 769 // decrease the reference count until it gets put on the wire. 770 onDispatch(node, message); 771 } 772 773 @Override 774 public void onFailure() { 775 Destination nodeDest = (Destination) node.getRegionDestination(); 776 if (nodeDest != null) { 777 if (node != QueueMessageReference.NULL_MESSAGE) { 778 nodeDest.getDestinationStatistics().getDispatched().increment(); 779 nodeDest.getDestinationStatistics().getInflight().increment(); 780 LOG.trace("{} failed to dispatch: {} - {}, dispatched: {}, inflight: {}", new Object[]{ info.getConsumerId(), message.getMessageId(), message.getDestination(), getSubscriptionStatistics().getDispatched().getCount(), dispatched.size() }); 781 } 782 } 783 if (node instanceof QueueMessageReference) { 784 ((QueueMessageReference) node).unlock(); 785 } 786 } 787 }); 788 context.getConnection().dispatchAsync(md); 789 } else { 790 context.getConnection().dispatchSync(md); 791 onDispatch(node, message); 792 } 793 return true; 794 } 795 796 protected void onDispatch(final MessageReference node, final Message message) { 797 Destination nodeDest = (Destination) node.getRegionDestination(); 798 if (nodeDest != null) { 799 if (node != QueueMessageReference.NULL_MESSAGE) { 800 nodeDest.getDestinationStatistics().getDispatched().increment(); 801 nodeDest.getDestinationStatistics().getInflight().increment(); 802 LOG.trace("{} dispatched: {} - {}, dispatched: {}, inflight: {}", new Object[]{ info.getConsumerId(), message.getMessageId(), message.getDestination(), getSubscriptionStatistics().getDispatched().getCount(), dispatched.size() }); 803 } 804 } 805 806 if (info.isDispatchAsync()) { 807 try { 808 dispatchPending(); 809 } catch (IOException e) { 810 context.getConnection().serviceExceptionAsync(e); 811 } 812 } 813 } 814 815 /** 816 * inform the MessageConsumer on the client to change it's prefetch 817 * 818 * @param newPrefetch 819 */ 820 @Override 821 public void updateConsumerPrefetch(int newPrefetch) { 822 if (context != null && context.getConnection() != null && context.getConnection().isManageable()) { 823 ConsumerControl cc = new ConsumerControl(); 824 cc.setConsumerId(info.getConsumerId()); 825 cc.setPrefetch(newPrefetch); 826 context.getConnection().dispatchAsync(cc); 827 } 828 } 829 830 /** 831 * @param node 832 * @param message 833 * @return MessageDispatch 834 */ 835 protected MessageDispatch createMessageDispatch(MessageReference node, Message message) { 836 MessageDispatch md = new MessageDispatch(); 837 md.setConsumerId(info.getConsumerId()); 838 839 if (node == QueueMessageReference.NULL_MESSAGE) { 840 md.setMessage(null); 841 md.setDestination(null); 842 } else { 843 Destination regionDestination = (Destination) node.getRegionDestination(); 844 md.setDestination(regionDestination.getActiveMQDestination()); 845 md.setMessage(message); 846 md.setRedeliveryCounter(node.getRedeliveryCounter()); 847 } 848 849 return md; 850 } 851 852 /** 853 * Use when a matched message is about to be dispatched to the client. 854 * 855 * @param node 856 * @return false if the message should not be dispatched to the client 857 * (another sub may have already dispatched it for example). 858 * @throws IOException 859 */ 860 protected abstract boolean canDispatch(MessageReference node) throws IOException; 861 862 protected abstract boolean isDropped(MessageReference node); 863 864 /** 865 * Used during acknowledgment to remove the message. 866 * 867 * @throws IOException 868 */ 869 protected abstract void acknowledge(ConnectionContext context, final MessageAck ack, final MessageReference node) throws IOException; 870 871 872 public int getMaxProducersToAudit() { 873 return maxProducersToAudit; 874 } 875 876 public void setMaxProducersToAudit(int maxProducersToAudit) { 877 this.maxProducersToAudit = maxProducersToAudit; 878 if (this.pending != null) { 879 this.pending.setMaxProducersToAudit(maxProducersToAudit); 880 } 881 } 882 883 public int getMaxAuditDepth() { 884 return maxAuditDepth; 885 } 886 887 public void setMaxAuditDepth(int maxAuditDepth) { 888 this.maxAuditDepth = maxAuditDepth; 889 if (this.pending != null) { 890 this.pending.setMaxAuditDepth(maxAuditDepth); 891 } 892 } 893 894 @Override 895 public void setPrefetchSize(int prefetchSize) { 896 this.info.setPrefetchSize(prefetchSize); 897 try { 898 this.dispatchPending(); 899 } catch (Exception e) { 900 LOG.trace("Caught exception during dispatch after prefetch change.", e); 901 } 902 } 903}