talentrydev/backoff

1.3.0 2023-10-18 09:32 UTC

This package is auto-updated.

Last update: 2024-04-18 10:38:17 UTC


README

This module provides a retry mechanism which implements exponential backoff and jitter. In you are unfamiliar with those concepts check out the following page: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

We expose a single class that you can use like so:

(new Backoff)
    ->setBackoffStrategy(new ExponentialStrategy(200))
    // Either ExponentialStrategy or ConstantStrategy
    // Default is ExponentialStrategy with 200ms base wait time
    // Every BackoffStrategy must be passed a number of milliseconds that it will
    // use for the initial wait time (or also for further attempts depending on the Strategy)
    
    ->setWaitStrategy(new USleepStrategy())
    // Either USleepStrategy or VoidStrategy (aka dont wait)
    // Default is USleepStrategy
    
    ->setRetryDeciderStrategy(new MaxAttemptStrategy(4))
    // Default is MaxAttemptStrategy with a default of 4 attempts. No other strategy available out of the box
    // Using one or even multiple custom strategies is explained further down
    
    ->setJitterStrategy(new FullJitterStrategy())
    // Either FullJitterStrategy or NoJitterStrategy
    // Default is FullJitterStrategy
    
    // you can pass any callable here
    ->run(
        function () {
            // network operation to external service that might fail
        }
    );

The simplest case looks like this:

(new Backoff())
    ->run(
        function () {
            // network operation to external service that might fail
        }
    );

By default we keep trying until the numer of max attempts is reached (MaxAttemptStrategy with default 4). If you want to stop trying earlier based on a different logic (e.g. the exception being thrown), you can pass a custom RetryDeciderStratey:

(new Backoff)
    ->setRetryDeciderStrategy(
        new class implements RetryDeciderStrategy {
            public function shouldRetry(
                int $currentAttempt,
                \Throwable $exception,
                $callableResult = null,
            ): bool {
                // only try again if we receive this kind of exception
                return $exception instanceof NetworkSaturedException;
            }
        }
    )
    ->run(
        function () {
            // network operation to external service that might fail
        }
    );

In the above example the code might run forever if the condition is always met. To avoid this you can use the CompositeStrategy that allows for multiple strategies to be used. This way you can e.g. check on a specific condition but still abort after a certain number of retries. The order of strategies does not matter as we will stop execution as soon as the first strategy fails.

$customStrategy = new class implements RetryDeciderStrategy {
    public function shouldRetry(
        int $currentAttempt,
        \Throwable $exception,
        $callableResult = null
    ): bool {
        // only try again if we receive this kind of exception
        return $exception instanceof NetworkSaturedException;
    }
};

$retryStrategy = new CompositeStrategy();

$retryStrategy
    ->addStrategy($customStrategy)
    ->addStrategy(new MaxAttemptsStrategy(7));

(new Backoff)
    ->setRetryDeciderStrategy($retryStrategy)
    ->run(
        function () {
            // network operation to external service that might fail
        }
    );

Development

  • Install composer dependencies: make deps
  • Run tests: make test
  • Run code sniffer: make cs
  • Fix code sniffer violations: make csfix