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

Overview

PHP Backoff

Latest Version on Packagist Build Software License Quality Score Total Downloads

Easily wrap your code with retry functionality. This library provides:

  1. 4 backoff 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

Installation

composer require stechstudio/backoff

Defaults

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

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

Quickstart

The simplest way to use Backoff is with the global backoff helper function:

$result = backoff(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.

Backoff class usage

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

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

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

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

Changing defaults

If you find you want different defaults, you can modify them via static class properties:

Backoff::$defaultMaxAttempts = 10;
Backoff::$defaultStrategy = 'exponential';
Backoff::$defaultJitterEnabled = true;

You might want to do this somewhere in your application bootstrap for example. These defaults will be used anytime you create an instance of the Backoff class or use the backoff() helper function.

Strategies

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

The default base time for all strategies is 100 milliseconds.

Constant

$strategy = new ConstantStrategy(500);

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

Linear

$strategy = new LinearStrategy(200);

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

Polynomial

$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

$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:

backoff(function() {
    ...
}, 10, 'constant');

// OR

$backoff = new Backoff(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:

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

// OR

$backoff = new Backoff(10, new LinearStrategy(500));

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

backoff(function() {
    ...
}, 10, 1000);

// OR

$backoff = new Backoff(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.

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

// OR

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

Wait cap

You may want to use a fast growing backoff 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 backoff helper function, or using the setWaitCap() method on the Backoff class.

Jitter

If you have a lot of clients starting a job at the same time and encountering failures, any of the above backoff 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://www.awsarchitectureblog.com/2015/03/backoff.html

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

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.

Custom retry decider

By default Backoff 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. Backoff 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.

$backoff->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.

getMessage()); }); ">
$backoff->setErrorHandler(function($exception, $attempt, $maxAttempts) {
    Log::error("On run $attempt we hit a problem: " . $exception->getMessage());
});
Comments
  • FR: Define set of exception classes to be retried

    FR: Define set of exception classes to be retried

    Currently, this has to be implemented by the user via the decider setting. Could be simplified by e.g. providing a static \STS\Backoff\Backoff::exceptionClassDecider(array $exceptionClasses): function or similar helper-method.

    opened by arnegroskurth 3
  • waitCap does not work

    waitCap does not work

    If i'm not mistaken, new Backoff(20, 'constant', 30000, true); doesn't set the right wait time, because it uses:

            if (is_string($strategy) && array_key_exists($strategy, $this->strategies)) {
                return new $this->strategies[$strategy];
            }
    

    which doesn't set Strategy's base value.

    opened by nachitox 3
  • Implemented suggestions from #2

    Implemented suggestions from #2

    Fixes #2 (may want to wait for the test fixes so I can re-base, but no common files so git should handle)

    Signed-off-by: Lewis Cowles [email protected]

    opened by Lewiscowles1986 2
  • Handle PHP 7 Error / Throwable

    Handle PHP 7 Error / Throwable

    In PHP 7 Errors are thrown in a similar manner as Exceptions.

    Suggestion: Make backoff handle Throwable's (and keep backwards compatibility).

    PR coming up!

    opened by kler 1
  • Intermittent test failures

    Intermittent test failures

    Fascinatingly the changes I suggested in #2 highlighted intermittent errors using php7.2.2 on Windows 10 64-bit

    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    .............................                                     29 / 29 (100%)
    
    Time: 591 ms, Memory: 4.00MB
    
    OK (29 tests, 68 assertions)
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    .............................                                     29 / 29 (100%)
    
    Time: 552 ms, Memory: 4.00MB
    
    OK (29 tests, 68 assertions)
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    .............................                                     29 / 29 (100%)
    
    Time: 571 ms, Memory: 4.00MB
    
    OK (29 tests, 68 assertions)
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    ....................F........                                     29 / 29 (100%)
    
    Time: 544 ms, Memory: 4.00MB
    
    There was 1 failure:
    
    1) STS\Backoff\HelpersTest::testWaitCap
    Failed asserting that false is true.
    
    C:\Users\lewis\Desktop\backoff\tests\HelpersTest.php:66
    
    FAILURES!
    Tests: 29, Assertions: 68, Failures: 1.
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    ....................F........                                     29 / 29 (100%)
    
    Time: 612 ms, Memory: 4.00MB
    
    There was 1 failure:
    
    1) STS\Backoff\HelpersTest::testWaitCap
    Failed asserting that false is true.
    
    C:\Users\lewis\Desktop\backoff\tests\HelpersTest.php:66
    
    FAILURES!
    Tests: 29, Assertions: 68, Failures: 1.
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    ..........F..................                                     29 / 29 (100%)
    
    Time: 559 ms, Memory: 4.00MB
    
    There was 1 failure:
    
    1) STS\Backoff\BackoffTest::testWait
    Failed asserting that false is true.
    
    C:\Users\lewis\Desktop\backoff\tests\BackoffTest.php:151
    
    FAILURES!
    Tests: 29, Assertions: 68, Failures: 1.
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    ....................F........                                     29 / 29 (100%)
    
    Time: 553 ms, Memory: 4.00MB
    
    There was 1 failure:
    
    1) STS\Backoff\HelpersTest::testWaitCap
    Failed asserting that false is true.
    
    C:\Users\lewis\Desktop\backoff\tests\HelpersTest.php:66
    
    FAILURES!
    Tests: 29, Assertions: 68, Failures: 1.
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    ..........F..................                                     29 / 29 (100%)
    
    Time: 558 ms, Memory: 4.00MB
    
    There was 1 failure:
    
    1) STS\Backoff\BackoffTest::testWait
    Failed asserting that false is true.
    
    C:\Users\lewis\Desktop\backoff\tests\BackoffTest.php:151
    
    FAILURES!
    Tests: 29, Assertions: 68, Failures: 1.
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    .............................                                     29 / 29 (100%)
    
    Time: 573 ms, Memory: 4.00MB
    
    OK (29 tests, 68 assertions)
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    ..........F..................                                     29 / 29 (100%)
    
    Time: 554 ms, Memory: 4.00MB
    
    There was 1 failure:
    
    1) STS\Backoff\BackoffTest::testWait
    Failed asserting that false is true.
    
    C:\Users\lewis\Desktop\backoff\tests\BackoffTest.php:151
    
    FAILURES!
    Tests: 29, Assertions: 68, Failures: 1.
    
    lewis@ASUS-LAPTOP MINGW64 ~/Desktop/backoff (master)
    $ vendor/bin/phpunit
    PHPUnit 5.5.7 by Sebastian Bergmann and contributors.
    
    ..........F.........F........                                     29 / 29 (100%)
    
    Time: 544 ms, Memory: 4.00MB
    
    There were 2 failures:
    
    1) STS\Backoff\BackoffTest::testWait
    Failed asserting that false is true.
    
    C:\Users\lewis\Desktop\backoff\tests\BackoffTest.php:151
    
    2) STS\Backoff\HelpersTest::testWaitCap
    Failed asserting that false is true.
    
    C:\Users\lewis\Desktop\backoff\tests\HelpersTest.php:66
    
    FAILURES!
    Tests: 29, Assertions: 68, Failures: 2.
    

    I committed my changes to a separate branch, then ran git checkout master && git reset --hard. Looks like there is a small problem with the wait tests

    opened by Lewiscowles1986 1
  • FR: Logging

    FR: Logging

    Would be great to have some logging via an optionally passed in instance of Psr\Log\LoggerInterface. Would be interesting to log failed attempts with some information about the thrown exception and about the next retry strategy/delay.

    opened by arnegroskurth 3
Releases(1.2)
Owner
Signature Tech Studio
Signature Tech Studio
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
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
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
Michael Pratt 307 Dec 23, 2022
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

WeepingDogel 5 Dec 15, 2022
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.

National Library of Finland 195 Dec 24, 2022
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

Starless Sky Protocol 3 Jun 19, 2022
StringBuffer is a PHP class providing operations for efficient string buffering

StringBuffer is a PHP class providing operations for efficient string buffering

null 1 May 26, 2022
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

United Bible Societies Institute for Computer Assisted Publishing 3 Jul 29, 2022
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

null 28 Dec 18, 2022
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

null 1 Oct 14, 2021
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

Fresns 82 Dec 31, 2022
A comprehensive library for generating differences between two strings in multiple formats (unified, side by side HTML etc). Based on the difflib implementation in Python

PHP Diff Class Introduction A comprehensive library for generating differences between two hashable objects (strings or arrays). Generated differences

Chris Boulton 708 Dec 25, 2022
This plugin adds 95% of 1.16 blocks & items and their functionality

INether This plugin adds 95% of 1.16 blocks & items and their functionality Implemented blocks Ancient Debris All types of Basalt Crimson & Warped Fun

null 34 Dec 7, 2022
Laravel Blog Package. Easiest way to add a blog to your Laravel website. A package which adds wordpress functionality to your website and is compatible with laravel 8.

Laravel Blog Have you worked with Wordpress? Developers call this package wordpress-like laravel blog. Give our package a Star to support us ⭐ ?? Inst

Binshops 279 Dec 28, 2022