Code Monkey home page Code Monkey logo

yet-another-try's Introduction

Yet Another Try

Build Status codecov Maven Central

Features

  • Configure:
    • max number of attempts
    • timeout
    • delay between attempts
    • which exceptions should be retried and which should not.
    • whenever or not use the invocation thread for the first attempt
  • Implements plain java ExecutorService, therefore it is fully compatible with the code, which uses ExecutorService directly.
  • Uses CompletableFuture as a return type.
  • Has both asynchronous and synchronous versions.
  • Collects statistics about successful and failed attempts if requested.

Dependencies and prerequisites

The library requires Java 8+. Use the following code snippets to add the library to your project:

  • Gradle
dependencies {
    compile "com.github.sorokinigor:yet-another-try:1.1.0"
}

repositories {
    mavenCentral()
}
  • Maven
<dependency>
  <groupId>com.github.sorokinigor</groupId>
  <artifactId>yet-another-try</artifactId>
  <version>1.1.0</version>
</dependency>

Usage

The main entry point is Retry utility class.

/*
  Uses the current thread for the first attempt
  and passed ScheduledExecutorService for the subsequent attempts,
  does not retry on malformed request.
*/
AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
    .retryOnce()
    .runFirstAttemptInInvocationThread()
    .terminateOn(IllegalArgumentException.class)
    .terminateOn(HttpGenericException.class, e -> e.statusCode() == 400)
    .build();
CompletableFuture<String> future = executor.submit(() -> faultyResource("malformedRequest"));
future.whenComplete((response, exception) -> System.out.println(
    "Response '" + response + "', exception '" + exception + "'."
));

//Uses default lazy singleton instance of AsyncRetryExecutor
Retry.async()
    .submit(() -> faultyResource("request"))
    .thenAccept(response -> System.out.println("Response is '" + response + "'."));

/*
  Uses the current thread for task invocation.
  Tries 2 times with fixed rate between attempts.
*/
SyncRetryExecutor syncExecutor = Retry.sync()
    .maxAttempts(2)
    .backOff(Backoffs.fixedRate(1L, TimeUnit.SECONDS))
    .build();
String response = syncExecutor.execute(() -> faultyResource("syncRequest"));
    
/*
 Shortcut for ad hoc synchronous execution.
 Completes with exception on timeout.
 */
String result = Retry.sync()
    .timeout(5L, TimeUnit.SECONDS)
    .execute(() -> faultyResource("adhoc request"));

Asynchronous

Any arbitrary ScheduledExecutorService should be passed in order to use asynchronous executor. Example:

AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
    .maxAttempts(3)
    .timeout(10, TimeUnit.SECONDS)
    .backOff(Backoffs.fixedDelay(1L, TimeUnit.SECONDS))
    .retryOn(NotYetConnectedException.class)
    .terminateOn(NullPointerException.class)
    .build();

CompletableFuture<Integer> result = executor.submit(() -> {
      try (SocketChannel socket = SocketChannel.open(new InetSocketAddress("music.yandex.ru", 80))) {
        socket.configureBlocking(false);
        ByteBuffer buffer = ByteBuffer.allocate(10);
        return socket.read(buffer);
      }
    })
    .thenApply(numberOfBytesRead -> numberOfBytesRead / 2);

Please note that by default AsyncRetryExecutor manages the lifecycle of the passed ScheduledExecutorService. Consequently, the AsyncRetryExecutor will shutdown underlying ScheduledExecutorService. If you want to prevent it, use:

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
AsyncRetryExecutor executor = Retry.async(executorService)
    .doNotShutdownExecutors()
    .build();

Timeout

This code snippet shows how you can specify a timeout for a task:

AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
    .timeout(10L, TimeUnit.SECONDS)
    .build();

After the timeout is expired, the result CompletableFuture is completed with TimeoutException. Since then, there would be no retries of the task.

By default, the same executor is used for both task execution and timeout handling, but it is configurable:

ScheduledExecutorService taskExecutor = Executors.newSingleThreadScheduledExecutor();
ScheduledExecutorService timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
AsyncRetryExecutor executor = Retry.async(taskExecutor)
    .timeout(5L, TimeUnit.SECONDS)
    .timeoutExecutorService(timeoutExecutor)
    .build();

Delay

The library itself contains exponential (default and preferable), fixed delay and fixed rate backoffs for delay calculation. But, feel free to implement your own backoff strategy, as the Backoff interface is a part of the public API.

In order to instantiate the built-in backoff strategies use Backoffs utility class.

Exponential

The delay is exponentially increases until it reaches the upper bound for the delay or the number of attempts. After the calculation of the exponential backoff, it also adds an additional random delay based on the passed random factor. For instance, 0.2 adds up to 20% delay. Example:

AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
    .backOff(Backoffs.exponential(3L, 30L, TimeUnit.SECONDS, 0.2D))
    .build();

Fixed delay

It always uses the same delay for each attempt. Example:

AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
    .backOff(Backoffs.fixedDelay(1L, TimeUnit.SECONDS))
    .build();

Fixed rate

It subtracts the task execution time from the delay. If the execution time is greater than or equal the delay, the delay is 0. Example:

AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
    .backOff(Backoffs.fixedRate(1L, TimeUnit.SECONDS))
    .build();

Exceptions

The library provides the ability to retry only specific type exception and the exception matching the predicate. Also, it is possible to configure to stop retrying after a specific exception (by type or predicate too). Example:

AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
     .retryOn(SocketException.class)
     .retryOn(HttpGenericException.class, e -> e.statusCode() == 500)
     .terminateOn(IllegalStateException.class)
     .terminateOn(HttpGenericException.class, e -> e.statusCode() == 400)
     .terminatePredicate(e -> e instanceof BindException && e.getMessage().contains("in use"))
     .build();

Notice that the task is retried only if:

  • Any of the retry predicates returns true or you didn't specify any (in that case there is a default retry predicate, which always returns true).
  • None of the terminate predicates returns true or you didn't specify any (in that case there is a default terminate predicate, which always returns false).

Default executor

A default lazy singleton instance of asynchronous executor is available via Retry.async() method. Example:

CompletableFuture<String> future = Retry.async()
    .submit(() -> faultyResource("request"));

It is lazily instantiated on first usage and creates a shutdown hook for the internal ScheduledExecutorService shutting down.

Statistics

There is a simple wrapper for the asynchronous executor, which collects the number of failed attempts and the number of successful and failed tasks. Example:

AsyncRetryExecutor executor = Retry.async(Executors.newSingleThreadScheduledExecutor())
    .maxAttempts(2)
    .build();
StatisticsExecutorService statsExecutor = Retry.gatherStatisticFor(executor);
CompletableFuture<String> successful = statsExecutor.submit(() -> "successful");
CompletableFuture<String> failed = statsExecutor.submit(() -> { throw new Exception(); });
successful.thenAcceptBoth(failed, (ignored1, ignored2) -> {})
    .whenComplete((ignored, ignoredException) -> {
      System.out.println(statsExecutor.stats());
      //Stats{successful=1, failed=1, failedAttempts=2}
    });

Synchronous

The synchronous executor does not use any thread pool, instead, it uses the current thread for task execution. It has approximately the same configuration as asynchronous one, except the settings related to ScheduledExecutorService. Example:

SyncRetryExecutor executor = Retry.sync()
    .maxAttempts(3)
    .timeout(10, TimeUnit.SECONDS)
    .backOff(Backoffs.fixedDelay(1L, TimeUnit.SECONDS))
    .retryOn(NotYetConnectedException.class)
    .terminateOn(NullPointerException.class)
    .build();

int numberOfBytesRead = executor.execute(() -> {
    try (SocketChannel socket = SocketChannel.open(new InetSocketAddress("music.yandex.ru", 80))) {
      socket.configureBlocking(false);
      ByteBuffer buffer = ByteBuffer.allocate(10);
      return socket.read(buffer);
    }
});

If you do not want to store a reference to the executor, you can use a shortcut:

String response = Retry.sync()
    .withoutDelay()
    .terminateOn(IllegalArgumentException.class)
    .terminateOn(UnsupportedOperationException.class)
    .execute(() -> faultyResource("request"));

yet-another-try's People

Contributors

sorokinigor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

yet-another-try's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.