New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Timeout Decorator #142
Timeout Decorator #142
Conversation
Add test cases.
Add test cases around expected throwable result.
Thx for your PR. We have a look at it. |
Hi @maarek, Here is first points to discuss:
Ideally you should decorate only already provided instances of |
@storozhukBM Thanks for your comments. I was particularly interested in getting the ball rolling on this enhancement. I have no problem with changes to the implementation or API involved and I hope that we can come up with a solution that fits well into the framework. |
We could get some inspiration from Guava's TimeLimiter |
OK guys, Here is my proposal:
Hope it all makes sense for you guys. I want to hear your thoughts on it. |
@storozhukBM @maarek concerning Java stopping a thread, I've found that the following code snippet is very reliable to stopping Java threads (if the design above is conducive to using thread executors):
I also use a controlled thread shutdown approach with a synchronized Boolean, "isRunning" to aid in gracefully shutting down threads who are also monitoring that semaphore. |
Maybe we should take a step back for a second and think about "timing out" work without the use of threads, or at least using a small, fixed set that does not grow with the amount of work to Does the timeout has to be exact or could there be some slack - could the "good enough" approximation of requested timeout give us some headroom in possible implementations ? I am thinking along the lines of a global entity that can timeout work, and signal it accordingly - interrupt threads, inject error emission, cancel disposables etc. Just an idea. |
Maybe already discussed elsewhere, but why not add timeouts directly to I say this because most of the use cases we have involve treating a timeout similarly to an exception. If too many timeouts occur we'd want the circuit breaker to open to prevent future executions. The downside is that you'd have to rely on the code segments not running infinitely, and you'd be recording errors potentially a large amount of time after the timeout threshold was breached (say the threshold was 500 ms and the code took 10 seconds). Also, the work would not be cancellable. |
@drmaas The idea was to create a lightweight, extendable Circuit Breaker where you can add additional features by combining different decorators (higher order functions). RxJava: You can combine RxJava's timeout operator with our custom CircuitBreaker RxJava operator as follows:
Java8: You could decorate a Callable as follows:
Vertx: You can create a timer which fails a Vert.x Future.
Ratpack plans to add a timeout operator for it's Promise: ratpack/ratpack#495 We could implement a decorator which works for a set of interfaces like Java's We should not add timeout handling into the CircuitBreaker directly, because we don't execute calls in a different thread. Timeouts are also not working correctly in Hystrix, when you use |
@RobWin 👍 💯 👍 |
Sorry, I was toying with some ideas over the weekend on the ExecutorService impl. When you say decorate a Future, are you wanting to return a Future or return the standard supplier type (Callable, Runnable, etc) to be used in the CircuitBreaker.
|
We need to have only two decorators: static <T, F extends Future<T>> F decorateFuture(Timeout timeout, F future)
static <T, F extends Future<T>> Supplier<F> decorateFutureSupplier(
Timeout timeout,
Supplier<F> futureSupplier
) |
Can we even decorate a Future? Does it actually make sense? We can decorate a Future with a CircuitBreaker, but not with a preconfigured timeout value, or? |
@RobWin This is what I struggled with. A Future is not technically decoratable. What I can do is Proxy future.get() and/or future.get(oldTimeout, oldUnit) to future.get(newTimeout, newUnit) if that is the route that we'd like to go. I am working on that now. How does the CircuitBreaker decorate a Future now though? Through a future Supplier? |
No, not yet. But I guess input would be a |
I think I know how we can implement @storozhukBM signature now. |
Just pushed a preliminary update to the branch providing the two decorators through proxy. Codacy and Travis hate me for some reason. |
I think we can implement this without a proxy by executing the Future and returning a new (Completable)Future which either contains the result of the decorated Future or the Exception/TimeoutException. |
Does it matter then that you are converting the input Future from some F extends Future to CompletableFuture? |
CompletableFuture implements Future. We should not change the return type. |
NOTE: I renamed My original idea was simpler : package io.github.resilience4j.timeout;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author bstorozhuk
*/
public class TimeoutFuture<T> implements Future<T> {
private final Future<T> target;
private final long maxTimeoutInNanos;
private final AtomicLong waitingStartTimeInNanos = new AtomicLong();
public TimeoutFuture(TimeoutConfig config, Future<T> target) {
maxTimeoutInNanos = config.getTimeoutDuration().toNanos();
this.target = target;
}
@Override public boolean cancel(boolean mayInterruptIfRunning) {
return target.cancel(mayInterruptIfRunning);
}
@Override public boolean isCancelled() {
return target.isCancelled();
}
@Override public boolean isDone() {
return target.isDone();
}
@Override public T get() throws InterruptedException, ExecutionException {
try {
long timeLeftInNanos = timeLeftInNanos();
T result = target.get(timeLeftInNanos, NANOSECONDS);
return result;
} catch (TimeoutException e) {
target.cancel(true);
throw new TimeExceededException(e);
}
}
@Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
requireNonNull(unit, "Unit can't be null");
long timeLeftInNanos = timeLeftInNanos();
long specifiedTimeoutInNanos = unit.toNanos(timeout);
if (specifiedTimeoutInNanos > timeLeftInNanos) {
return this.get();
}
T result = target.get(specifiedTimeoutInNanos, NANOSECONDS);
return result;
}
private long timeLeftInNanos() {
long startTimeInNanos = waitingStartTimeInNanos.get();
if (startTimeInNanos == 0) {
waitingStartTimeInNanos.set(System.nanoTime());
}
Long currentWaitingTimeInNanos = System.nanoTime() - startTimeInNanos;
return maxTimeoutInNanos - currentWaitingTimeInNanos;
}
} Then you can use it like that: static <T> Future<T> decorateFuture(Timeout timeout, Future<T> future) {
return new TimeoutFuture<>(timeout.getTimeoutConfig(), future);
}
static <T> Supplier<Future<T>> decorateFutureSupplier(Timeout timeout, Supplier<Future<T>> futureSupplier) {
return () -> {
Future<T> target = futureSupplier.get();
return new TimeoutFuture<>(timeout.getTimeoutConfig(), target);
};
} |
I went the proxy route figuring that we wouldn't want to return a different Future type than the input type. Even if they are all implement Future. The TimeoutFuture would never be usable in instances like CompletableFuture composition or Promise chaining. Note: I'm not sure if those are valid cases but just something that came to mind. |
From the other point of view it is not good to generate proxy for each decorated object. |
@maarek please note that I changed |
Before I commit again: Should we return a CompletableFuture, a custom TimeoutFuture or TimeoutCompletableFuture? :) |
|
Java 9’s CompletableFuture introduces new methods like I provide my idea for TimeLimiter in some minutes. |
What we actually want to achieve is that we want to call a potentially long running method asynchronously, but timeout when it takes to long and optionally cancel the long running method. What about this solution.
It allows to limit the duration of a Future and decorate the call of the future with a CircuitBreaker.
Of course it's not a reactive, event-driven solution, but that's not possible with Java8 Futures. |
@maarek @storozhukBM What do you think about my suggestion? |
@RobWin So the input being: Future or Supplier and maybe output for each being: Callable or Runnable? It fits nicely with the input for CircuitBreaker without more work. I am fairly pragmatic so if it works as is with the rest of Resilience4j then I am all for it. |
Does it add any value for your use case? |
Decorate Future<T> and Future<T> Supplier to return Callable<T>.
import java.util.function.Supplier; | ||
|
||
/** | ||
* A TimeLimiter decorator stops execution at a configurable rate. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after a configurable duration, or?
This pull request is for Enhancement #67
Adds a decorator on some Supplier, Function, Runnable, Consumer, and Callable in and wraps the function in a single threaded executed Future with a timeout specified in the TimeoutConfig.
Upon Concurrent TimeoutException, ExecutableException, InterruptedException, wraps checked exception in a RuntimeException resilience4j.timeout.TimeoutException with cause being the checked exception.
Example Usage:
Note: It will wrap a functions Throwable in a Concurrent ExecutionException which will throw as a TimeoutException(ExecutionException(Throwable)). Not sure if this is the best approach.