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