foldright / cffu

🦝 Java CompletableFuture-Fu(CF-Fu, pronounced "Shifu"), a tiny sidekick library to make CompletableFuture usage more convenient, more efficient and safer in your application. πŸ˜‹πŸš€πŸ¦Ί
https://github.com/foldright/cffu
Apache License 2.0
156 stars 21 forks source link

add retry support for CompletableFuture #200

Closed linzee1 closed 1 month ago

linzee1 commented 2 months ago

Here is a Spring Retry example:

RetryTemplate template = RetryTemplate.builder()
                .maxAttempts(3)
                .fixedBackoff(1000)
                .retryOn(RemoteAccessException.class)
                .build();
template.execute(ctx -> {
    // ... do something
});

It is easy to implement retry for CompletableFuture.

@Slf4j
class RetryNAttemptsDemo {
    public static void main(String[] args) {
        // return succeed result by 4th try
        var times = new AtomicInteger();
        Supplier<Integer> task = () -> {
            if (times.getAndIncrement() < 3) {
                throw new RuntimeException("error result");
            } else {
                return 42;
            }
        };
        // using retry method
        retry(4, () -> supplyAsync(task))
            .thenAcceptAsync(r -> log.info("success result: {}", r))
            .exceptionallyAsync(throwable -> {
                log.error("final result", throwable);
                return null;
            })
            .join();
    }

    public static <T> CompletableFuture<T> retry(int attempts, Supplier<CompletionStage<T>> supplier) {
        var result = new CompletableFuture<T>();
        retryNAttempts(result, attempts, supplier);
        return result;
    }

    private static <T> void retryNAttempts(CompletableFuture<T> result, int attempts, Supplier<CompletionStage<T>> supplier) {
        supplier.get()
            .thenAccept(result::complete)
            .exceptionally(throwable -> {
                if (attempts > 0L) {
                    log.warn("retrying");
                    retryNAttempts(result, attempts - 1, supplier);
                } else {
                    log.error("retry failed", throwable);
                    result.completeExceptionally(throwable);
                }
                return null;
            });
    }
}

but it is better to add static utility method for CompletableFuture with less bug and multiple retry strategy support.

There are 3 types of strategies for retrying:

  1. trigger strategy (for specific value or exception)
  2. backoff strategy (no delay, fixed delay, exponential backoff, etc.)
  3. temination strategy ( n times, with timeout, util success result)

Goal:

  1. add static utility method retry(Retry retry, Supplier(CompletionStage<T>) supplier) : CompetionStage<T>
  2. utility method to create Retry instance
  3. Retry support strategies discussed above
  4. Retry instance is immutable and reusable
oldratlee commented 2 months ago

@linzee1 Thanks for your professional issue πŸ‘

It'll take some time to understand and feedback. πŸ’•


more info about retry see the author's article ι‡θ―•ζœΊεˆΆδΈŽCompletableFuture拓展. πŸ†™

oldratlee commented 1 month ago

Mature resilience4j fault tolerance library provides retry with first-class support of CompletionStage/CompletableFuture.

It's recommended to use resilience4j retry rather than re-implementing in cffu.