Failsafe library features

November 09, 2020 No comments Failsafe features Java library

1. Introduction

Failsafe is a simple, lightweight library for handling failures in Java applications. In this article, we are going to present some of the features that Failsafe provides.

2. Schedulers in Failsafe library

In order to perform asynchronous executions Failsafe library uses ForkJoinPool.commonPool but we can also configure a specific ScheduledExecutorService, Scheduler or ExecutorService that can be used instead.

We can supply your executor via the FailsafeExecutor.with() method.

ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);

RetryPolicy<Object> retryPolicy = new RetryPolicy<>().handle(ConnectException.class)
                                                     .withDelay(Duration.ofSeconds(1))
                                                     .withMaxRetries(3);

Failsafe.with(retryPolicy).with(executor).getAsync(this::connect);

3. Event listeners

Failsafe supports event listeners that allow monitoring executions.

There are different listeners for:

  • Failsafe Executor,
  • Policy implementations.
 Failsafe.with(retryPolicy, circuitBreaker)
         .onComplete(e -> {
              if (e.getResult() != null) {
                 log.info("Connected to {}", e.getResult());
              } else if (e.getFailure() != null) {
                 log.error("Failed to create connection", e.getFailure());
              }
          })
         .onSuccess(e -> log.info("Connected to {}", e.getResult()))
         .onFailure(e -> log.error("Failed to create connection", e.getFailure()))
         .get(this::connect);

At the top level of Failsafe API, we are getting notifications when an execution:

  • completes successfully for all policies,
  • fails for any policy,
  • completes for all policies.

At the policy level, Failsafe can notify you when an execution succeeds or fails for a particular policy:

policy.onSuccess(e -> log.info("Connected to {}", e.getResult()))
      .onFailure(e -> log.error("Failed to create connection", e.getFailure()));

4. Strong typing

Failsafe executions are typed based on the expected result. We can declare common Object type and more specific result types like in the following example:

RetryPolicy<HttpResponse> retryPolicy = new RetryPolicy<HttpResponse>()
     .handleResultIf(response -> response.getStatusCode == 500)
     .onFailedAttempt(e -> log.warn("Failed attempt: {}", e.getLastResult().getStatusCode()));

The Failsafe execution will look like the following:

HttpResponse response = Failsafe.with(retryPolicy)
     .onSuccess(e -> log.info("Success: {}", e.getResult().getStatusCode()))  
     .get(this::sendHttpRequest);

5. Execution context

Failsafe library provide information about the execution such as the number of execution attempts, start and elapsed times, and the last result or failure:

Failsafe.with(retryPolicy).run(ctx -> {
  log.debug("Connection attempt #{}", ctx.getAttemptCount());
  connect();
});

Execution context is useful if we want to create an execution that depends on results from a previous attempt.

6. Execution cancellation

Failsafe supports cancellation and optional interruption of executions:

  • Cancellation will cause any async execution retries and timeout attempts to stop,
  • Interruption will cause the execution thread’s interrupt flag to be set.

Cancellations can be triggered by Timeout. Interruptions could be triggered manually like in the following code:

Future<Connection> future = Failsafe.with(retryPolicy).getAsync(this::connect);
future.cancel(shouldInterrupt);

In the execution code we can check if it was cancelled:

Failsafe.with(timeout).getAsync(ctx -> {
  while (!ctx.isCancelled())
    doWork();
});

or interrupted:

Failsafe.with(timeout).getAsync(ctx -> {
  while (!Thread.isInterrupted())
    doBlockingWork();
});

7. Async API Support

Failsafe can be integrated with asynchronous code that reports completion via callbacks. The runAsyncExecution, getAsyncExecution, and getStageAsyncExecution methods provide an AsyncExecution reference that can be used to manually schedule retries or complete the execution from inside asynchronous callbacks.

8. CompletionStage Object Support

Failsafe can accept a CompletionStage and return a new CompletableFuture with failure handling built-in:

Failsafe.with(retryPolicy)
        .getStageAsync(this::connectAsync)
        .thenApplyAsync(value -> value + "bar")
        .thenAccept(System.out::println));

9. Functional Interface Support

Failsafe can be used to create resilient functional interfaces.

Function<String, Connection> connect = address -> Failsafe.with(retryPolicy).get(() -> connect(address));

10. Execution Tracking

Execution tracking is a useful feature for integrating with APIs that have their retry mechanism. We can configure Failsafe to manually retry executions as needed.

Execution execution = new Execution(retryPolicy);
while (!execution.isComplete()) {
  try {
    doSomething();
    execution.complete();
  } catch (ConnectException e) {
    execution.recordFailure(e);
  }
}

11. Conclusion

In this article, we showcased several features that come with Failsafe. This useful library allows us to control completely executions, monitor them, and cancel when we want to. Failsafe supports 'modern' Java interfaces like Future, CompletionStage, or CompletableFuture which makes it extremely easy to integrate into modern applications.

{{ message }}

{{ 'Comments are closed.' | trans }}