crowdstar / exponential-backoff
Prevent overloading an unavailable service by doubling the timeout each iteration.
Installs: 10 360
Dependents: 1
Suggesters: 0
Security: 0
Stars: 14
Watchers: 6
Forks: 1
Open Issues: 0
Requires
- php: >=7.1
Requires (Dev)
- crowdstar/reflection: ~1.0.0 || ~2.0.0
- phpunit/phpunit: ~7.0 || ~8.0 || ~9.0
- swoole/ide-helper: ~5.0
Suggests
- ext-swoole: Allow to do exponential backoff in non-blocking mode in Swoole
README
Summary
Exponential back-offs prevent overloading an unavailable service by doubling the timeout each iteration. This class uses an exponential back-off algorithm to calculate the timeout for the next request.
This library allows doing exponential backoff in non-blocking mode in Swoole.
Installation
composer require crowdstar/exponential-backoff:~3.0.0
Sample Usage
In following code pieces, we assume that you want to store return value of method MyClass::fetchData() in variable $result, and you want to do exponential backoff on that because something unexpected could happen when running method MyClass::fetchData().
1. Retry When Return Value Is Empty
Following code is to try to fetch some non-empty data back with method MyClass::fetchData(). This piece of code will try a few more times (by default 4) until either we get some non-empty data back, or we have reached maximum numbers of retries.
<?php use CrowdStar\Backoff\EmptyValueCondition; use CrowdStar\Backoff\ExponentialBackoff; $result = (new ExponentialBackoff(new EmptyValueCondition()))->run( function () { return MyClass::fetchData(); } ); ?>
2. Retry When Certain Exceptions Thrown Out
Following code is to try to fetch some data back with method MyClass::fetchData(), which may throw out exceptions. This piece of code will try a few more times (by default 4) until either we get some data back, or we have reached maximum numbers of retries.
NOTE: Internal PHP errors (class Error) won't trigger exponential backoff. They should be fixed manually.
<?php use CrowdStar\Backoff\ExceptionBasedCondition; use CrowdStar\Backoff\ExponentialBackoff; // Allow to catch multiple types of exceptions and throwable objects. $backoff = new ExponentialBackoff(new ExceptionBasedCondition(Exception::class, Throwable::class)); try { $result = $backoff->run( function () { return MyClass::fetchData(); } ); } catch (Throwable $t) { // Handle the errors here. } ?>
Don't Throw Out an Exception When Finally Failed
When method call MyClass::fetchData() finally fails with an exception caught, we can silence the exception without throwing it out by overriding method AbstractRetryCondition::throwable():
<?php use CrowdStar\Backoff\AbstractRetryCondition; use CrowdStar\Backoff\ExponentialBackoff; $backoff = new ExponentialBackoff( new class extends AbstractRetryCondition { public function throwable(): bool { return false; } public function met($result, ?Exception $e): bool { return (empty($e) || (!($e instanceof Exception))); } } ); $backoff->run( function () { return MyClass::fetchData(); } ); ?>
If needed, you can have more complex logic defined when overriding method AbstractRetryCondition::throwable().
3. Retry When Customized Condition Met
Following code is to try to fetch some non-empty data back with method MyClass::fetchData(). This piece of code works the same as the first example, except that here it's implemented with a customized condition class instead of class \CrowdStar\Backoff\EmptyValueCondition.
<?php use CrowdStar\Backoff\AbstractRetryCondition; use CrowdStar\Backoff\ExponentialBackoff; $backoff = new ExponentialBackoff( new class extends AbstractRetryCondition { public function met($result, ?Exception $e): bool { return !empty($result); } } ); $result = $backoff->run( function () { return MyClass::fetchData(); } ); ?>
4. More Options When Doing Exponential Backoff
Following code is to try to fetch some data back with method MyClass::fetchData(). This piece of code works the same as the second example, except that here it's implemented with a customized condition class instead of class \CrowdStar\Backoff\ExceptionBasedCondition.
In this piece of code, we also show what options are available when doing exponential backoff with the package.
<?php use CrowdStar\Backoff\AbstractRetryCondition; use CrowdStar\Backoff\EmptyValueCondition; use CrowdStar\Backoff\ExceptionBasedCondition; use CrowdStar\Backoff\ExponentialBackoff; $backoff = new ExponentialBackoff(new EmptyValueCondition()); $backoff = new ExponentialBackoff(new ExceptionBasedCondition()); $backoff = new ExponentialBackoff(new ExceptionBasedCondition(Exception::class, Throwable::class)); $backoff = new ExponentialBackoff( new class extends AbstractRetryCondition { public function met($result, ?Exception $e): bool { return (empty($e) || (!($e instanceof Exception))); } } ); $backoff ->setType(ExponentialBackoff::TYPE_SECONDS) ->setType(ExponentialBackoff::TYPE_MICROSECONDS) ->setMaxAttempts(3) ->setMaxAttempts(4); $result = $backoff->run( function () { return MyClass::fetchData(); } ); ?>
5. To Disable Exponential Backoff Temporarily
There are two ways to disable exponential backoff temporarily for code piece like following:
<?php $result = MyClass::fetchData(); ?>
First, you may disable exponential backoff temporarily by calling method \CrowdStar\Backoff\ExponentialBackoff::disable(). For example:
<?php use CrowdStar\Backoff\EmptyValueCondition; use CrowdStar\Backoff\ExponentialBackoff; $backoff = new ExponentialBackoff(new EmptyValueCondition()); $backoff->disable(); $result = $backoff->run(function () {return MyClass::fetchData();}); ?>
You may also disable exponential backoff temporarily by using class \CrowdStar\Backoff\NullCondition:
<?php use CrowdStar\Backoff\ExponentialBackoff; use CrowdStar\Backoff\NullCondition; $result = (new ExponentialBackoff(new NullCondition())) ->setRetryCondition(new NullCondition()) // The method here is for demonstration purpose. ->run(function () {return MyClass::fetchData();}); ?>
All these 3 code piece work the same, having return value of method call MyClass::fetchData() assigned to variable $result.
Sample Scripts
Sample scripts can be found under folder examples/. Before running them under CLI, please do a composer update first:
composer update -n