com.github.siwenyan.common.Retry Maven / Gradle / Ivy
package com.github.siwenyan.common;
import org.apache.log4j.Logger;
public abstract class Retry implements IAction {
private static final long TIMEOUT_IN_MILLIS_DEFAULT = 60 * 1000;
private static final long SLEEP_IN_MILLIS_DEFAULT = 1000;
private static final long BREAK_IN_MILLIS_DEFAULT = 100;
private static Logger log = Logger.getLogger(Retry.class);
private final String trace;
private final long timeoutInMillis;
private final long sleepInMillis;
private final long breakInMillis;
private final IEvaluator untilCondition;
private final DoubleAction doubleTry;
private final DoubleAction doubleUntil;
private final DoubleAction doubleEarlyUntil;
protected Object outcomeCompleted = null;
protected Object outcomeTimeout = null;
private IAction tryOnce = new IAction() {
@Override
public boolean execute() throws Exception {
try {
tryOnce();
return true;
} catch (ImmediateAbortException e) {
log.error(trace + "Immediate Abort at tryOnce(): " + e.getMessage());
throw e;
} catch (Exception e) {
return false;
}
}
};
private IAction weakRecover = new IAction() {
@Override
public boolean execute() throws Exception {
try {
weakRecover(null);
return true;
} catch (ImmediateAbortException e) {
log.error(trace + "Immediate Abort at weakRecover(): " + e.getMessage());
throw e;
} catch (Exception e) {
return false;
}
}
};
private IAction until = new IAction() {
@Override
public boolean execute() throws Exception {
if (null == untilCondition) {
return until();
} else {
return untilCondition.evaluate() && until();
}
}
};
private IAction earlyUntil = new IAction() {
//early until evaluation is possible only when untilCondition is not null
@Override
public boolean execute() throws Exception {
if (null == untilCondition) {
return false;
} else {
return untilCondition.evaluate() && until();
}
}
};
private IAction untilRecover = new IAction() {
@Override
public boolean execute() throws Exception {
return untilRecover(null);
}
};
public Retry(String trace, long timeoutInMillis, long sleepInMillis, long breakInMillis, IEvaluator untilCondition) {
this.trace = (null == trace ? "" : trace.trim()) + ">";
this.timeoutInMillis = timeoutInMillis > 0 ? timeoutInMillis : TIMEOUT_IN_MILLIS_DEFAULT;
this.sleepInMillis = sleepInMillis > 0 ? sleepInMillis : SLEEP_IN_MILLIS_DEFAULT;
this.breakInMillis = breakInMillis > 0 ? breakInMillis : BREAK_IN_MILLIS_DEFAULT;
this.untilCondition = untilCondition;
this.doubleTry = new DoubleAction(tryOnce, weakRecover, trace, getBreakMillis());
this.doubleUntil = new DoubleAction(until, untilRecover, trace, getBreakMillis());
this.doubleEarlyUntil = new DoubleAction(earlyUntil, untilRecover, trace, getBreakMillis());
}
public Retry(String trace) {
this(trace, -1, -1, -1, null);
}
public Retry(String trace, IEvaluator untilCondition) {
this(trace, -1, -1, -1, untilCondition);
}
public Retry(String trace, long timeoutInMillis, long sleepInMillis, long breakInMillis) {
this(trace, timeoutInMillis, sleepInMillis, breakInMillis, null);
}
@Override
public boolean execute() {
if (doubleEarlyUntil.execute()) {
return true;
}
long t0 = System.currentTimeMillis();
long t1 = t0 + this.getTimeoutMillis();
int retries = 0;
while (System.currentTimeMillis() < t1) {
log.warn(trace + ": #" + (retries + 1));
if (doubleTry.execute()) {
if (doubleUntil.execute()) {
return true;
}
}
retries++;
log.debug(trace + "Wait a while and retry.");
sleep(this.getSleepMillis());
}
// timeout exit
this.timeout();
log.warn(trace + "Timeout after retrying " + retries + " time(s) in " + ((System.currentTimeMillis() - t0) / 1000) + " seconds.");
return false;
}
abstract protected void tryOnce() throws Exception;
public Object getOutcomeCompleted() {
return this.outcomeCompleted;
}
public void setOutcomeCompleted(Object o) {
this.outcomeCompleted = o;
}
public Object getOutcomeTimeout() {
return this.outcomeTimeout;
}
public void setOutcomeTimeout(Object o) {
this.outcomeTimeout = o;
}
protected boolean until() throws Exception {
// no until check by default
log.debug(trace + "No implementation for until() method.");
return true;
}
protected void weakRecover(Exception e) throws Exception {
// do nothing by default
// to be overridden
log.debug(trace + "No implementation for weakRecover() method.");
}
protected boolean untilRecover(Exception e) throws Exception {
// do nothing by default
// to be overridden
log.debug(trace + "No implementation for untilRecover() method.");
log.debug(trace + "Return false means no need to check until condition for the second time.");
return false;
}
protected long getBreakMillis() {
return this.breakInMillis;
}
protected long getSleepMillis() {
return this.sleepInMillis;
}
protected long getTimeoutMillis() {
return this.timeoutInMillis;
}
protected void timeout() {
// do nothing by default
log.debug(trace + "No implementation for timeout() method.");
}
private void sleep(long millis) {
if (millis <= 0) {
return;
}
try {
log.debug(trace + "Wait for " + (millis / 1000) + " seconds.");
Thread.sleep(millis);
} catch (Exception e) {
// do nothing
}
}
}