/*
* Copyright 2016 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.apiman.plugins.circuit_breaker;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
* @author eric.wittmann@gmail.com
*/
public class Circuit {
private final int limit;
private final int timeWindowMillis;
private final int resetMillis;
private boolean broken;
private Set<CircuitFault> faults = new HashSet<>();
private Date resetOn;
private Date hardResetOn;
private boolean resetting;
/**
* Constructor.
* @param limit the # of faults that will trip the circuit
* @param timeWindow the time window in seconds
* @param reset the reset time in seconds
*/
public Circuit(int limit, int timeWindow, int reset) {
this.limit = limit;
this.timeWindowMillis = timeWindow * 1000;
this.resetMillis = reset * 1000;
}
/**
* @return the broken
*/
public boolean isBroken() {
if (this.hardResetOn != null) {
long now = System.currentTimeMillis();
if (now >= this.hardResetOn.getTime()) {
reset();
}
}
return broken;
}
/**
* Trip (open) the circuit.
*/
public void trip() {
synchronized (faults) {
this.resetOn = new Date(System.currentTimeMillis() + this.resetMillis);
if (this.hardResetOn == null) {
this.hardResetOn = new Date(System.currentTimeMillis() + this.resetMillis * 10);
}
this.broken = true;
this.faults.clear();
this.resetting = false;
}
}
/**
* Reset the circuit back to its original state. Returns true if the reset
* is successful.
*/
public boolean reset() {
if (this.resetting == false) {
return false;
}
synchronized (faults) {
faults.clear();
broken = false;
resetOn = null;
hardResetOn = null;
resetting = false;
return true;
}
}
/**
* Adds a fault to the circuit.
*/
public void addFault() {
synchronized (faults) {
faults.add(new CircuitFault());
filterFaults();
if (faults.size() >= this.limit || isResetting()) {
trip();
}
}
}
/**
* Filter the faults based on the time window.
*/
private void filterFaults() {
long from = System.currentTimeMillis() - this.timeWindowMillis;
long to = System.currentTimeMillis();
Set<CircuitFault> toRemove = new HashSet<>();
for (CircuitFault fault : faults) {
if (!fault.isInWindow(from, to)) {
toRemove.add(fault);
}
}
faults.removeAll(toRemove);
}
/**
* @return true if the circuit can be reset
*/
public boolean isResettable() {
if (resetting == true) {
return false;
} else if (!broken) {
return false;
} else {
long now = System.currentTimeMillis();
return now >= this.resetOn.getTime();
}
}
/**
* Called to start resetting the circuit. Returns true if
*/
public boolean startReset() {
if (!isResettable()) {
return false;
}
resetting = true;
return true;
}
/**
* @return true if the circuit is currently being reset
*/
public boolean isResetting() {
return resetting;
}
}