Retry logic with CompletableFuture

后端 未结 7 2155
别跟我提以往
别跟我提以往 2021-01-31 18:26

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

7条回答
  •  悲哀的现实
    2021-01-31 19:08

    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;
    }
    

提交回复
热议问题