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}