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