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