summaryrefslogtreecommitdiff
path: root/vendor/aws/aws-sdk-php/src/RetryMiddleware.php
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2022-11-23 21:14:33 +0300
committerAndrew Dolgov <[email protected]>2022-11-23 21:14:33 +0300
commit0c8af4992cb0f7589dcafaad65ada12753c64594 (patch)
tree18e83d068c3e7dd2499331de977782b382279396 /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.php277
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);
+ }
+}