package net.greencoding.thysdrus.circuitbreaker.core;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import net.greencoding.thysdrus.circuitbreaker.core.model.CircuitBreaker;
import net.greencoding.thysdrus.circuitbreaker.core.model.CircuitBreakerEntry;
import net.greencoding.thysdrus.circuitbreaker.core.model.CircuitBreakerRegisterResult;
import net.greencoding.thysdrus.circuitbreaker.core.model.CircuitBreakerStatus;
import net.greencoding.thysdrus.circuitbreaker.event.CircuitBreakerRegisteredEvent;
import net.greencoding.thysdrus.event.core.DefaultEventHub;
import net.greencoding.thysdrus.event.core.Event;
import net.greencoding.thysdrus.event.core.EventHub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Circuit Breaker Registry. It manages all circuit breakers.
*
* @author Nabil Ben Said (nabil.bensaid@gmail.com)
*
*/
public final class DefaultCircuitBreakerRegistry implements CircuitBreakerRegistry {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final String name;
private Map<String, CircuitBreakerEntry> registry = new HashMap<String, CircuitBreakerEntry>();
private EventHub eventHub = DefaultEventHub.getSingelton();
protected DefaultCircuitBreakerRegistry(String name) {
this.name = name;
}
/*
* (non-Javadoc)
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#halfOpenConditionSatisfied(java.lang.String)
*/
@Override
public boolean halfOpenConditionSatisfied(String circuitBreakerKey){
CircuitBreakerEntry entry = registry.get(circuitBreakerKey);
if (entry.getStatus().equals(CircuitBreakerStatus.OPEN) &&
System.currentTimeMillis() - entry.getLastOpenedTimestamp() >= entry.getCircuitBreaker().getRetryTimeoutMs()) {
logger.info("condition for HALF-OPEN state is satisfied. CB-key: {}", circuitBreakerKey);
// condition for half open is satisfied
return true;
}
return false;
}
/*
* (non-Javadoc)
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#isRegistered(java.lang.String)
*/
@Override
public boolean isRegistered(String circuitBreakerKey){
return registry.containsKey(circuitBreakerKey);
}
/*
* (non-Javadoc)
*
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#
* registerCircuitBreaker
* (net.greencoding.thysdrus.circuitbreaker.core.model.CircuitBreaker)
*/
@Override
public CircuitBreakerRegisterResult registerCircuitBreaker(CircuitBreaker circuitBreaker) {
assert circuitBreaker != null;
logger.info("register CB: {}", circuitBreaker);
CircuitBreakerEntry entry = null;
synchronized (this) {
entry = registry.get(circuitBreaker.getCircuitBreakerKey());
if (entry != null) {
logger.warn("you are trying to register CB twice. {}", circuitBreaker);
return new CircuitBreakerRegisterResult(false, "CB is already registered");
}
entry = new CircuitBreakerEntry(circuitBreaker);
registry.put(circuitBreaker.getCircuitBreakerKey(), entry);
}
logger.info("successfully registered CB under CB-Entry {}", entry);
Event event = new CircuitBreakerRegisteredEvent();
eventHub.publishEvent(event);
return new CircuitBreakerRegisterResult(true, "CB successfully registered");
}
/*
* (non-Javadoc)
*
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#
* getCircuitBreakerStatus(java.lang.String)
*/
@Override
public CircuitBreakerStatus getCircuitBreakerStatus(String circuitBreakerKey) {
CircuitBreakerEntry entry = registry.get(circuitBreakerKey);
logger.info("CircuitBreakerEntry: {}", entry);
if (entry == null) {
throw new IllegalStateException("No CircuitBreakerEntry found for key: " + circuitBreakerKey);
}
CircuitBreakerStatus status = entry.getStatus();
logger.info("Status from CB with key {} is {}", circuitBreakerKey, status);
return status;
}
/*
* (non-Javadoc)
*
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#
* tryHalfOpenLock(java.lang.String)
*/
@Override
public boolean tryHalfOpenLock(String circuitBreakerKey) {
CircuitBreakerEntry entry = registry.get(circuitBreakerKey);
Lock lock = entry.getHalfOpenLock();
boolean successfully = lock.tryLock();
if (successfully) {
logger.info("successfully occured half-open lock by {}", circuitBreakerKey);
} else {
logger.info("CB {} coudn't occure half-open lock ", circuitBreakerKey);
}
return successfully;
}
/*
* (non-Javadoc)
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#releaseHalfOpenLock(java.lang.String, net.greencoding.thysdrus.circuitbreaker.core.model.CircuitBreakerStatus)
*/
@Override
public boolean releaseHalfOpenLock(String circuitBreakerKey, CircuitBreakerStatus status) {
CircuitBreakerEntry entry = registry.get(circuitBreakerKey);
if (entry == null) {
throw new IllegalStateException("No CB-Entry found with CB key " + circuitBreakerKey);
}
if (!entry.getStatus().equals(CircuitBreakerStatus.HALF_OPEN)) {
logger.warn("CB with key {} is not in HALF-OPEN status currently.");
return false;
}
synchronized (entry) {
Lock lock = entry.getHalfOpenLock();
lock.unlock();
entry.setStatus(status);
}
return true;
}
/*
* (non-Javadoc)
*
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#
* resetCircuitBreaker(java.lang.String)
*/
@Override
public void resetCircuitBreaker(String circuitBreakerKey) {
CircuitBreakerEntry entry = registry.get(circuitBreakerKey);
if (entry == null) {
logger.warn("no CB Entry found for key {}", circuitBreakerKey);
}
entry.setStatus(CircuitBreakerStatus.CLOSED);
}
/*
* (non-Javadoc)
*
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#
* unregisterCircuitBreaker(java.lang.String)
*/
@Override
public void unregisterCircuitBreaker(String circuitBreakerKey) {
CircuitBreakerEntry entry = registry.remove(circuitBreakerKey);
if (entry == null) {
logger.warn("no CB Entry found for key {}", circuitBreakerKey);
} else {
logger.info("removed CB Entry with key {} from registry.", circuitBreakerKey);
}
}
/*
* (non-Javadoc)
* @see net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#handleMethodInvocationException(java.lang.String, java.lang.Throwable)
*/
@Override
public boolean handleMethodInvocationException(final String circuitBreakerKey, final Throwable cause, final boolean inHalfOpenStatus) {
boolean isFailure = false;
synchronized (this) {
CircuitBreakerEntry entry = registry.get(circuitBreakerKey);
isFailure = isFailureIndicator(entry.getCircuitBreaker().getFailureIndications(), cause);
if (isFailure) {
long now = System.currentTimeMillis();
entry.addFailureTimestamp(now);
if (inHalfOpenStatus){
releaseHalfOpenLock(circuitBreakerKey, CircuitBreakerStatus.OPEN);
entry.setLastOpenedTimestamp(now);
} else if (openConditionSatisfied(entry, now)) {
entry.setStatus(CircuitBreakerStatus.OPEN);
entry.setLastOpenedTimestamp(now);
entry.getLastFailureTimestamps().clear();
}
// TODO fire stats events
}
}
return isFailure;
}
private boolean openConditionSatisfied(CircuitBreakerEntry entry, long now) {
if (!entry.getStatus().equals(CircuitBreakerStatus.OPEN) &&
entry.getLastFailureTimestamps().size() == entry.getCircuitBreaker().getFailureThreshold() &&
((now - entry.getLastFailureTimestamps().get(0)) <= entry.getCircuitBreaker().getFailureThresholdTimeFrameMs()) ) {
// open condition is satisfied
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* net.greencoding.thysdrus.circuitbreaker.core.CircuitBreakerRegistry#destroy
* ()
*/
@Override
public void destroy() {
registry.clear();
}
public String getName() {
return name;
}
private boolean isFailureIndicator(Class<? extends Throwable>[] failureIndications, Throwable cause) {
for (Class<? extends Throwable> clazz : failureIndications){
if (clazz.isAssignableFrom(cause.getClass())) {
return true;
}
}
return false;
}
}