Tiny PHP library providing retry functionality with multiple backoff strategies and jitter support

Overview

JBZoo / Retry

Coverage Status Psalm Coverage CodeFactor PHP Strict Types
Stable Version Latest Unstable Version Dependents GitHub Issues Total Downloads GitHub License

  1. 4 retry strategies (plus the ability to use your own)
  2. Optional jitter / randomness to spread out retries and minimize collisions
  3. Wait time cap
  4. Callbacks for custom retry logic or error handling

Notes:

  • This is a fork. You can find the original project here.
  • Now the codebase super strict, and it's covered with tests as much as possible. The original author is great, but the code was smelly :) It's sooo easy, and it took just one my evening... ;)
  • I don't like wording "backoff" in the code. Yeah, it's fun but... I believe "retry" is more obvious. Sorry :)
  • There is nothing wrong to use import instead of global namespace for function. Don't use old-school practices.
  • Static variables with default values are deprecated and disabled. See dump of thoughts below.
  • New methods setJitterPercent|getJitterPercent, setJitterMinCap|getJitterMinCap to have fine-tuning.
  • My project has aliases for backward compatibility with the original. ;)

Installation

composer require jbzoo/retry

Defaults

This library provides sane defaults, so you can hopefully just jump in for most of your use cases.

By default, the retry is quadratic with a 100ms base time (attempt^2 * 100), a max of 5 retries, and no jitter.

Quickstart

The simplest way to use Retry is with the retry helper function:

use function JBZoo\Retry\retry;

$result = retry(function() {
    return doSomeWorkThatMightFail();
});

If successful $result will contain the result of the closure. If max attempts are exceeded the inner exception is re-thrown.

You can of course provide other options via the helper method if needed.

Method parameters are $callback, $maxAttempts, $strategy, $waitCap, $useJitter.

Retry class usage

The Retry class constructor parameters are $maxAttempts, $strategy, $waitCap, $useJitter.

use JBZoo\Retry\Retry;

$retry = new Retry(10, 'exponential', 10000, true);
$result = $retry->run(function() {
    return doSomeWorkThatMightFail();
});

Or if you are injecting the Retry class with a dependency container, you can set it up with setters after the fact. Note that setters are chainable.

use JBZoo\Retry\Retry;

// Assuming a fresh instance of $retry was handed to you
$result = (new Retry())
    ->setStrategy('constant')
    ->setMaxAttempts(10)
    ->enableJitter()
    ->run(function() {
        return doSomeWorkThatMightFail();
    });

Changing defaults

Important Note: It's a fork. So I left it here just for backward compatibility. Static variables are deprecated and don't work at all!

This is terrible practice! Explicit is better than implicit. ;)

  • Example #1. Different parts of your project can have completely different settings.
  • Example #2. Imagine what would happen if some third3-party library (in ./vendor) uses its own default settings. Let's fight!
  • Example #3. It's just an attempt to store variables in a global namespace. Do you see it?

So the next variables are deprecated, and they don't influence anything.

use JBZoo\Retry\Retry;

Retry::$defaultMaxAttempts;
Retry::$defaultStrategy;
Retry::$defaultJitterEnabled;

Just use dependencies injection or so and don't warm your head.

Strategies

There are four built-in strategies available: constant, linear, polynomial, and exponential.

The default base time for all strategies is 100 milliseconds.

Constant

use JBZoo\Retry\Strategies\ConstantStrategy;

$strategy = new ConstantStrategy(500);

This strategy will sleep for 500 milliseconds on each retry loop.

Linear

use JBZoo\Retry\Strategies\LinearStrategy;
$strategy = new LinearStrategy(200);

This strategy will sleep for attempt * baseTime, providing linear retry starting at 200 milliseconds.

Polynomial

use JBZoo\Retry\Strategies\PolynomialStrategy;
$strategy = new PolynomialStrategy(100, 3);

This strategy will sleep for (attempt^degree) * baseTime, so in this case (attempt^3) * 100.

The default degree if none provided is 2, effectively quadratic time.

Exponential

use JBZoo\Retry\Strategies\ExponentialStrategy;
$strategy = new ExponentialStrategy(100);

This strategy will sleep for (2^attempt) * baseTime.

Specifying strategy

In our earlier code examples we specified the strategy as a string:

use JBZoo\Retry\Retry;
use function JBZoo\Retry\retry;

retry(function() {
    // ...
}, 10, 'constant');

// OR

$retry = new Retry(10, 'constant');

This would use the ConstantStrategy with defaults, effectively giving you a 100 millisecond sleep time.

You can create the strategy instance yourself in order to modify these defaults:

use JBZoo\Retry\Retry;
use JBZoo\Retry\Strategies\LinearStrategy;
use function JBZoo\Retry\retry;

retry(function() {
    // ...
}, 10, new LinearStrategy(500));

// OR

$retry = new Retry(10, new LinearStrategy(500));

You can also pass in an integer as the strategy, will translate to a ConstantStrategy with the integer as the base time in milliseconds:

use JBZoo\Retry\Retry;
use function JBZoo\Retry\retry;

retry(function() {
    // ...
}, 10, 1000);

// OR

$retry = new Retry(10, 1000);

Finally, you can pass in a closure as the strategy if you wish. This closure should receive an integer attempt and return a sleep time in milliseconds.

use JBZoo\Retry\Retry;
use function JBZoo\Retry\retry;

retry(function() {
    // ...
}, 10, function($attempt) {
    return (100 * $attempt) + 5000;
});

// OR

$retry = new Retry(10);
$retry->setStrategy(function($attempt) {
    return (100 * $attempt) + 5000;
});

Wait cap

You may want to use a fast growing retry time (like exponential) but then also set a max wait time so that it levels out after a while.

This cap can be provided as the fourth argument to the retry helper function, or using the setWaitCap() method on the Retry class.

Jitter

If you have a lot of clients starting a job at the same time and encountering failures, any of the above retry strategies could mean the workers continue to collide at each retry.

The solution for this is to add randomness. See here for a good explanation:

https://aws.amazon.com/ru/blogs/architecture/exponential-backoff-and-jitter

You can enable jitter by passing true in as the fifth argument to the retry helper function, or by using the enableJitter() method on the Retry class.

By default, we use the "FullJitter" approach outlined in the above article, where a random number between 0 and the sleep time provided by your selected strategy is used.

But you can change the maximum time for Jitter with method setJitterPercent(). It's 100 by default. Also you can set min value for jitter with setJitterMinCap(it's0` by default).

Custom retry decider

By default, Retry will retry if an exception is encountered, and if it has not yet hit max retries.

You may provide your own retry decider for more advanced use cases. Perhaps you want to retry based on time rather than number of retries, or perhaps there are scenarios where you would want retry even when an exception was not encountered.

Provide the decider as a callback, or an instance of a class with an __invoke method. Retry will hand it four parameters: the current attempt, max attempts, the last result received, and the exception if one was encountered. Your decider needs to return true or false.

use JBZoo\Retry\Retry;

$retry = new Retry();
$retry->setDecider(function($attempt, $maxAttempts, $result, $exception = null) {
    return someCustomLogic();
});

Error handler callback

You can provide a custom error handler to be notified anytime an exception occurs, even if we have yet to reach max attempts. This is a useful place to do logging for example.

use JBZoo\Retry\Retry;

$retry = new Retry();
$retry->setErrorHandler(function($exception, $attempt, $maxAttempts) {
    Log::error("On run {$attempt}/{$maxAttempts} we hit a problem: {$exception->getMessage()}");
});

Unit tests and check code style

make update
make test-all

License

MIT

See Also

  • CI-Report-Converter - Converting different error reports for deep compatibility with popular CI systems.
  • Composer-Diff - See what packages have changed after composer update.
  • Composer-Graph - Dependency graph visualization of composer.json based on mermaid-js.
  • Mermaid-PHP - Generate diagrams and flowcharts with the help of the mermaid script language.
  • Utils - Collection of useful PHP functions, mini-classes, and snippets for every day.
  • Image - Package provides object-oriented way to manipulate with images as simple as possible.
  • Data - Extended implementation of ArrayObject. Use files as config/array.
  • SimpleTypes - Converting any values and measures - money, weight, exchange rates, length, ...
You might also like...
It is an open-source and free project, which is faced with the drawing lovers, providing a free and simple Gallery service
It is an open-source and free project, which is faced with the drawing lovers, providing a free and simple Gallery service

It is an open-source and free project, which is faced with the drawing lovers, providing a free and simple Gallery service

Skosmos is a web-based tool providing services for accessing controlled vocabularies, which are used by indexers describing documents and searchers looking for suitable keywords.

Skosmos is a web-based tool providing services for accessing controlled vocabularies, which are used by indexers describing documents and searchers looking for suitable keywords.

Starless Sky is a network protocol for secure identities, providing the use of assymetric identities, public information, end-to-end messaging and smart contracts
Starless Sky is a network protocol for secure identities, providing the use of assymetric identities, public information, end-to-end messaging and smart contracts

Descentralized network protocol providing smart identity over an secure layer. What is the Starless Sky Protocol? Starless Sky is a network protocol f

StringBuffer is a PHP class providing operations for efficient string buffering

StringBuffer is a PHP class providing operations for efficient string buffering

The Assure Alliance support website. This website is based on Questions2Answers and is a forum for support using Biblical Tools

The Assure Alliance support website. This website is based on Questions2Answers and is a forum for support using Biblical Tools

A simple API with Guzzle wrapper, providing easy access to wppconnect's endpoints.

WPPConnect Team Wppconnect Laravel Client A simple API with Guzzle wrapper, providing easy access to wppconnect's endpoints. Requirements PHP 7.4 or n

Bundle providing Honeypot field for the Form Builder in Ibexa DXP Experience/Commerce (3.X)

IbexaHoneypot Bundle providing Honeypot field for the Form Builder in Ibexa DXP Experience/Commerce (3.X) What is Honey pot? A honey pot trap involves

Fresns core library: Cross-platform general-purpose multiple content forms social network service software
Fresns core library: Cross-platform general-purpose multiple content forms social network service software

About Fresns Fresns is a free and open source social network service software, a general-purpose community product designed for cross-platform, and su

Releases(3.0.0)
Owner
JBZoo Toolbox
Awesome developer tools and libs
JBZoo Toolbox
Automatically retry non-atomic upsert operation when unique key constraints are violated.

Laravel Retry on Duplicate Key Automatically retry non-atomic upsert operation when unique constraints are violated. e.g. firstOrCreate() updateOrCrea

mpyw 8 Dec 7, 2022
File uploads with validation and storage strategies

Upload This component simplifies file validation and uploading. Usage Assume a file is uploaded with this HTML form: <form method="POST" enctype="mult

Brandon Savage 1.7k Dec 27, 2022
🌐 A minimalist languages library that made plugins support multiple languages.

libLanguages · libLanguages is a PocketMine-MP library for making plugins support multiple languages. Easy To Learn: Just declare it in onEnable() fun

thebigcrafter 1 May 1, 2022
MOP is a php query handling and manipulation library providing easy and reliable way to manipulate query and get result in a fastest way

Mysql Optimizer mysql optimizer also known as MOP is a php query handling and manipulation library providing easy and reliable way to manipulate query

null 2 Nov 20, 2021
Tiny, fast and simple PHP boilerplate built on top of FlightPHP

BlessPHP Tiny, fast and simple PHP boilerplate built on top of FlightPHP. Good enough to use as skeleton for prototypes and some pet-projects. The nam

Anthony Axenov 2 Sep 20, 2022
File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery

File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.

Sebastian Tschan 31.1k Dec 30, 2022
Wake PC is a super tiny password protected webapp for linux machines that sends WOL packets, written in PHP.

Wake PC Wake PC is a super tiny password protected webapp for linux machines that sends WOL packets, written in PHP. How to set up Quick setup You can

Dániel Szabó 45 Dec 30, 2022
A tiny REPL for PHP

Boris A tiny, but robust REPL for PHP. Announcement: I'm looking to add one or two additional collaborators with commit access. If you are actively in

null 2.2k Dec 30, 2022
Small library providing some functional programming tools for PHP, based on Rambda

Functional library for PHP. Features: set of useful functions helpful in functional programming all functions are automatically curried every array ca

Wojciech Nawalaniec 5 Jun 16, 2022
A PHP library providing ISO 3166-1 data

league/iso3166 A PHP library providing ISO 3166-1 data. What is ISO 3166-1 ISO 3166-1 is part of the ISO 3166 standard published by the International

The League of Extraordinary Packages 555 Dec 21, 2022