com.ajjpj.afoundation.concurrent.ACircuitBreaker Maven / Gradle / Ivy
The newest version!
package com.ajjpj.afoundation.concurrent;
import com.ajjpj.afoundation.collection.ACollectionHelper;
import com.ajjpj.afoundation.function.AFunction1;
import com.ajjpj.afoundation.function.AFunction1NoThrow;
import com.ajjpj.afoundation.function.AStatement2NoThrow;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author arno
*/
public class ACircuitBreaker implements ATaskScheduler {
private final ATaskScheduler threadPool;
private final AtomicLong retryAt = new AtomicLong ();
private final AtomicInteger numFailures = new AtomicInteger ();
private final int maxNumFailures;
private final long recoveryMillis;
@SuppressWarnings ("unused")
public ACircuitBreaker (ATaskScheduler threadPool) {
this (threadPool, 3, 1, TimeUnit.MINUTES);
}
public ACircuitBreaker (ATaskScheduler threadPool, int maxNumFailures, long recoveryDelay, TimeUnit recoveryTimeUnit) {
this.threadPool = threadPool;
this.maxNumFailures = maxNumFailures;
this.recoveryMillis = recoveryTimeUnit.toMillis (recoveryDelay);
}
@Override public AFuture submit (Callable task, long timeout, TimeUnit timeoutUnit) {
if (shouldSubmit ()) {
return withCircuitBreaker (threadPool.submit (task, timeout, timeoutUnit));
}
return failure();
}
@Override public AFuture submit (Runnable task, T result, long timeout, TimeUnit timeoutUnit) {
if (shouldSubmit ()) {
return withCircuitBreaker (threadPool.submit (task, result, timeout, timeoutUnit));
}
return failure();
}
@Override public List> submitAll (List params, AFunction1 taskFunction, long timeout, TimeUnit timeoutUnit) {
if (shouldSubmit ()) {
final List> raw = threadPool.submitAll (params, taskFunction, timeout, timeoutUnit);
return ACollectionHelper.map (raw, new AFunction1NoThrow, AFuture> () {
@Override public AFuture apply (AFuture param) {
return withCircuitBreaker (param);
}
});
}
return failureList (params.size ());
}
@Override public List> submitAllWithDefaultValue (List params, AFunction1 taskFunction, long timeout, TimeUnit timeoutUnit, R defaultValue) {
if (shouldSubmit ()) {
final List> raw = threadPool.submitAllWithDefaultValue (params, taskFunction, timeout, timeoutUnit, defaultValue);
return ACollectionHelper.map (raw, new AFunction1NoThrow, AFuture> () {
@Override public AFuture apply (AFuture param) {
return withCircuitBreaker (param);
}
});
}
return failureList (params.size ());
}
private List> failureList (int size) {
final List> result = new ArrayList<> ();
for (int i=0; ifailure ());
}
return result;
}
private AFuture failure () {
final AFutureImpl result = AFutureImpl.unscheduled (threadPool);
result.setException (new RejectedByCircuitBreakerException ("recovering from previous failures (CircuitBreaker)"));
return result;
}
private boolean shouldSubmit () {
if (numFailures.get () < maxNumFailures) {
return true;
}
//store 'System.currentTimeMillis()' in a variable for speedup: we can reuse the value below
final long now = System.currentTimeMillis ();
// we need to remember this initial value of 'retryAt' for both the initial check and the later compareAndSet(), otherwise we would have a race condition
final long retryAtValue = retryAt.get ();
//noinspection SimplifiableIfStatement
if (retryAtValue > now) {
return false;
}
return retryAt.compareAndSet (retryAtValue, now + recoveryMillis);
}
private AFuture withCircuitBreaker (AFuture f) {
f.onFinished (new AStatement2NoThrow () {
@Override public void apply (T result, Throwable th) {
if (th != null) {
numFailures.incrementAndGet ();
retryAt.set (System.currentTimeMillis () + recoveryMillis);
}
else {
numFailures.set (0);
}
}
});
return f;
}
}