Skip to content

Commit

Permalink
allow to silence an exception when finally failed
Browse files Browse the repository at this point in the history
  • Loading branch information
deminy committed Jan 20, 2021
1 parent 7c900b2 commit 26310e6
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 5 deletions.
48 changes: 45 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
[![Latest Unstable Version](https://poser.pugx.org/Crowdstar/exponential-backoff/v/unstable.svg)](https://packagist.org/packages/crowdstar/exponential-backoff)
[![License](https://poser.pugx.org/Crowdstar/exponential-backoff/license.svg)](https://packagist.org/packages/crowdstar/exponential-backoff)

* [Summary](#summary)
* [Installation](#installation)
* [Sample Usage](#sample-usage)
* [1. Retry When Return Value Is Empty](#1-retry-when-return-value-is-empty)
* [2. Retry When Certain Exception Thrown Out](#2-retry-when-certain-exception-thrown-out)
* [Don't Throw Out an Exception When Finally Failed](#dont-throw-out-an-exception-when-finally-failed)
* [3. Retry When Customized Condition Met](#3-retry-when-customized-condition-met)
* [4. More Options When Doing Exponential Backoff](#4-more-options-when-doing-exponential-backoff)
* [5. To Disable Exponential Backoff Temporarily](#5-to-disable-exponential-backoff-temporarily)
* [Sample Scripts](#sample-scripts)

# Summary

Exponential back-offs prevent overloading an unavailable service by doubling the timeout each iteration. This class uses
Expand Down Expand Up @@ -65,6 +76,39 @@ try {
?>
```

### 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
<?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
Expand Down Expand Up @@ -114,9 +158,7 @@ $backoff = new ExponentialBackoff(
new class extends AbstractRetryCondition {
public function met($result, ?Exception $e): bool
{
$exception = $this->getException();

return (empty($e) || (!($e instanceof $exception)));
return (empty($e) || (!($e instanceof Exception)));
}
}
);
Expand Down
15 changes: 15 additions & 0 deletions src/AbstractRetryCondition.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@
*/
abstract class AbstractRetryCondition
{
/**
* When the caller finally fails with an exception caught, this method tells if the exception should be thrown out
* or not.
*
* Saying that you are creating a customized condition where exceptions are thrown out. In this case, you can use
* this method to decide if the exception should be thrown out (when finally failed), or when to throw out the
* exception.
*
* @return bool
*/
public function throwable(): bool
{
return true;
}

/**
* Don't retry if conditions met.
*
Expand Down
4 changes: 2 additions & 2 deletions src/ExponentialBackoff.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ public function run(Closure $c, ...$params)
}
} while ($this->retry($result, $e));

// If you still have an exception, throw it
if (!empty($e)) {
// If you still have an exception, throw it out if needed.
if (!empty($e) && $this->getRetryCondition()->throwable()) {
throw $e;
}

Expand Down
98 changes: 98 additions & 0 deletions tests/unit/CustomizedConditionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

/**************************************************************************
* Copyright 2018 Glu Mobile Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*************************************************************************/

declare(strict_types=1);

namespace CrowdStar\Tests\Backoff;

use CrowdStar\Backoff\AbstractRetryCondition;
use CrowdStar\Backoff\ExponentialBackoff;
use Exception;
use PHPUnit\Framework\TestCase;

/**
* Class CustomizedConditionTest
*
* @package CrowdStar\Tests\Backoff
*/
class CustomizedConditionTest extends TestCase
{
/**
* The $backoff object in this test is the same as the one in the next method self::testUnthrowableException(),
* except that method $backoff->throwable() returns TRUE.
*
* @covers \CrowdStar\Backoff\AbstractRetryCondition::throwable()
* @covers \CrowdStar\Backoff\ExponentialBackoff::run()
*/
public function testThrowableException()
{
$helper = (new Helper())->setException(Exception::class)->setExpectedFailedAttempts(4);
$backoff = (new ExponentialBackoff(
new class extends AbstractRetryCondition {
public function throwable(): bool
{
// This tells the caller to throw out the exception when finally failed.
return true;
}
public function met($result, ?Exception $e): bool
{
return (empty($e) || (!($e instanceof Exception)));
}
}
));

$this->expectException(Exception::class); // Next function call will through out an exception.
$backoff->run(
function () use ($helper) {
return $helper->getValueAfterExpectedNumberOfFailedAttemptsWithExceptionsThrownOut();
}
);
}

/**
* The $backoff object in this test is the same as the one in the previous method self::testThrowableException(),
* except that method $backoff->throwable() returns FALSE.
*
* @covers \CrowdStar\Backoff\AbstractRetryCondition::throwable()
* @covers \CrowdStar\Backoff\ExponentialBackoff::run()
*/
public function testUnthrowableException()
{
$helper = (new Helper())->setException(Exception::class)->setExpectedFailedAttempts(4);
$backoff = (new ExponentialBackoff(
new class extends AbstractRetryCondition {
public function throwable(): bool
{
// This tells the caller NOT to throw out the exception when finally failed.
return false;
}
public function met($result, ?Exception $e): bool
{
return (empty($e) || (!($e instanceof Exception)));
}
}
));

$this->addToAssertionCount(1); // Since there is no assertions in this test, we manually add the count by 1.
$backoff->run(
function () use ($helper) {
return $helper->getValueAfterExpectedNumberOfFailedAttemptsWithExceptionsThrownOut();
}
);
}
}

0 comments on commit 26310e6

Please sign in to comment.