I need to submit a task in an async framework I\'m working on, but I need to catch for exceptions, and retry the same task multiple times before \"aborting\".
The code I
Chaining subsequent retries can be straight-forward:
public CompletableFuture executeActionAsync() {
CompletableFuture f=executeMycustomActionHere();
for(int i=0; i executeMycustomActionHere().join());
}
return f;
}
Read about the drawbacks below
This simply chains as many retries as intended, as these subsequent stages won’t do anything in the non-exceptional case.
One drawback is that if the first attempt fails immediately, so that f
is already completed exceptionally when the first exceptionally
handler is chained, the action will be invoked by the calling thread, removing the asynchronous nature of the request entirely. And generally, join()
may block a thread (the default executor will start a new compensation thread then, but still, it’s discouraged). Unfortunately, there is neither, an exceptionallyAsync
or an exceptionallyCompose
method.
A solution not invoking join()
would be
public CompletableFuture executeActionAsync() {
CompletableFuture f=executeMycustomActionHere();
for(int i=0; i executeMycustomActionHere())
.thenCompose(Function.identity());
}
return f;
}
demonstrating how involved combining “compose” and an “exceptionally” handler is.
Further, only the last exception will be reported, if all retries failed. A better solution should report the first exception, with subsequent exceptions of the retries added as suppressed exceptions. Such a solution can be build by chaining a recursive call, as hinted by Gili’s answer, however, in order to use this idea for exception handling, we have to use the steps to combine “compose” and “exceptionally” shown above:
public CompletableFuture executeActionAsync() {
return executeMycustomActionHere()
.thenApply(CompletableFuture::completedFuture)
.exceptionally(t -> retry(t, 0))
.thenCompose(Function.identity());
}
private CompletableFuture retry(Throwable first, int retry) {
if(retry >= MAX_RETRIES) return CompletableFuture.failedFuture(first);
return executeMycustomActionHere()
.thenApply(CompletableFuture::completedFuture)
.exceptionally(t -> { first.addSuppressed(t); return retry(first, retry+1); })
.thenCompose(Function.identity());
}
CompletableFuture.failedFuture
is a Java 9 method, but it would be trivial to add a Java 8 compatible backport to your code if needed:
public static CompletableFuture failedFuture(Throwable t) {
final CompletableFuture cf = new CompletableFuture<>();
cf.completeExceptionally(t);
return cf;
}