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}