diff --git a/CHANGELOG.md b/CHANGELOG.md index 3203b98..0504306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ ### Added - `PluginClientFactory` to create `PluginClient` instances. +- Added new option 'delay' for `RetryPlugin`. +- Added new option 'decider' for `RetryPlugin`. + +### Changed + +- The `RetryPlugin` does now wait between retries. To disable/change this feature you must write something like: + +```php +$plugin = new RetryPlugin(['delay' => function(RequestInterface $request, Exception $e, $retries) { + return 0; +}); +``` ### Deprecated diff --git a/spec/Plugin/RetryPluginSpec.php b/spec/Plugin/RetryPluginSpec.php index 981c16d..269e90a 100644 --- a/spec/Plugin/RetryPluginSpec.php +++ b/spec/Plugin/RetryPluginSpec.php @@ -101,4 +101,12 @@ function it_does_not_keep_history_of_old_failure(RequestInterface $request, Resp $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); $this->handleRequest($request, $next, function () {})->shouldReturnAnInstanceOf('Http\Client\Promise\HttpFulfilledPromise'); } + + function it_has_an_exponential_default_delay(RequestInterface $request, Exception\HttpException $exception) + { + $this->defaultDelay($request, $exception, 0)->shouldBe(1000); + $this->defaultDelay($request, $exception, 1)->shouldBe(2000); + $this->defaultDelay($request, $exception, 2)->shouldBe(4000); + $this->defaultDelay($request, $exception, 3)->shouldBe(8000); + } } diff --git a/src/Plugin/RetryPlugin.php b/src/Plugin/RetryPlugin.php index bbb1ffa..1a58004 100644 --- a/src/Plugin/RetryPlugin.php +++ b/src/Plugin/RetryPlugin.php @@ -24,6 +24,16 @@ final class RetryPlugin implements Plugin */ private $retry; + /** + * @var callable + */ + private $delay; + + /** + * @var callable + */ + private $decider; + /** * Store the retry counter for each request. * @@ -35,6 +45,8 @@ final class RetryPlugin implements Plugin * @param array $config { * * @var int $retries Number of retries to attempt if an exception occurs before letting the exception bubble up. + * @var callable $decider A callback that gets a request and an exception to decide after a failure whether the request should be retried. + * @var callable $delay A callback that gets a request, an exception and the number of retries and returns how many milliseconds we should wait before trying again. * } */ public function __construct(array $config = []) @@ -42,11 +54,19 @@ public function __construct(array $config = []) $resolver = new OptionsResolver(); $resolver->setDefaults([ 'retries' => 1, + 'decider' => function (RequestInterface $request, Exception $e) { + return true; + }, + 'delay' => __CLASS__.'::defaultDelay', ]); $resolver->setAllowedTypes('retries', 'int'); + $resolver->setAllowedTypes('decider', 'callable'); + $resolver->setAllowedTypes('delay', 'callable'); $options = $resolver->resolve($config); $this->retry = $options['retries']; + $this->decider = $options['decider']; + $this->delay = $options['delay']; } /** @@ -73,12 +93,30 @@ public function handleRequest(RequestInterface $request, callable $next, callabl throw $exception; } - ++$this->retryStorage[$chainIdentifier]; + if (!call_user_func($this->decider, $request, $exception)) { + throw $exception; + } + + $time = call_user_func($this->delay, $request, $exception, $this->retryStorage[$chainIdentifier]); + usleep($time); // Retry in synchrone + ++$this->retryStorage[$chainIdentifier]; $promise = $this->handleRequest($request, $next, $first); return $promise->wait(); }); } + + /** + * @param RequestInterface $request + * @param Exception $e + * @param int $retries The number of retries we made before. First time this get called it will be 0. + * + * @return int + */ + public static function defaultDelay(RequestInterface $request, Exception $e, $retries) + { + return pow(2, $retries) * 1000; + } }