}
@Override
public Subscriber<? super R> call(final Subscriber<? super R> child) {
final CompositeSubscription s = new CompositeSubscription();
// if the child unsubscribes we unsubscribe our parent as well
child.add(s);
/*
* Define the action to perform on timeout outside of the TimerListener to it can capture the HystrixRequestContext
* of the calling thread which doesn't exist on the Timer thread.
*/
final HystrixContextRunnable timeoutRunnable = new HystrixContextRunnable(originalCommand.concurrencyStrategy, new Runnable() {
@Override
public void run() {
child.onError(new HystrixTimeoutException());
}
});
TimerListener listener = new TimerListener() {
@Override
public void tick() {
// if we can go from NOT_EXECUTED to TIMED_OUT then we do the timeout codepath
// otherwise it means we lost a race and the run() execution completed
if (originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)) {
// do fallback logic
// report timeout failure
originalCommand.metrics.markTimeout(System.currentTimeMillis() - originalCommand.invocationStartTime);
// we record execution time because we are returning before
originalCommand.recordTotalExecutionTime(originalCommand.invocationStartTime);
// shut down the original request
s.unsubscribe();
timeoutRunnable.run();
}
}
@Override
public int getIntervalTimeInMilliseconds() {
return originalCommand.properties.executionIsolationThreadTimeoutInMilliseconds().get();
}
};
Reference<TimerListener> _tl = null;
if (isNonBlocking) {
/*
* Scheduling a separate timer to do timeouts is more expensive
* so we'll only do it if we're being used in a non-blocking manner.
*/
_tl = HystrixTimer.getInstance().addTimerListener(listener);
} else {
/*
* Otherwise we just set the hook that queue().get() can trigger if a timeout occurs.
*
* This allows the blocking and non-blocking approaches to be coded basically the same way
* though it is admittedly awkward if we were just blocking (the use of Reference annoys me for example)
*/
_tl = new SoftReference<TimerListener>(listener);
}
final Reference<TimerListener> tl = _tl;
// set externally so execute/queue can see this
originalCommand.timeoutTimer.set(tl);
/**
* If this subscriber receives values it means the parent succeeded/completed
*/
Subscriber<R> parent = new Subscriber<R>() {
@Override
public void onCompleted() {
if (isNotTimedOut()) {
// stop timer and pass notification through
tl.clear();
child.onCompleted();
}
}
@Override
public void onError(Throwable e) {
if (isNotTimedOut()) {
// stop timer and pass notification through
tl.clear();
child.onError(e);
}
}
@Override
public void onNext(R v) {
if (isNotTimedOut()) {
child.onNext(v);
}
}
private boolean isNotTimedOut() {
// if already marked COMPLETED (by onNext) or succeeds in setting to COMPLETED
return originalCommand.isCommandTimedOut.get() == TimedOutStatus.COMPLETED ||
originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.COMPLETED);
}
};
// if s is unsubscribed we want to unsubscribe the parent
s.add(parent);
return parent;
}