crowdstar/exponential-backoff

Prevent overloading an unavailable service by doubling the timeout each iteration.

3.0.11 2023-05-05 22:27 UTC

This package is auto-updated.

Last update: 2024-03-30 18:08:36 UTC


README

Library Status Latest Stable Version Latest Unstable Version License

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