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