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     */
017    package org.apache.camel.impl;
018    
019    import java.util.concurrent.locks.Lock;
020    import java.util.concurrent.locks.ReentrantLock;
021    
022    import org.apache.camel.Consumer;
023    import org.apache.camel.Endpoint;
024    import org.apache.camel.Exchange;
025    import org.apache.camel.LoggingLevel;
026    import org.apache.camel.Route;
027    import org.apache.camel.processor.Logger;
028    import org.apache.commons.logging.LogFactory;
029    
030    /**
031     * A throttle based {@link org.apache.camel.spi.RoutePolicy} which is capable of dynamic
032     * throttling a route based on number of current inflight exchanges.
033     *
034     * @version $Revision: 832342 $
035     */
036    public class ThrottlingInflightRoutePolicy extends RoutePolicySupport {
037    
038        public enum ThrottlingScope {
039            Context, Route
040        }
041    
042        private final Lock lock = new ReentrantLock();
043        private ThrottlingScope scope = ThrottlingScope.Route;
044        private int maxInflightExchanges = 1000;
045        private int resumePercentOfMax = 70;
046        private int resumeInflightExchanges = 700;
047        private LoggingLevel loggingLevel = LoggingLevel.INFO;
048        private Logger logger;
049    
050        public ThrottlingInflightRoutePolicy() {
051        }
052    
053        @Override
054        public String toString() {
055            return "ThrottlingInflightRoutePolicy[" + maxInflightExchanges + " / " + resumePercentOfMax + "% using scope " + scope + "]";
056        }
057    
058        public void onExchangeDone(Route route, Exchange exchange) {
059            // this works the best when this logic is executed when the exchange is done
060            Consumer consumer = route.getConsumer();
061    
062            int size = getSize(consumer, exchange);
063            if (maxInflightExchanges > 0 && size > maxInflightExchanges) {
064                try {
065                    lock.lock();
066                    stopConsumer(size, consumer);
067                } catch (Exception e) {
068                    handleException(e);
069                } finally {
070                    lock.unlock();
071                }
072            }
073    
074            // reload size in case a race condition with too many at once being invoked
075            // so we need to ensure that we read the most current size and start the consumer if we are already to low
076            size = getSize(consumer, exchange);
077            if (size <= resumeInflightExchanges) {
078                try {
079                    lock.lock();
080                    startConsumer(size, consumer);
081                } catch (Exception e) {
082                    handleException(e);
083                } finally {
084                    lock.unlock();
085                }
086            }
087        }
088    
089        public int getMaxInflightExchanges() {
090            return maxInflightExchanges;
091        }
092    
093        /**
094         * Sets the upper limit of number of concurrent inflight exchanges at which point reached
095         * the throttler should suspend the route.
096         * <p/>
097         * Is default 1000.
098         *
099         * @param maxInflightExchanges the upper limit of concurrent inflight exchanges
100         */
101        public void setMaxInflightExchanges(int maxInflightExchanges) {
102            this.maxInflightExchanges = maxInflightExchanges;
103            // recalculate, must be at least at 1
104            this.resumeInflightExchanges = Math.max(resumePercentOfMax * maxInflightExchanges / 100, 1);
105        }
106    
107        public int getResumePercentOfMax() {
108            return resumePercentOfMax;
109        }
110    
111        /**
112         * Sets at which percentage of the max the throttler should start resuming the route.
113         * <p/>
114         * Will by default use 70%.
115         *
116         * @param resumePercentOfMax the percentage must be between 0 and 100
117         */
118        public void setResumePercentOfMax(int resumePercentOfMax) {
119            if (resumePercentOfMax < 0 || resumePercentOfMax > 100) {
120                throw new IllegalArgumentException("Must be a percentage between 0 and 100, was: " + resumePercentOfMax);
121            }
122    
123            this.resumePercentOfMax = resumePercentOfMax;
124            // recalculate, must be at least at 1
125            this.resumeInflightExchanges = Math.max(resumePercentOfMax * maxInflightExchanges / 100, 1);
126        }
127    
128        public ThrottlingScope getScope() {
129            return scope;
130        }
131    
132        /**
133         * Sets which scope the throttling should be based upon, either route or total scoped.
134         *
135         * @param scope the scope
136         */
137        public void setScope(ThrottlingScope scope) {
138            this.scope = scope;
139        }
140    
141        public LoggingLevel getLoggingLevel() {
142            return loggingLevel;
143        }
144    
145        public Logger getLogger() {
146            if (logger == null) {
147                logger = createLogger();
148            }
149            return logger;
150        }
151    
152        /**
153         * Sets the logger to use for logging throttling activity.
154         *
155         * @param logger the logger
156         */
157        public void setLogger(Logger logger) {
158            this.logger = logger;
159        }
160    
161        /**
162         * Sets the logging level to report the throttling activity.
163         * <p/>
164         * Is default <tt>INFO</tt> level.
165         *
166         * @param loggingLevel the logging level
167         */
168        public void setLoggingLevel(LoggingLevel loggingLevel) {
169            this.loggingLevel = loggingLevel;
170        }
171    
172        protected Logger createLogger() {
173            return new Logger(LogFactory.getLog(ThrottlingInflightRoutePolicy.class), getLoggingLevel());
174        }
175    
176        private int getSize(Consumer consumer, Exchange exchange) {
177            if (scope == ThrottlingScope.Context) {
178                return exchange.getContext().getInflightRepository().size();
179            } else {
180                Endpoint endpoint = consumer.getEndpoint();
181                return exchange.getContext().getInflightRepository().size(endpoint);
182            }
183        }
184    
185        private void startConsumer(int size, Consumer consumer) throws Exception {
186            boolean started = super.startConsumer(consumer);
187            if (started) {
188                getLogger().log("Throttling consumer: " + size + " <= " + resumeInflightExchanges + " inflight exchange by resuming consumer.");
189            }
190        }
191    
192        private void stopConsumer(int size, Consumer consumer) throws Exception {
193            boolean stopped = super.stopConsumer(consumer);
194            if (stopped) {
195                getLogger().log("Throttling consumer: " + size + " > " + maxInflightExchanges + " inflight exchange by suspending consumer.");
196            }
197        }
198    
199    
200    }