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.policy;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.concurrent.ConcurrentHashMap;
025
026import org.apache.activemq.broker.Broker;
027import org.apache.activemq.broker.ConnectionContext;
028import org.apache.activemq.broker.region.Destination;
029import org.apache.activemq.broker.region.Subscription;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * Abort slow consumers when they reach the configured threshold of slowness,
035 *
036 * default is that a consumer that has not Ack'd a message for 30 seconds is slow.
037 *
038 * @org.apache.xbean.XBean
039 */
040public class AbortSlowAckConsumerStrategy extends AbortSlowConsumerStrategy {
041
042    private static final Logger LOG = LoggerFactory.getLogger(AbortSlowAckConsumerStrategy.class);
043
044    private final Map<String, Destination> destinations = new ConcurrentHashMap<String, Destination>();
045    private long maxTimeSinceLastAck = 30*1000;
046    private boolean ignoreIdleConsumers = true;
047    private boolean ignoreNetworkConsumers = true;
048
049    public AbortSlowAckConsumerStrategy() {
050        this.name = "AbortSlowAckConsumerStrategy@" + hashCode();
051    }
052
053    @Override
054    public void setBrokerService(Broker broker) {
055        super.setBrokerService(broker);
056
057        // Task starts right away since we may not receive any slow consumer events.
058        if (taskStarted.compareAndSet(false, true)) {
059            scheduler.executePeriodically(this, getCheckPeriod());
060        }
061    }
062
063    @Override
064    public void slowConsumer(ConnectionContext context, Subscription subs) {
065        // Ignore these events, we just look at time since last Ack.
066    }
067
068    @Override
069    public void run() {
070
071        if (maxTimeSinceLastAck < 0) {
072            // nothing to do
073            LOG.info("no limit set, slowConsumer strategy has nothing to do");
074            return;
075        }
076
077        if (getMaxSlowDuration() > 0) {
078            // For subscriptions that are already slow we mark them again and check below if
079            // they've exceeded their configured lifetime.
080            for (SlowConsumerEntry entry : slowConsumers.values()) {
081                entry.mark();
082            }
083        }
084
085        List<Destination> disposed = new ArrayList<Destination>();
086
087        for (Destination destination : destinations.values()) {
088            if (destination.isDisposed()) {
089                disposed.add(destination);
090                continue;
091            }
092
093            // Not explicitly documented but this returns a stable copy.
094            List<Subscription> subscribers = destination.getConsumers();
095
096            updateSlowConsumersList(subscribers);
097        }
098
099        // Clean up an disposed destinations to save space.
100        for (Destination destination : disposed) {
101            destinations.remove(destination.getName());
102        }
103
104        abortAllQualifiedSlowConsumers();
105    }
106
107    private void updateSlowConsumersList(List<Subscription> subscribers) {
108        for (Subscription subscriber : subscribers) {
109            if (isIgnoreNetworkSubscriptions() && subscriber.getConsumerInfo().isNetworkSubscription()) {
110                if (slowConsumers.remove(subscriber) != null) {
111                    LOG.info("network sub: {} is no longer slow", subscriber.getConsumerInfo().getConsumerId());
112                }
113                continue;
114            }
115
116            if (isIgnoreIdleConsumers() && subscriber.getDispatchedQueueSize() == 0) {
117                // Not considered Idle so ensure its cleared from the list
118                if (slowConsumers.remove(subscriber) != null) {
119                    LOG.info("idle sub: {} is no longer slow", subscriber.getConsumerInfo().getConsumerId());
120                }
121                continue;
122            }
123
124            long lastAckTime = subscriber.getTimeOfLastMessageAck();
125            long timeDelta = System.currentTimeMillis() - lastAckTime;
126
127            if (timeDelta > maxTimeSinceLastAck) {
128                if (!slowConsumers.containsKey(subscriber)) {
129                    LOG.debug("sub: {} is now slow", subscriber.getConsumerInfo().getConsumerId());
130                    SlowConsumerEntry entry = new SlowConsumerEntry(subscriber.getContext());
131                    entry.mark(); // mark consumer on first run
132                    slowConsumers.put(subscriber, entry);
133                } else if (getMaxSlowCount() > 0) {
134                    slowConsumers.get(subscriber).slow();
135                }
136            } else {
137                if (slowConsumers.remove(subscriber) != null) {
138                    LOG.info("sub: {} is no longer slow", subscriber.getConsumerInfo().getConsumerId());
139                }
140            }
141        }
142    }
143
144    private void abortAllQualifiedSlowConsumers() {
145        HashMap<Subscription, SlowConsumerEntry> toAbort = new HashMap<Subscription, SlowConsumerEntry>();
146        for (Entry<Subscription, SlowConsumerEntry> entry : slowConsumers.entrySet()) {
147            if (getMaxSlowDuration() > 0 && (entry.getValue().markCount * getCheckPeriod() >= getMaxSlowDuration()) ||
148                getMaxSlowCount() > 0 && entry.getValue().slowCount >= getMaxSlowCount()) {
149
150                LOG.trace("Transferring consumer{} to the abort list: {} slow duration = {}, slow count = {}",
151                        new Object[]{ entry.getKey().getConsumerInfo().getConsumerId(),
152                        entry.getValue().markCount * getCheckPeriod(),
153                        entry.getValue().getSlowCount() });
154
155                toAbort.put(entry.getKey(), entry.getValue());
156                slowConsumers.remove(entry.getKey());
157            } else {
158
159                LOG.trace("Not yet time to abort consumer {}: slow duration = {}, slow count = {}", new Object[]{ entry.getKey().getConsumerInfo().getConsumerId(), entry.getValue().markCount * getCheckPeriod(), entry.getValue().slowCount });
160
161            }
162        }
163
164        // Now if any subscriptions made it into the aborts list we can kick them.
165        abortSubscription(toAbort, isAbortConnection());
166    }
167
168    @Override
169    public void addDestination(Destination destination) {
170        this.destinations.put(destination.getName(), destination);
171    }
172
173    /**
174     * Gets the maximum time since last Ack before a subscription is considered to be slow.
175     *
176     * @return the maximum time since last Ack before the consumer is considered to be slow.
177     */
178    public long getMaxTimeSinceLastAck() {
179        return maxTimeSinceLastAck;
180    }
181
182    /**
183     * Sets the maximum time since last Ack before a subscription is considered to be slow.
184     *
185     * @param maxTimeSinceLastAck
186     *      the maximum time since last Ack (mills) before the consumer is considered to be slow.
187     */
188    public void setMaxTimeSinceLastAck(long maxTimeSinceLastAck) {
189        this.maxTimeSinceLastAck = maxTimeSinceLastAck;
190    }
191
192    /**
193     * Returns whether the strategy is configured to ignore consumers that are simply idle, i.e
194     * consumers that have no pending acks (dispatch queue is empty).
195     *
196     * @return true if the strategy will ignore idle consumer when looking for slow consumers.
197     */
198    public boolean isIgnoreIdleConsumers() {
199        return ignoreIdleConsumers;
200    }
201
202    /**
203     * Sets whether the strategy is configured to ignore consumers that are simply idle, i.e
204     * consumers that have no pending acks (dispatch queue is empty).
205     *
206     * When configured to not ignore idle consumers this strategy acks not only on consumers
207     * that are actually slow but also on any consumer that has not received any messages for
208     * the maxTimeSinceLastAck.  This allows for a way to evict idle consumers while also
209     * aborting slow consumers.
210     *
211     * @param ignoreIdleConsumers
212     *      Should this strategy ignore idle consumers or consider all consumers when checking
213     *      the last ack time verses the maxTimeSinceLastAck value.
214     */
215    public void setIgnoreIdleConsumers(boolean ignoreIdleConsumers) {
216        this.ignoreIdleConsumers = ignoreIdleConsumers;
217    }
218
219    /**
220     * Returns whether the strategy is configured to ignore subscriptions that are from a network
221     * connection.
222     *
223     * @return true if the strategy will ignore network connection subscriptions when looking
224     *         for slow consumers.
225     */
226    public boolean isIgnoreNetworkSubscriptions() {
227        return ignoreNetworkConsumers;
228    }
229
230    /**
231     * Sets whether the strategy is configured to ignore consumers that are part of a network
232     * connection to another broker.
233     *
234     * When configured to not ignore idle consumers this strategy acts not only on consumers
235     * that are actually slow but also on any consumer that has not received any messages for
236     * the maxTimeSinceLastAck.  This allows for a way to evict idle consumers while also
237     * aborting slow consumers however for a network subscription this can create a lot of
238     * unnecessary churn and if the abort connection option is also enabled this can result
239     * in the entire network connection being torn down and rebuilt for no reason.
240     *
241     * @param ignoreNetworkConsumers
242     *      Should this strategy ignore subscriptions made by a network connector.
243     */
244    public void setIgnoreNetworkConsumers(boolean ignoreNetworkConsumers) {
245        this.ignoreNetworkConsumers = ignoreNetworkConsumers;
246    }
247
248}