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 org.apache.activemq.ActiveMQMessageAudit; 020import org.apache.activemq.broker.Broker; 021import org.apache.activemq.broker.ConnectionContext; 022import org.apache.activemq.broker.region.cursors.FilePendingMessageCursor; 023import org.apache.activemq.broker.region.cursors.PendingMessageCursor; 024import org.apache.activemq.broker.region.cursors.VMPendingMessageCursor; 025import org.apache.activemq.broker.region.policy.MessageEvictionStrategy; 026import org.apache.activemq.broker.region.policy.OldestMessageEvictionStrategy; 027import org.apache.activemq.command.*; 028import org.apache.activemq.thread.Scheduler; 029import org.apache.activemq.transaction.Synchronization; 030import org.apache.activemq.transport.TransmitCallback; 031import org.apache.activemq.usage.SystemUsage; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035import javax.jms.JMSException; 036import java.io.IOException; 037import java.util.ArrayList; 038import java.util.LinkedList; 039import java.util.List; 040import java.util.concurrent.atomic.AtomicInteger; 041import java.util.concurrent.atomic.AtomicLong; 042 043public class TopicSubscription extends AbstractSubscription { 044 045 private static final Logger LOG = LoggerFactory.getLogger(TopicSubscription.class); 046 private static final AtomicLong CURSOR_NAME_COUNTER = new AtomicLong(0); 047 048 protected PendingMessageCursor matched; 049 protected final SystemUsage usageManager; 050 boolean singleDestination = true; 051 Destination destination; 052 private final Scheduler scheduler; 053 054 private int maximumPendingMessages = -1; 055 private MessageEvictionStrategy messageEvictionStrategy = new OldestMessageEvictionStrategy(); 056 private final AtomicInteger discarded = new AtomicInteger(); 057 private final Object matchedListMutex = new Object(); 058 private int memoryUsageHighWaterMark = 95; 059 // allow duplicate suppression in a ring network of brokers 060 protected int maxProducersToAudit = 1024; 061 protected int maxAuditDepth = 1000; 062 protected boolean enableAudit = false; 063 protected ActiveMQMessageAudit audit; 064 protected boolean active = false; 065 protected boolean discarding = false; 066 private boolean useTopicSubscriptionInflightStats = true; 067 068 //Used for inflight message size calculations 069 protected final Object dispatchLock = new Object(); 070 protected final List<DispatchedNode> dispatched = new ArrayList<>(); 071 072 public TopicSubscription(Broker broker,ConnectionContext context, ConsumerInfo info, SystemUsage usageManager) throws Exception { 073 super(broker, context, info); 074 this.usageManager = usageManager; 075 String matchedName = "TopicSubscription:" + CURSOR_NAME_COUNTER.getAndIncrement() + "[" + info.getConsumerId().toString() + "]"; 076 if (info.getDestination().isTemporary() || broker.getTempDataStore()==null ) { 077 this.matched = new VMPendingMessageCursor(false); 078 } else { 079 this.matched = new FilePendingMessageCursor(broker,matchedName,false); 080 } 081 082 this.scheduler = broker.getScheduler(); 083 } 084 085 public void init() throws Exception { 086 this.matched.setSystemUsage(usageManager); 087 this.matched.setMemoryUsageHighWaterMark(getCursorMemoryHighWaterMark()); 088 this.matched.start(); 089 if (enableAudit) { 090 audit= new ActiveMQMessageAudit(maxAuditDepth, maxProducersToAudit); 091 } 092 this.active=true; 093 } 094 095 @Override 096 public void add(MessageReference node) throws Exception { 097 if (isDuplicate(node)) { 098 return; 099 } 100 // Lets use an indirect reference so that we can associate a unique 101 // locator /w the message. 102 node = new IndirectMessageReference(node.getMessage()); 103 getSubscriptionStatistics().getEnqueues().increment(); 104 synchronized (matchedListMutex) { 105 // if this subscriber is already discarding a message, we don't want to add 106 // any more messages to it as those messages can only be advisories generated in the process, 107 // which can trigger the recursive call loop 108 if (discarding) return; 109 110 if (!isFull() && matched.isEmpty()) { 111 // if maximumPendingMessages is set we will only discard messages which 112 // have not been dispatched (i.e. we allow the prefetch buffer to be filled) 113 dispatch(node); 114 setSlowConsumer(false); 115 } else { 116 if (info.getPrefetchSize() > 1 && matched.size() > info.getPrefetchSize()) { 117 // Slow consumers should log and set their state as such. 118 if (!isSlowConsumer()) { 119 LOG.warn("{}: has twice its prefetch limit pending, without an ack; it appears to be slow", toString()); 120 setSlowConsumer(true); 121 for (Destination dest: destinations) { 122 dest.slowConsumer(getContext(), this); 123 } 124 } 125 } 126 if (maximumPendingMessages != 0) { 127 boolean warnedAboutWait = false; 128 while (active) { 129 while (matched.isFull()) { 130 if (getContext().getStopping().get()) { 131 LOG.warn("{}: stopped waiting for space in pendingMessage cursor for: {}", toString(), node.getMessageId()); 132 getSubscriptionStatistics().getEnqueues().decrement(); 133 return; 134 } 135 if (!warnedAboutWait) { 136 LOG.info("{}: Pending message cursor [{}] is full, temp usag ({}%) or memory usage ({}%) limit reached, blocking message add() pending the release of resources.", 137 new Object[]{ 138 toString(), 139 matched, 140 matched.getSystemUsage().getTempUsage().getPercentUsage(), 141 matched.getSystemUsage().getMemoryUsage().getPercentUsage() 142 }); 143 warnedAboutWait = true; 144 } 145 matchedListMutex.wait(20); 146 } 147 // Temporary storage could be full - so just try to add the message 148 // see https://issues.apache.org/activemq/browse/AMQ-2475 149 if (matched.tryAddMessageLast(node, 10)) { 150 break; 151 } 152 } 153 if (maximumPendingMessages > 0) { 154 // calculate the high water mark from which point we 155 // will eagerly evict expired messages 156 int max = messageEvictionStrategy.getEvictExpiredMessagesHighWatermark(); 157 if (maximumPendingMessages > 0 && maximumPendingMessages < max) { 158 max = maximumPendingMessages; 159 } 160 if (!matched.isEmpty() && matched.size() > max) { 161 removeExpiredMessages(); 162 } 163 // lets discard old messages as we are a slow consumer 164 while (!matched.isEmpty() && matched.size() > maximumPendingMessages) { 165 int pageInSize = matched.size() - maximumPendingMessages; 166 // only page in a 1000 at a time - else we could blow the memory 167 pageInSize = Math.max(1000, pageInSize); 168 LinkedList<MessageReference> list = null; 169 MessageReference[] oldMessages=null; 170 synchronized(matched){ 171 list = matched.pageInList(pageInSize); 172 oldMessages = messageEvictionStrategy.evictMessages(list); 173 for (MessageReference ref : list) { 174 ref.decrementReferenceCount(); 175 } 176 } 177 int messagesToEvict = 0; 178 if (oldMessages != null){ 179 messagesToEvict = oldMessages.length; 180 for (int i = 0; i < messagesToEvict; i++) { 181 MessageReference oldMessage = oldMessages[i]; 182 discard(oldMessage); 183 } 184 } 185 // lets avoid an infinite loop if we are given a bad eviction strategy 186 // for a bad strategy lets just not evict 187 if (messagesToEvict == 0) { 188 LOG.warn("No messages to evict returned for {} from eviction strategy: {} out of {} candidates", new Object[]{ 189 destination, messageEvictionStrategy, list.size() 190 }); 191 break; 192 } 193 } 194 } 195 dispatchMatched(); 196 } 197 } 198 } 199 } 200 201 private boolean isDuplicate(MessageReference node) { 202 boolean duplicate = false; 203 if (enableAudit && audit != null) { 204 duplicate = audit.isDuplicate(node); 205 if (LOG.isDebugEnabled()) { 206 if (duplicate) { 207 LOG.debug("{}, ignoring duplicate add: {}", this, node.getMessageId()); 208 } 209 } 210 } 211 return duplicate; 212 } 213 214 /** 215 * Discard any expired messages from the matched list. Called from a 216 * synchronized block. 217 * 218 * @throws IOException 219 */ 220 protected void removeExpiredMessages() throws IOException { 221 try { 222 matched.reset(); 223 while (matched.hasNext()) { 224 MessageReference node = matched.next(); 225 node.decrementReferenceCount(); 226 if (node.isExpired()) { 227 matched.remove(); 228 node.decrementReferenceCount(); 229 if (broker.isExpired(node)) { 230 ((Destination) node.getRegionDestination()).getDestinationStatistics().getExpired().increment(); 231 broker.messageExpired(getContext(), node, this); 232 } 233 break; 234 } 235 } 236 } finally { 237 matched.release(); 238 } 239 } 240 241 @Override 242 public void processMessageDispatchNotification(MessageDispatchNotification mdn) { 243 synchronized (matchedListMutex) { 244 try { 245 matched.reset(); 246 while (matched.hasNext()) { 247 MessageReference node = matched.next(); 248 node.decrementReferenceCount(); 249 if (node.getMessageId().equals(mdn.getMessageId())) { 250 synchronized(dispatchLock) { 251 matched.remove(); 252 getSubscriptionStatistics().getDispatched().increment(); 253 if (isUseTopicSubscriptionInflightStats()) { 254 dispatched.add(new DispatchedNode(node)); 255 getSubscriptionStatistics().getInflightMessageSize().addSize(node.getSize()); 256 } 257 node.decrementReferenceCount(); 258 } 259 break; 260 } 261 } 262 } finally { 263 matched.release(); 264 } 265 } 266 } 267 268 @Override 269 public synchronized void acknowledge(final ConnectionContext context, final MessageAck ack) throws Exception { 270 super.acknowledge(context, ack); 271 272 if (ack.isStandardAck()) { 273 updateStatsOnAck(context, ack); 274 } else if (ack.isPoisonAck()) { 275 if (ack.isInTransaction()) { 276 throw new JMSException("Poison ack cannot be transacted: " + ack); 277 } 278 updateStatsOnAck(context, ack); 279 contractPrefetchExtension(ack.getMessageCount()); 280 } else if (ack.isIndividualAck()) { 281 updateStatsOnAck(context, ack); 282 if (ack.isInTransaction()) { 283 expandPrefetchExtension(1); 284 } 285 } else if (ack.isExpiredAck()) { 286 updateStatsOnAck(ack); 287 contractPrefetchExtension(ack.getMessageCount()); 288 } else if (ack.isDeliveredAck()) { 289 // Message was delivered but not acknowledged: update pre-fetch counters. 290 expandPrefetchExtension(ack.getMessageCount()); 291 } else if (ack.isRedeliveredAck()) { 292 // No processing for redelivered needed 293 return; 294 } else { 295 throw new JMSException("Invalid acknowledgment: " + ack); 296 } 297 298 dispatchMatched(); 299 } 300 301 private void updateStatsOnAck(final ConnectionContext context, final MessageAck ack) { 302 if (context.isInTransaction()) { 303 context.getTransaction().addSynchronization(new Synchronization() { 304 305 @Override 306 public void afterRollback() { 307 contractPrefetchExtension(ack.getMessageCount()); 308 } 309 310 @Override 311 public void afterCommit() throws Exception { 312 contractPrefetchExtension(ack.getMessageCount()); 313 updateStatsOnAck(ack); 314 dispatchMatched(); 315 } 316 }); 317 } else { 318 updateStatsOnAck(ack); 319 } 320 } 321 322 @Override 323 public Response pullMessage(ConnectionContext context, final MessagePull pull) throws Exception { 324 325 // The slave should not deliver pull messages. 326 if (getPrefetchSize() == 0) { 327 328 final long currentDispatchedCount = getSubscriptionStatistics().getDispatched().getCount(); 329 prefetchExtension.set(pull.getQuantity()); 330 dispatchMatched(); 331 332 // If there was nothing dispatched.. we may need to setup a timeout. 333 if (currentDispatchedCount == getSubscriptionStatistics().getDispatched().getCount() || pull.isAlwaysSignalDone()) { 334 335 // immediate timeout used by receiveNoWait() 336 if (pull.getTimeout() == -1) { 337 // Send a NULL message to signal nothing pending. 338 dispatch(null); 339 prefetchExtension.set(0); 340 } 341 342 if (pull.getTimeout() > 0) { 343 scheduler.executeAfterDelay(new Runnable() { 344 345 @Override 346 public void run() { 347 pullTimeout(currentDispatchedCount, pull.isAlwaysSignalDone()); 348 } 349 }, pull.getTimeout()); 350 } 351 } 352 } 353 return null; 354 } 355 356 /** 357 * Occurs when a pull times out. If nothing has been dispatched since the 358 * timeout was setup, then send the NULL message. 359 */ 360 private final void pullTimeout(long currentDispatchedCount, boolean alwaysSendDone) { 361 synchronized (matchedListMutex) { 362 if (currentDispatchedCount == getSubscriptionStatistics().getDispatched().getCount() || alwaysSendDone) { 363 try { 364 dispatch(null); 365 } catch (Exception e) { 366 context.getConnection().serviceException(e); 367 } finally { 368 prefetchExtension.set(0); 369 } 370 } 371 } 372 } 373 374 /** 375 * Update the statistics on message ack. 376 * @param ack 377 */ 378 private void updateStatsOnAck(final MessageAck ack) { 379 //Allow disabling inflight stats to save memory usage 380 if (isUseTopicSubscriptionInflightStats()) { 381 synchronized(dispatchLock) { 382 boolean inAckRange = false; 383 List<DispatchedNode> removeList = new ArrayList<>(); 384 for (final DispatchedNode node : dispatched) { 385 MessageId messageId = node.getMessageId(); 386 if (ack.getFirstMessageId() == null 387 || ack.getFirstMessageId().equals(messageId)) { 388 inAckRange = true; 389 } 390 if (inAckRange) { 391 removeList.add(node); 392 if (ack.getLastMessageId().equals(messageId)) { 393 break; 394 } 395 } 396 } 397 398 for (final DispatchedNode node : removeList) { 399 dispatched.remove(node); 400 getSubscriptionStatistics().getInflightMessageSize().addSize(-node.getSize()); 401 402 final Destination destination = node.getDestination(); 403 incrementStatsOnAck(destination, ack, 1); 404 if (!ack.isInTransaction()) { 405 contractPrefetchExtension(1); 406 } 407 } 408 } 409 } else { 410 if (singleDestination && destination != null) { 411 incrementStatsOnAck(destination, ack, ack.getMessageCount()); 412 } 413 if (!ack.isInTransaction()) { 414 contractPrefetchExtension(ack.getMessageCount()); 415 } 416 } 417 } 418 419 private void incrementStatsOnAck(final Destination destination, final MessageAck ack, final int count) { 420 getSubscriptionStatistics().getDequeues().add(count); 421 destination.getDestinationStatistics().getDequeues().add(count); 422 destination.getDestinationStatistics().getInflight().subtract(count); 423 if (info.isNetworkSubscription()) { 424 destination.getDestinationStatistics().getForwards().add(count); 425 } 426 if (ack.isExpiredAck()) { 427 destination.getDestinationStatistics().getExpired().add(count); 428 } 429 } 430 431 @Override 432 public int countBeforeFull() { 433 return getPrefetchSize() == 0 ? prefetchExtension.get() : info.getPrefetchSize() + prefetchExtension.get() - getDispatchedQueueSize(); 434 } 435 436 @Override 437 public int getPendingQueueSize() { 438 return matched(); 439 } 440 441 @Override 442 public long getPendingMessageSize() { 443 return matched.messageSize(); 444 } 445 446 @Override 447 public int getDispatchedQueueSize() { 448 return (int)(getSubscriptionStatistics().getDispatched().getCount() - 449 getSubscriptionStatistics().getDequeues().getCount()); 450 } 451 452 public int getMaximumPendingMessages() { 453 return maximumPendingMessages; 454 } 455 456 @Override 457 public long getDispatchedCounter() { 458 return getSubscriptionStatistics().getDispatched().getCount(); 459 } 460 461 @Override 462 public long getEnqueueCounter() { 463 return getSubscriptionStatistics().getEnqueues().getCount(); 464 } 465 466 @Override 467 public long getDequeueCounter() { 468 return getSubscriptionStatistics().getDequeues().getCount(); 469 } 470 471 /** 472 * @return the number of messages discarded due to being a slow consumer 473 */ 474 public int discarded() { 475 return discarded.get(); 476 } 477 478 /** 479 * @return the number of matched messages (messages targeted for the 480 * subscription but not yet able to be dispatched due to the 481 * prefetch buffer being full). 482 */ 483 public int matched() { 484 return matched.size(); 485 } 486 487 /** 488 * Sets the maximum number of pending messages that can be matched against 489 * this consumer before old messages are discarded. 490 */ 491 public void setMaximumPendingMessages(int maximumPendingMessages) { 492 this.maximumPendingMessages = maximumPendingMessages; 493 } 494 495 public MessageEvictionStrategy getMessageEvictionStrategy() { 496 return messageEvictionStrategy; 497 } 498 499 /** 500 * Sets the eviction strategy used to decide which message to evict when the 501 * slow consumer needs to discard messages 502 */ 503 public void setMessageEvictionStrategy(MessageEvictionStrategy messageEvictionStrategy) { 504 this.messageEvictionStrategy = messageEvictionStrategy; 505 } 506 507 public synchronized int getMaxProducersToAudit() { 508 return maxProducersToAudit; 509 } 510 511 public synchronized void setMaxProducersToAudit(int maxProducersToAudit) { 512 this.maxProducersToAudit = maxProducersToAudit; 513 if (audit != null) { 514 audit.setMaximumNumberOfProducersToTrack(maxProducersToAudit); 515 } 516 } 517 518 public synchronized int getMaxAuditDepth() { 519 return maxAuditDepth; 520 } 521 522 public synchronized void setMaxAuditDepth(int maxAuditDepth) { 523 this.maxAuditDepth = maxAuditDepth; 524 if (audit != null) { 525 audit.setAuditDepth(maxAuditDepth); 526 } 527 } 528 529 public boolean isEnableAudit() { 530 return enableAudit; 531 } 532 533 public synchronized void setEnableAudit(boolean enableAudit) { 534 this.enableAudit = enableAudit; 535 if (enableAudit && audit == null) { 536 audit = new ActiveMQMessageAudit(maxAuditDepth,maxProducersToAudit); 537 } 538 } 539 540 // Implementation methods 541 // ------------------------------------------------------------------------- 542 @Override 543 public boolean isFull() { 544 return getPrefetchSize() == 0 ? prefetchExtension.get() == 0 : getDispatchedQueueSize() - prefetchExtension.get() >= info.getPrefetchSize(); 545 } 546 547 @Override 548 public int getInFlightSize() { 549 return getDispatchedQueueSize(); 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 (getDispatchedQueueSize() - 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 (getDispatchedQueueSize() - prefetchExtension.get()) >= (info.getPrefetchSize() * .9); 566 } 567 568 /** 569 * @param memoryUsageHighWaterMark the memoryUsageHighWaterMark to set 570 */ 571 public void setMemoryUsageHighWaterMark(int memoryUsageHighWaterMark) { 572 this.memoryUsageHighWaterMark = memoryUsageHighWaterMark; 573 } 574 575 /** 576 * @return the memoryUsageHighWaterMark 577 */ 578 public int getMemoryUsageHighWaterMark() { 579 return this.memoryUsageHighWaterMark; 580 } 581 582 /** 583 * @return the usageManager 584 */ 585 public SystemUsage getUsageManager() { 586 return this.usageManager; 587 } 588 589 /** 590 * @return the matched 591 */ 592 public PendingMessageCursor getMatched() { 593 return this.matched; 594 } 595 596 /** 597 * @param matched the matched to set 598 */ 599 public void setMatched(PendingMessageCursor matched) { 600 this.matched = matched; 601 } 602 603 /** 604 * inform the MessageConsumer on the client to change it's prefetch 605 * 606 * @param newPrefetch 607 */ 608 @Override 609 public void updateConsumerPrefetch(int newPrefetch) { 610 if (context != null && context.getConnection() != null && context.getConnection().isManageable()) { 611 ConsumerControl cc = new ConsumerControl(); 612 cc.setConsumerId(info.getConsumerId()); 613 cc.setPrefetch(newPrefetch); 614 context.getConnection().dispatchAsync(cc); 615 } 616 } 617 618 private void dispatchMatched() throws IOException { 619 synchronized (matchedListMutex) { 620 if (!matched.isEmpty() && !isFull()) { 621 try { 622 matched.reset(); 623 624 while (matched.hasNext() && !isFull()) { 625 MessageReference message = matched.next(); 626 message.decrementReferenceCount(); 627 matched.remove(); 628 // Message may have been sitting in the matched list a while 629 // waiting for the consumer to ak the message. 630 if (message.isExpired()) { 631 discard(message); 632 continue; // just drop it. 633 } 634 dispatch(message); 635 } 636 } finally { 637 matched.release(); 638 } 639 } 640 } 641 } 642 643 private void dispatch(final MessageReference node) throws IOException { 644 Message message = node != null ? node.getMessage() : null; 645 if (node != null) { 646 node.incrementReferenceCount(); 647 } 648 // Make sure we can dispatch a message. 649 MessageDispatch md = new MessageDispatch(); 650 md.setMessage(message); 651 md.setConsumerId(info.getConsumerId()); 652 if (node != null) { 653 md.setDestination(((Destination)node.getRegionDestination()).getActiveMQDestination()); 654 synchronized(dispatchLock) { 655 getSubscriptionStatistics().getDispatched().increment(); 656 if (isUseTopicSubscriptionInflightStats()) { 657 dispatched.add(new DispatchedNode(node)); 658 getSubscriptionStatistics().getInflightMessageSize().addSize(node.getSize()); 659 } 660 } 661 662 // Keep track if this subscription is receiving messages from a single destination. 663 if (singleDestination) { 664 if (destination == null) { 665 destination = (Destination)node.getRegionDestination(); 666 } else { 667 if (destination != node.getRegionDestination()) { 668 singleDestination = false; 669 } 670 } 671 } 672 673 if (getPrefetchSize() == 0) { 674 decrementPrefetchExtension(1); 675 } 676 } 677 678 if (info.isDispatchAsync()) { 679 if (node != null) { 680 md.setTransmitCallback(new TransmitCallback() { 681 682 @Override 683 public void onSuccess() { 684 Destination regionDestination = (Destination) node.getRegionDestination(); 685 regionDestination.getDestinationStatistics().getDispatched().increment(); 686 regionDestination.getDestinationStatistics().getInflight().increment(); 687 node.decrementReferenceCount(); 688 } 689 690 @Override 691 public void onFailure() { 692 Destination regionDestination = (Destination) node.getRegionDestination(); 693 regionDestination.getDestinationStatistics().getDispatched().increment(); 694 regionDestination.getDestinationStatistics().getInflight().increment(); 695 node.decrementReferenceCount(); 696 } 697 }); 698 } 699 context.getConnection().dispatchAsync(md); 700 } else { 701 context.getConnection().dispatchSync(md); 702 if (node != null) { 703 Destination regionDestination = (Destination) node.getRegionDestination(); 704 regionDestination.getDestinationStatistics().getDispatched().increment(); 705 regionDestination.getDestinationStatistics().getInflight().increment(); 706 node.decrementReferenceCount(); 707 } 708 } 709 } 710 711 private void discard(MessageReference message) { 712 discarding = true; 713 try { 714 message.decrementReferenceCount(); 715 matched.remove(message); 716 discarded.incrementAndGet(); 717 if (destination != null) { 718 destination.getDestinationStatistics().getDequeues().increment(); 719 } 720 LOG.debug("{}, discarding message {}", this, message); 721 Destination dest = (Destination) message.getRegionDestination(); 722 if (dest != null) { 723 dest.messageDiscarded(getContext(), this, message); 724 } 725 broker.getRoot().sendToDeadLetterQueue(getContext(), message, this, new Throwable("TopicSubDiscard. ID:" + info.getConsumerId())); 726 } finally { 727 discarding = false; 728 } 729 } 730 731 @Override 732 public String toString() { 733 return "TopicSubscription:" + " consumer=" + info.getConsumerId() + ", destinations=" + destinations.size() + ", dispatched=" + getDispatchedQueueSize() + ", delivered=" 734 + getDequeueCounter() + ", matched=" + matched() + ", discarded=" + discarded() + ", prefetchExtension=" + prefetchExtension.get() 735 + ", usePrefetchExtension=" + isUsePrefetchExtension(); 736 } 737 738 @Override 739 public void destroy() { 740 this.active=false; 741 synchronized (matchedListMutex) { 742 try { 743 matched.destroy(); 744 } catch (Exception e) { 745 LOG.warn("Failed to destroy cursor", e); 746 } 747 } 748 setSlowConsumer(false); 749 synchronized(dispatchLock) { 750 dispatched.clear(); 751 } 752 } 753 754 @Override 755 public int getPrefetchSize() { 756 return info.getPrefetchSize(); 757 } 758 759 @Override 760 public void setPrefetchSize(int newSize) { 761 info.setPrefetchSize(newSize); 762 try { 763 dispatchMatched(); 764 } catch(Exception e) { 765 LOG.trace("Caught exception on dispatch after prefetch size change."); 766 } 767 } 768 769 public boolean isUseTopicSubscriptionInflightStats() { 770 return useTopicSubscriptionInflightStats; 771 } 772 773 public void setUseTopicSubscriptionInflightStats(boolean useTopicSubscriptionInflightStats) { 774 this.useTopicSubscriptionInflightStats = useTopicSubscriptionInflightStats; 775 } 776 777 private static class DispatchedNode { 778 private final int size; 779 private final MessageId messageId; 780 private final Destination destination; 781 782 public DispatchedNode(final MessageReference node) { 783 super(); 784 this.size = node.getSize(); 785 this.messageId = node.getMessageId(); 786 this.destination = node.getRegionDestination() instanceof Destination ? 787 ((Destination)node.getRegionDestination()) : null; 788 } 789 790 public long getSize() { 791 return size; 792 } 793 794 public MessageId getMessageId() { 795 return messageId; 796 } 797 798 public Destination getDestination() { 799 return destination; 800 } 801 } 802 803}