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.camel.processor.loadbalancer;
018
019import java.util.List;
020import java.util.concurrent.RejectedExecutionException;
021import java.util.concurrent.atomic.AtomicInteger;
022
023import org.apache.camel.AsyncCallback;
024import org.apache.camel.AsyncProcessor;
025import org.apache.camel.CamelContext;
026import org.apache.camel.CamelContextAware;
027import org.apache.camel.Exchange;
028import org.apache.camel.Processor;
029import org.apache.camel.Traceable;
030import org.apache.camel.util.AsyncProcessorConverterHelper;
031
032@Deprecated
033public class CircuitBreakerLoadBalancer extends LoadBalancerSupport implements Traceable, CamelContextAware {
034    private static final int STATE_CLOSED = 0;
035    private static final int STATE_HALF_OPEN = 1;
036    private static final int STATE_OPEN = 2;
037
038    private final List<Class<?>> exceptions;
039    private CamelContext camelContext;
040    private int threshold;
041    private long halfOpenAfter;
042    private long lastFailure;
043
044    // stateful statistics
045    private AtomicInteger failures = new AtomicInteger();
046    private AtomicInteger state = new AtomicInteger(STATE_CLOSED);
047    private final ExceptionFailureStatistics statistics = new ExceptionFailureStatistics();
048
049    public CircuitBreakerLoadBalancer() {
050        this(null);
051    }
052
053    public CircuitBreakerLoadBalancer(List<Class<?>> exceptions) {
054        this.exceptions = exceptions;
055        statistics.init(exceptions);
056    }
057
058    public void setHalfOpenAfter(long halfOpenAfter) {
059        this.halfOpenAfter = halfOpenAfter;
060    }
061
062    public long getHalfOpenAfter() {
063        return halfOpenAfter;
064    }
065
066    public void setThreshold(int threshold) {
067        this.threshold = threshold;
068    }
069
070    public int getThreshold() {
071        return threshold;
072    }
073
074    public int getState() {
075        return state.get();
076    }
077
078    @Override
079    public CamelContext getCamelContext() {
080        return camelContext;
081    }
082
083    @Override
084    public void setCamelContext(CamelContext camelContext) {
085        this.camelContext = camelContext;
086    }
087
088    public List<Class<?>> getExceptions() {
089        return exceptions;
090    }
091
092    /**
093     * Has the given Exchange failed
094     */
095    protected boolean hasFailed(Exchange exchange) {
096        if (exchange == null) {
097            return false;
098        }
099
100        boolean answer = false;
101
102        if (exchange.getException() != null) {
103            if (exceptions == null || exceptions.isEmpty()) {
104                // always failover if no exceptions defined
105                answer = true;
106            } else {
107                for (Class<?> exception : exceptions) {
108                    // will look in exception hierarchy
109                    if (exchange.getException(exception) != null) {
110                        answer = true;
111                        break;
112                    }
113                }
114            }
115
116            if (answer) {
117                // record the failure in the statistics
118                statistics.onHandledFailure(exchange.getException());
119            }
120        }
121
122        log.trace("Failed: {} for exchangeId: {}", answer, exchange.getExchangeId());
123
124        return answer;
125    }
126
127    @Override
128    public boolean isRunAllowed() {
129        boolean forceShutdown = camelContext.getShutdownStrategy().forceShutdown(this);
130        if (forceShutdown) {
131            log.trace("Run not allowed as ShutdownStrategy is forcing shutting down");
132        }
133        return !forceShutdown && super.isRunAllowed();
134    }
135
136    public boolean process(final Exchange exchange, final AsyncCallback callback) {
137
138        // can we still run
139        if (!isRunAllowed()) {
140            log.trace("Run not allowed, will reject executing exchange: {}", exchange);
141            if (exchange.getException() == null) {
142                exchange.setException(new RejectedExecutionException("Run is not allowed"));
143            }
144            callback.done(true);
145            return true;
146        }
147
148        return calculateState(exchange, callback);
149    }
150
151    private boolean calculateState(final Exchange exchange, final AsyncCallback callback) {
152        boolean output;
153        if (state.get() == STATE_HALF_OPEN) {
154            if (failures.get() == 0) {
155                output = closeCircuit(exchange, callback);
156            } else {
157                output = openCircuit(exchange, callback);
158            }
159        } else if (state.get() == STATE_OPEN) {
160            if (failures.get() >= threshold && System.currentTimeMillis() - lastFailure < halfOpenAfter) {
161                output = openCircuit(exchange, callback);
162            } else {
163                output = halfOpenCircuit(exchange, callback);
164            }
165        } else if (state.get() == STATE_CLOSED) {
166            if (failures.get() >= threshold && System.currentTimeMillis() - lastFailure < halfOpenAfter) {
167                output = openCircuit(exchange, callback);
168            } else if (failures.get() >= threshold && System.currentTimeMillis() - lastFailure >= halfOpenAfter) {
169                output = halfOpenCircuit(exchange, callback);
170            } else {
171                output = closeCircuit(exchange, callback);
172            }
173        } else {
174            throw new IllegalStateException("Unrecognised circuitBreaker state " + state.get());
175        }
176        return output;
177    }
178
179    private boolean openCircuit(final Exchange exchange, final AsyncCallback callback) {
180        boolean output = rejectExchange(exchange, callback);
181        state.set(STATE_OPEN);
182        logState();
183        return output;
184    }
185
186    private boolean halfOpenCircuit(final Exchange exchange, final AsyncCallback callback) {
187        boolean output = executeProcessor(exchange, callback);
188        state.set(STATE_HALF_OPEN);
189        logState();
190        return output;
191    }
192
193    private boolean closeCircuit(final Exchange exchange, final AsyncCallback callback) {
194        boolean output = executeProcessor(exchange, callback);
195        state.set(STATE_CLOSED);
196        logState();
197        return output;
198    }
199
200    private void logState() {
201        if (log.isDebugEnabled()) {
202            log.debug(dumpState());
203        }
204    }
205
206    public String dumpState() {
207        int num = state.get();
208        String state = stateAsString(num);
209        if (lastFailure > 0) {
210            return String.format("State %s, failures %d, closed since %d", state, failures.get(), System.currentTimeMillis() - lastFailure);
211        } else {
212            return String.format("State %s, failures %d", state, failures.get());
213        }
214    }
215
216    private boolean executeProcessor(final Exchange exchange, final AsyncCallback callback) {
217        Processor processor = getProcessors().get(0);
218        if (processor == null) {
219            throw new IllegalStateException("No processors could be chosen to process CircuitBreaker");
220        }
221
222        // store state as exchange property
223        exchange.setProperty(Exchange.CIRCUIT_BREAKER_STATE, stateAsString(state.get()));
224
225        AsyncProcessor albp = AsyncProcessorConverterHelper.convert(processor);
226        // Added a callback for processing the exchange in the callback
227        boolean sync = albp.process(exchange, new CircuitBreakerCallback(exchange, callback));
228
229        // We need to check the exception here as albp is use sync call
230        if (sync) {
231            boolean failed = hasFailed(exchange);
232            if (!failed) {
233                failures.set(0);
234            } else {
235                failures.incrementAndGet();
236                lastFailure = System.currentTimeMillis();
237            }
238        } else {
239            // CircuitBreakerCallback can take care of failure check of the
240            // exchange
241            log.trace("Processing exchangeId: {} is continued being processed asynchronously", exchange.getExchangeId());
242            return false;
243        }
244
245        log.trace("Processing exchangeId: {} is continued being processed synchronously", exchange.getExchangeId());
246        callback.done(true);
247        return true;
248    }
249
250    private boolean rejectExchange(final Exchange exchange, final AsyncCallback callback) {
251        exchange.setException(new RejectedExecutionException("CircuitBreaker Open: failures: " + failures + ", lastFailure: " + lastFailure));
252        callback.done(true);
253        return true;
254    }
255
256    private static String stateAsString(int num) {
257        if (num == STATE_CLOSED) {
258            return "closed";
259        } else if (num == STATE_HALF_OPEN) {
260            return "half opened";
261        } else {
262            return "opened";
263        }
264    }
265
266    public String toString() {
267        return "CircuitBreakerLoadBalancer[" + getProcessors() + "]";
268    }
269
270    public String getTraceLabel() {
271        return "circuitbreaker";
272    }
273
274    public ExceptionFailureStatistics getExceptionFailureStatistics() {
275        return statistics;
276    }
277
278    public void reset() {
279        // reset state
280        failures.set(0);
281        state.set(STATE_CLOSED);
282        statistics.reset();
283    }
284
285    @Override
286    protected void doStart() throws Exception {
287        super.doStart();
288
289        // reset state
290        reset();
291    }
292
293    @Override
294    protected void doStop() throws Exception {
295        super.doStop();
296        // noop
297    }
298
299
300    class CircuitBreakerCallback implements AsyncCallback {
301        private final AsyncCallback callback;
302        private final Exchange exchange;
303
304        CircuitBreakerCallback(Exchange exchange, AsyncCallback callback) {
305            this.callback = callback;
306            this.exchange = exchange;
307        }
308
309        @Override
310        public void done(boolean doneSync) {
311            if (!doneSync) {
312                boolean failed = hasFailed(exchange);
313                if (!failed) {
314                    failures.set(0);
315                } else {
316                    failures.incrementAndGet();
317                    lastFailure = System.currentTimeMillis();
318                }
319            }
320            callback.done(doneSync);
321        }
322
323    }
324}