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