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