package tc.oc.commons.core.concurrent;
import java.sql.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
import com.google.common.util.concurrent.AbstractFuture;
import java.time.Duration;
import java.time.Instant;
/**
* Abstract base for a {@link com.google.common.util.concurrent.ListenableFuture} that
* fails automatically if it has not completed after a certain amount of time.
*
* This class implements the timeout, which starts when the object is instantiated.
* When the timeout elapses, {@link #setException} will be called with a
* {@link TimeoutException}. If this call succeeds, {@link #timeoutTask()} will be
* called, which calls {@link #interruptTask()}.
*
* The timer is cancelled if the future completes in any way, or is cancelled.
*/
public abstract class TimeoutFuture<T> extends AbstractFuture<T> {
private final TimerTask timeoutTask;
private final long startNanos;
private long stopNanos = -1;
protected TimeoutFuture(Duration timeout) {
this(Instant.now().plus(timeout));
}
protected TimeoutFuture(Instant expiresAt) {
this.startNanos = System.nanoTime();
this.timeoutTask = new TimerTask() {
@Override public void run() {
if(setException(new TimeoutException(makeTimeoutMessage()))) {
timeoutTask();
}
}
};
makeTimer().schedule(timeoutTask, Date.from(expiresAt));
}
protected String makeTimeoutMessage() {
return toString() + " timed out";
}
protected String makeTimerName() {
return toString() + " timer thread";
}
protected Timer makeTimer() {
return new Timer(makeTimerName(), true);
}
protected void timeoutTask() {
interruptTask();
}
private void stop() {
timeoutTask.cancel();
if(stopNanos == -1) {
stopNanos = System.nanoTime();
}
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if(super.cancel(mayInterruptIfRunning)) {
stop();
return true;
}
return false;
}
@Override
protected boolean set(@Nullable T value) {
stop();
return super.set(value);
}
@Override
protected boolean setException(Throwable throwable) {
stop();
return super.setException(throwable);
}
/**
* If the task is still running, return the elapsed time since it started.
* If the task is completed or cancelled, return the time it was running for.
*/
public long elapsedTimeNanos() {
if(stopNanos == -1) {
return System.nanoTime() - startNanos;
} else {
return stopNanos - startNanos;
}
}
public double elapsedTimeMillis() {
return elapsedTimeNanos() / 1000000d;
}
public Duration elapsedTime() {
return Duration.ofMillis(Math.round(elapsedTimeMillis()));
}
}