diff options
author | Andrew Dolgov <[email protected]> | 2022-11-23 21:14:33 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2022-11-23 21:14:33 +0300 |
commit | 0c8af4992cb0f7589dcafaad65ada12753c64594 (patch) | |
tree | 18e83d068c3e7dd2499331de977782b382279396 /vendor/aws/aws-sdk-php/src/RetryMiddleware.php |
initial
Diffstat (limited to 'vendor/aws/aws-sdk-php/src/RetryMiddleware.php')
-rw-r--r-- | vendor/aws/aws-sdk-php/src/RetryMiddleware.php | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/vendor/aws/aws-sdk-php/src/RetryMiddleware.php b/vendor/aws/aws-sdk-php/src/RetryMiddleware.php new file mode 100644 index 0000000..4fa81e9 --- /dev/null +++ b/vendor/aws/aws-sdk-php/src/RetryMiddleware.php @@ -0,0 +1,277 @@ +<?php +namespace Aws; + +use Aws\Exception\AwsException; +use Aws\Retry\RetryHelperTrait; +use GuzzleHttp\Exception\RequestException; +use Psr\Http\Message\RequestInterface; +use GuzzleHttp\Promise\PromiseInterface; +use GuzzleHttp\Promise; + +/** + * Middleware that retries failures. V1 implemention that supports 'legacy' mode. + * + * @internal + */ +class RetryMiddleware +{ + use RetryHelperTrait; + + private static $retryStatusCodes = [ + 500 => true, + 502 => true, + 503 => true, + 504 => true + ]; + + private static $retryCodes = [ + // Throttling error + 'RequestLimitExceeded' => true, + 'Throttling' => true, + 'ThrottlingException' => true, + 'ThrottledException' => true, + 'ProvisionedThroughputExceededException' => true, + 'RequestThrottled' => true, + 'BandwidthLimitExceeded' => true, + 'RequestThrottledException' => true, + 'TooManyRequestsException' => true, + 'IDPCommunicationError' => true, + 'EC2ThrottledException' => true, + ]; + + private $decider; + private $delay; + private $nextHandler; + private $collectStats; + + public function __construct( + callable $decider, + callable $delay, + callable $nextHandler, + $collectStats = false + ) { + $this->decider = $decider; + $this->delay = $delay; + $this->nextHandler = $nextHandler; + $this->collectStats = (bool) $collectStats; + } + + /** + * Creates a default AWS retry decider function. + * + * The optional $extraConfig parameter is an associative array + * that specifies additional retry conditions on top of the ones specified + * by default by the Aws\RetryMiddleware class, with the following keys: + * + * - errorCodes: (string[]) An indexed array of AWS exception codes to retry. + * Optional. + * - statusCodes: (int[]) An indexed array of HTTP status codes to retry. + * Optional. + * - curlErrors: (int[]) An indexed array of Curl error codes to retry. Note + * these should be valid Curl constants. Optional. + * + * @param int $maxRetries + * @param array $extraConfig + * @return callable + */ + public static function createDefaultDecider( + $maxRetries = 3, + $extraConfig = [] + ) { + $retryCurlErrors = []; + if (extension_loaded('curl')) { + $retryCurlErrors[CURLE_RECV_ERROR] = true; + } + + return function ( + $retries, + CommandInterface $command, + RequestInterface $request, + ResultInterface $result = null, + $error = null + ) use ($maxRetries, $retryCurlErrors, $extraConfig) { + // Allow command-level options to override this value + $maxRetries = null !== $command['@retries'] ? + $command['@retries'] + : $maxRetries; + + $isRetryable = self::isRetryable( + $result, + $error, + $retryCurlErrors, + $extraConfig + ); + + if ($retries >= $maxRetries) { + if (!empty($error) + && $error instanceof AwsException + && $isRetryable + ) { + $error->setMaxRetriesExceeded(); + } + return false; + } + + return $isRetryable; + }; + } + + private static function isRetryable( + $result, + $error, + $retryCurlErrors, + $extraConfig = [] + ) { + $errorCodes = self::$retryCodes; + if (!empty($extraConfig['error_codes']) + && is_array($extraConfig['error_codes']) + ) { + foreach($extraConfig['error_codes'] as $code) { + $errorCodes[$code] = true; + } + } + + $statusCodes = self::$retryStatusCodes; + if (!empty($extraConfig['status_codes']) + && is_array($extraConfig['status_codes']) + ) { + foreach($extraConfig['status_codes'] as $code) { + $statusCodes[$code] = true; + } + } + + if (!empty($extraConfig['curl_errors']) + && is_array($extraConfig['curl_errors']) + ) { + foreach($extraConfig['curl_errors'] as $code) { + $retryCurlErrors[$code] = true; + } + } + + if (!$error) { + if (!isset($result['@metadata']['statusCode'])) { + return false; + } + return isset($statusCodes[$result['@metadata']['statusCode']]); + } + + if (!($error instanceof AwsException)) { + return false; + } + + if ($error->isConnectionError()) { + return true; + } + + if (isset($errorCodes[$error->getAwsErrorCode()])) { + return true; + } + + if (isset($statusCodes[$error->getStatusCode()])) { + return true; + } + + if (count($retryCurlErrors) + && ($previous = $error->getPrevious()) + && $previous instanceof RequestException + ) { + if (method_exists($previous, 'getHandlerContext')) { + $context = $previous->getHandlerContext(); + return !empty($context['errno']) + && isset($retryCurlErrors[$context['errno']]); + } + + $message = $previous->getMessage(); + foreach (array_keys($retryCurlErrors) as $curlError) { + if (strpos($message, 'cURL error ' . $curlError . ':') === 0) { + return true; + } + } + } + + return false; + } + + /** + * Delay function that calculates an exponential delay. + * + * Exponential backoff with jitter, 100ms base, 20 sec ceiling + * + * @param $retries - The number of retries that have already been attempted + * + * @return int + * + * @link https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ + */ + public static function exponentialDelay($retries) + { + return mt_rand(0, (int) min(20000, (int) pow(2, $retries) * 100)); + } + + /** + * @param CommandInterface $command + * @param RequestInterface $request + * + * @return PromiseInterface + */ + public function __invoke( + CommandInterface $command, + RequestInterface $request = null + ) { + $retries = 0; + $requestStats = []; + $monitoringEvents = []; + $handler = $this->nextHandler; + $decider = $this->decider; + $delay = $this->delay; + + $request = $this->addRetryHeader($request, 0, 0); + + $g = function ($value) use ( + $handler, + $decider, + $delay, + $command, + $request, + &$retries, + &$requestStats, + &$monitoringEvents, + &$g + ) { + $this->updateHttpStats($value, $requestStats); + + if ($value instanceof MonitoringEventsInterface) { + $reversedEvents = array_reverse($monitoringEvents); + $monitoringEvents = array_merge($monitoringEvents, $value->getMonitoringEvents()); + foreach ($reversedEvents as $event) { + $value->prependMonitoringEvent($event); + } + } + if ($value instanceof \Exception || $value instanceof \Throwable) { + if (!$decider($retries, $command, $request, null, $value)) { + return Promise\Create::rejectionFor( + $this->bindStatsToReturn($value, $requestStats) + ); + } + } elseif ($value instanceof ResultInterface + && !$decider($retries, $command, $request, $value, null) + ) { + return $this->bindStatsToReturn($value, $requestStats); + } + + // Delay fn is called with 0, 1, ... so increment after the call. + $delayBy = $delay($retries++); + $command['@http']['delay'] = $delayBy; + if ($this->collectStats) { + $this->updateStats($retries, $delayBy, $requestStats); + } + + // Update retry header with retry count and delayBy + $request = $this->addRetryHeader($request, $retries, $delayBy); + + return $handler($command, $request)->then($g, $g); + }; + + return $handler($command, $request)->then($g, $g); + } +} |