1
0
mirror of https://github.com/guzzle/guzzle.git synced 2025-02-25 02:22:57 +01:00

Greatly simplifying the backoff plugin

This commit is contained in:
Michael Dowling 2014-02-08 14:34:48 -08:00
parent 8869d8285a
commit 7f979204e8
7 changed files with 105 additions and 171 deletions

View File

@ -1,45 +0,0 @@
<?php
namespace Guzzle\Plugin\Backoff;
use Guzzle\Http\Event\AbstractTransferStatsEvent;
abstract class AbstractRetryFilter implements RetryFilterInterface
{
/** @var RetryFilterInterface */
protected $next;
/** @var array Default cURL errors to retry */
protected static $defaultErrorCodes = array();
/** @var array Error codes that can be retried */
protected $errorCodes;
/**
* @param RetryFilterInterface $next The next filter in the chain
* @param array $codes Error codes to retry
*/
public function __construct(RetryFilterInterface $next = null, $codes = null)
{
$this->next = $next;
$this->errorCodes = array_fill_keys($codes ?: static::$defaultErrorCodes, 1);
}
/**
* Get the default failure codes to retry
*
* @return array
*/
public static function getDefaultFailureCodes()
{
return static::$defaultErrorCodes;
}
public function shouldRetry($retries, AbstractTransferStatsEvent $event)
{
return $this->should($retries, $event)
?: $this->next && $this->next->shouldRetry($retries, $event);
}
abstract protected function should($retries, AbstractTransferStatsEvent $event);
}

View File

@ -11,23 +11,23 @@ use Guzzle\Http\Event\RequestEvents;
*/
class BackoffPlugin implements EventSubscriberInterface
{
/** @var RetryFilterInterface */
protected $filter;
/** @var callable */
private $filter;
/** @var callable */
protected $delayFunc;
private $delayFunc;
/** @var int */
protected $maxRetries;
private $maxRetries;
/**
* @param RetryFilterInterface $filter Filter used to determine whether or not to retry a request
* @param callable $delayFunc Callable that accepts the number of retries and returns the amount of
* of time in seconds to delay.
* @param int $maxRetries Maximum number of retries
* @param callable $filter Filter used to determine whether or not to retry a request
* @param callable $delayFunc Callable that accepts the number of retries and returns the amount of
* of time in seconds to delay.
* @param int $maxRetries Maximum number of retries
*/
public function __construct(
RetryFilterInterface $filter,
callable $filter,
callable $delayFunc,
$maxRetries = 3
) {
@ -50,14 +50,16 @@ class BackoffPlugin implements EventSubscriberInterface
array $httpCodes = null,
array $curlCodes = null
) {
return new self(
new HttpStatusFilter(
new CurlResultFilter($curlCodes),
$httpCodes
),
new ExponentialDelay(),
$maxRetries
);
if (extension_loaded('curl')) {
$filter = self::createChainFilter([
self::createStatusFilter($httpCodes),
self::createCurlFilter($curlCodes)
]);
} else {
$filter = self::createStatusFilter($httpCodes);
}
return new self($filter, array(__CLASS__, 'exponentialDelay'), $maxRetries);
}
public static function getSubscribedEvents()
@ -70,12 +72,92 @@ class BackoffPlugin implements EventSubscriberInterface
public function onRequestSent(AbstractTransferStatsEvent $event)
{
$retries = (int) $event->getRequest()->getConfig()->get('retries');
if ($retries < $this->maxRetries && $this->filter->shouldRetry($retries, $event)) {
$request = $event->getRequest();
sleep(call_user_func($this->delayFunc, $retries));
$request->getConfig()->set('retries', ++$retries);
$event->intercept($event->getClient()->send($request));
$request = $event->getRequest();
$retries = (int) $request->getConfig()->get('retries');
if ($retries < $this->maxRetries) {
$filterFn = $this->filter;
if ($filterFn($retries, $event)) {
$delayFn = $this->delayFunc;
sleep($delayFn($retries));
$request->getConfig()->set('retries', ++$retries);
$event->intercept($event->getClient()->send($request));
}
}
}
/**
* Returns an exponential delay calculation
*
* @param int $retries Number of retries so far
*
* @return int
*/
public static function exponentialDelay($retries)
{
return (int) pow(2, $retries - 1);
}
/**
* Creates a retry filter based on HTTP status codes
*
* @param array $failureStatuses Pass an array of status codes to override the default of [500, 503]
*
* @return callable
*/
public static function createStatusFilter(array $failureStatuses = null)
{
$failureStatuses = $failureStatuses ?: [500, 503];
$failureStatuses = array_fill_keys($failureStatuses, 1);
return function ($retries, AbstractTransferStatsEvent $event) use ($failureStatuses) {
if (!($response = $event->getResponse())) {
return false;
}
return isset($failureStatuses[$response->getStatusCode()]);
};
}
/**
* Creates a retry filter based on cURL error codes.
*
* @param array $errorCodes Pass an array of curl error codes to override the default list of error codes.
*
* @return callable
*/
public static function createCurlFilter($errorCodes = null)
{
$errorCodes = $errorCodes ?: [CURLE_COULDNT_RESOLVE_HOST,
CURLE_COULDNT_CONNECT, CURLE_PARTIAL_FILE, CURLE_WRITE_ERROR,
CURLE_READ_ERROR, CURLE_OPERATION_TIMEOUTED,
CURLE_SSL_CONNECT_ERROR, CURLE_HTTP_PORT_FAILED, CURLE_GOT_NOTHING,
CURLE_SEND_ERROR, CURLE_RECV_ERROR];
$errorCodes = array_fill_keys($errorCodes, 1);
return function ($retries, AbstractTransferStatsEvent $event) use ($errorCodes) {
return isset($errorCodes[(int) $event->getTransferInfo('curl_result')]);
};
}
/**
* Creates a chain of callables that triggers one after the other until a callable returns true.
*
* @param array $filters Array of callables that accept the number of retries and an after send event and return
* true to retry the transaction or false to not retry.
*
* @return callable Returns a filter that can be used to determine if a transaction should be retried
*/
public static function createChainFilter(array $filters)
{
return function ($retries, AbstractTransferStatsEvent $event) use ($filters) {
foreach ($filters as $filter) {
if (call_user_func($filter, $retries, $event)) {
return true;
}
}
return false;
};
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace Guzzle\Plugin\Backoff;
use Guzzle\Http\Event\AbstractTransferStatsEvent;
/**
* Strategy used to retry when certain cURL error codes are encountered.
*/
class CurlResultFilter extends AbstractRetryFilter
{
/** @var array Default cURL errors to retry */
protected static $defaultErrorCodes = array(
CURLE_COULDNT_RESOLVE_HOST, CURLE_COULDNT_CONNECT, CURLE_PARTIAL_FILE, CURLE_WRITE_ERROR, CURLE_READ_ERROR,
CURLE_OPERATION_TIMEOUTED, CURLE_SSL_CONNECT_ERROR, CURLE_HTTP_PORT_FAILED, CURLE_GOT_NOTHING,
CURLE_SEND_ERROR, CURLE_RECV_ERROR
);
protected function should($retries, AbstractTransferStatsEvent $event)
{
return isset($this->errorCodes[(int) $event->getTransferInfo('curl_result')]);
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace Guzzle\Plugin\Backoff;
/**
* Implements an exponential backoff retry strategy.
*/
class ExponentialDelay
{
public function __invoke($retries)
{
return (int) pow(2, $retries - 1);
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace Guzzle\Plugin\Backoff;
use Guzzle\Http\Event\AbstractTransferStatsEvent;
/**
* Strategy used to retry HTTP requests based on the response code.
*
* Retries 500 and 503 error by default.
*/
class HttpStatusFilter extends AbstractRetryFilter
{
/** @var array Default cURL errors to retry */
protected static $defaultErrorCodes = [500, 503];
protected function should($retries, AbstractTransferStatsEvent $event)
{
if (!($response = $event->getResponse())) {
return false;
}
return isset($this->errorCodes[$response->getStatusCode()]);
}
}

View File

@ -1,20 +0,0 @@
<?php
namespace Guzzle\Plugin\Backoff;
use Guzzle\Http\Event\AbstractTransferStatsEvent;
/**
* Strategy used to retry HTTP requests when the response's reason phrase matches one of the registered phrases.
*/
class ReasonPhraseFilter extends AbstractRetryFilter
{
protected function should($retries, AbstractTransferStatsEvent $event)
{
if (!($response = $event->getResponse())) {
return false;
}
return isset($this->errorCodes[$response->getReasonPhrase()]);
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace Guzzle\Plugin\Backoff;
use Guzzle\Http\Event\AbstractTransferStatsEvent;
/**
* Determines if a request should be retried
*/
interface RetryFilterInterface
{
/**
* Determines if a request should be retried
*
* @param int $retries Number of retries of the request
* @param AbstractTransferStatsEvent $event Event used to determine whether or not to retry
*
* @return bool|int Returns false to not retry or the number of seconds to delay between retries
*/
public function shouldRetry($retries, AbstractTransferStatsEvent $event);
}