What is the easy way to add some addition info into the timeout exception? Suppose I have a code like that:
CompletableFuture<ResutType> getSomething()
{
... return someFuture.orTimeout(15, SECONDS);
}
This is all I'm getting currently downstream later on when callers of the method trying to do get()
:
java.util.concurrent.ExecutionException: java.util.concurrent.TimeoutException
at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073)
I need to add more info - which operation has timed out, how long timeout was, etc.. In other words I need some message to the exception like "The Something timed-out in 15s", ideally use a supplier like:
CompletableFuture<ResutType> getSomething()
{
return someFuture.orTimeout(15, SECONDS, () -> new TimeoutException("The Something timed-out in 15s"));
}
You can use exceptionallyCompose
(or any of the async variants of this method) for that:
public final class Example {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
sleep(3000);
return "DONE";
})
.orTimeout(1000, TimeUnit.MILLISECONDS)
.exceptionallyCompose(throwable -> {
if (throwable instanceof TimeoutException) {
var tex = new TimeoutException("The Something timed-out in 15s");
return CompletableFuture.failedFuture(tex);
}
return CompletableFuture.failedFuture(throwable);
});
sleep(2000);
future.get();
sleep(5000);
}
private static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
If your code fails due to a TimeoutException
, you can return a TimeoutException
(or any other throwable) that has more detail via a failed CompletableFuture
. Otherwise, you can just return the same throwable that caused one of the previous stages to be completed exceptionally.
For example, if there had been some RuntimeException
in supplyAsync
, future.get()
would still cause a RuntimeException
(wrapped within an ExecutionException
) to be thrown. If there is no exception (i.e. also no timeout), you get()
your result normally.
With that knowledge you can write your own little orTimeout
wrapper, for example:
public static <T> CompletableFuture<T> orTimeout(
CompletableFuture<T> cf,
long timeout, TimeUnit unit
) {
return cf.orTimeout(timeout, unit).exceptionallyCompose(throwable -> {
if (throwable instanceof TimeoutException) {
final var msg = String.format(
"The Something timed-out in %d %s.",
timeout, unit
);
return CompletableFuture.failedFuture(new TimeoutException(msg));
}
return CompletableFuture.failedFuture(throwable);
});
}
and then use it for all CompletableFuture
s like so:
CompletableFuture<String> future = orTimeout(CompletableFuture.supplyAsync(() -> {
sleep(10000);
return "DONE";
}), 1000, TimeUnit.MILLISECONDS);