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