Skip to content

Instantly share code, notes, and snippets.

@orottier
Last active July 21, 2023 10:10
Show Gist options
  • Select an option

  • Save orottier/3ac79378dd38b91ac6953c8618708eb4 to your computer and use it in GitHub Desktop.

Select an option

Save orottier/3ac79378dd38b91ac6953c8618708eb4 to your computer and use it in GitHub Desktop.

Revisions

  1. orottier revised this gist Aug 16, 2020. 2 changed files with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion retry.php → retry.php
    Original file line number Diff line number Diff line change
    @@ -33,7 +33,7 @@ function retry(callable $callable, $expectedErrors, $maxRetries = 5, $initialWai
    // exponential backoff
    if ($maxRetries > 0) {
    usleep($initialWait * 1E6);
    return retry($callable, $expectedErrors, $maxRetries - 1, $initialWait * $exponent);
    return retry($callable, $expectedErrors, $maxRetries - 1, $initialWait * $exponent, $exponent);
    }

    // max retries reached
    File renamed without changes.
  2. orottier revised this gist Sep 16, 2016. 3 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
    File renamed without changes.
  3. orottier renamed this gist Sep 16, 2016. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. orottier created this gist Sep 16, 2016.
    81 changes: 81 additions & 0 deletions RetryTest.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,81 @@
    <?php

    class RetryTest extends TestCase
    {
    public function setUp()
    {
    parent::setUp();
    // abuse superglobal to keep track of state
    $_GET['a'] = 0;
    }

    protected static function failOnce($exception)
    {
    $_GET['a']++;
    if ($_GET['a'] == 1) {
    throw $exception;
    }
    return $_GET['a'];
    }

    public function testSucceed()
    {
    $callable = function() { return 'hello'; };
    $return = retry($callable, Exception::class);
    $this->assertSame($return, 'hello');
    }

    public function testFailOnce()
    {
    $callable = function() {
    return self::failOnce(new Exception('Fail once'));
    };

    $return = retry($callable, Exception::class, 3, 0);
    $this->assertSame(2, $return);
    }

    public function testFailOnceInherited()
    {
    $callable = function() {
    return self::failOnce(new UnexpectedValueException('Fail once'));
    };

    $return = retry($callable, Exception::class, 3, 0);
    $this->assertSame(2, $return);
    }

    /**
    * @expectedException Exception
    */
    public function testFailAfterMax()
    {
    $callable = function() { throw new Exception('Error'); };
    $return = retry($callable, Exception::class, 3, 0);
    }

    public function testFailCount()
    {
    $callable = function() {
    $_GET['a']++;
    throw new Exception('FailCount');
    };
    $retries = 5;

    try {
    $return = retry($callable, Exception::class, $retries, 0);
    } catch(Exception $e) {
    $this->assertSame($e->getMessage(), 'FailCount');
    }
    $this->assertSame($_GET['a'], $retries + 1);
    }

    /**
    * @expectedException Exception
    */
    public function testFailUnexpected()
    {
    $callable = function() { throw new Exception('Error'); };
    $return = retry($callable, UnexpectedValueException::class);
    }
    }
    42 changes: 42 additions & 0 deletions retry.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,42 @@
    <?php

    /*
    * Retry function for e.g. external API calls
    *
    * Will try the risky API call, and retries with an ever increasing delay if it fails
    * Throws the latest error if $maxRetries is reached,
    * otherwise it will return the value returned from the closure.
    *
    * You specify the exceptions that you expect to occur.
    * If another exception is thrown, the script aborts
    *
    */

    function retry(callable $callable, $expectedErrors, $maxRetries = 5, $initialWait = 1.0, $exponent = 2)
    {
    if (!is_array($expectedErrors)) {
    $expectedErrors = [$expectedErrors];
    }

    try {
    return call_user_func($callable);
    } catch (Exception $e) {
    // get whole inheritance chain
    $errors = class_parents($e);
    array_push($errors, get_class($e));

    // if unexpected, re-throw
    if (!array_intersect($errors, $expectedErrors)) {
    throw $e;
    }

    // exponential backoff
    if ($maxRetries > 0) {
    usleep($initialWait * 1E6);
    return retry($callable, $expectedErrors, $maxRetries - 1, $initialWait * $exponent);
    }

    // max retries reached
    throw $e;
    }
    }
    18 changes: 18 additions & 0 deletions usage.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    <?php

    $expectedErrors = 'Google_Service_Exception';
    $maxRetries = 5; // means we try max 6 times
    $initialWait = 1.0; // seconds
    $exponent = 2; // double the waiting time each try

    /* your application stuff */
    $service = ...
    $calendarAddress = ...
    $vEvent = ...

    $returnValue = retry(
    function() use ($service, $calendarAddress, $vEvent) {
    return $service->events->insert($calendarAddress, $vEvent);
    },
    $expectedErrors, $maxRetries, $initialWait, $exponent
    );