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 }