['ifMetagenerationMatch', 'etag'], // Currently etag is not supported, so this preCondition never available 'buckets.setIamPolicy' => ['etag'], 'buckets.update' => ['ifMetagenerationMatch', 'etag'], 'hmacKey.update' => ['etag'], 'objects.compose' => ['ifGenerationMatch'], 'objects.copy' => ['ifGenerationMatch'], 'objects.delete' => ['ifGenerationMatch'], 'objects.insert' => ['ifGenerationMatch', 'ifGenerationNotMatch'], 'objects.patch' => ['ifMetagenerationMatch', 'etag'], 'objects.rewrite' => ['ifGenerationMatch'], 'objects.update' => ['ifMetagenerationMatch'], ]; /** * Retry strategies which enforce certain behaviour like: * - Always retrying a call when an exception occurs(within the limits of 'max retries'). * - Never retrying a call when an exception occurs. * - Retrying only when the operation is considered idempotent(default). * These configurations are supplied for per api call basis. * */ /** * Header that identifies a specific request hash. The * hash needs to stay the same for multiple retries. */ private static $INVOCATION_ID_HEADER = 'gccl-invocation-id'; /** * Header that identifies the attempt count for a request. The * value will increment by 1 with every retry. */ private static $ATTEMPT_COUNT_HEADER = 'gccl-attempt-count'; /** * Return a retry decider function. * * @param string $resource resource name, eg: buckets. * @param string $method method name, eg: get * @param array $args * @return callable */ private function getRestRetryFunction($resource, $method, array $args) { if (isset($args['restRetryFunction'])) { return $args['restRetryFunction']; } $methodName = \sprintf('%s.%s', $resource, $method); $isOpIdempotent = \in_array($methodName, self::$idempotentOps); $preconditionNeeded = \array_key_exists($methodName, self::$condIdempotentOps); $preconditionSupplied = $this->isPreConditionSupplied($methodName, $args); $retryStrategy = isset($args['retryStrategy']) ? $args['retryStrategy'] : StorageClient::RETRY_IDEMPOTENT; return function (\Exception $exception) use($isOpIdempotent, $preconditionNeeded, $preconditionSupplied, $retryStrategy) { return $this->retryDeciderFunction($exception, $isOpIdempotent, $preconditionNeeded, $preconditionSupplied, $retryStrategy); }; } /** * This function returns true when the user given * precondtions ($preConditions) has values that are present * in the precondition map ($this->condIdempotentMap) for that method. * eg: condIdempotentMap has entry 'objects.copy' => ['ifGenerationMatch'], * if the user has given 'ifGenerationMatch' in the 'objects.copy' operation, * it will be available in the $preConditions * as an array ['ifGenerationMatch']. This makes the array_intersect * function return a non empty result and this function returns true. * * @param string $methodName method name, eg: buckets.get. * @param array $args arguments which include preconditions provided, * eg: ['ifGenerationMatch' => 0]. * @return bool */ private function isPreConditionSupplied($methodName, array $args) { if (isset(self::$condIdempotentOps[$methodName])) { // return true if required precondition are given. return !empty(\array_intersect(self::$condIdempotentOps[$methodName], \array_keys($args))); } return \false; } /** * Decide whether the op needs to be retried or not. * * @param \Exception $exception The exception object received * while sending the request. * @param int $currentAttempt Current retry attempt. * @param bool $isIdempotent * @param bool $preconditionNeeded * @param bool $preconditionSupplied * @param int $maxRetries * @return bool */ private function retryDeciderFunction(\Exception $exception, $isIdempotent, $preconditionNeeded, $preconditionSupplied, $retryStrategy) { if ($retryStrategy == StorageClient::RETRY_NEVER) { return \false; } $statusCode = $exception->getCode(); // Retry if the exception status code matches // with one of the retriable status code and // the operation is either idempotent or conditionally // idempotent with preconditions supplied. if (\in_array($statusCode, self::$httpRetryCodes)) { if ($retryStrategy == StorageClient::RETRY_ALWAYS) { return \true; } elseif ($isIdempotent) { return \true; } elseif ($preconditionNeeded) { return $preconditionSupplied; } } return \false; } /** * Utility func that returns the list of headers that need to be * attached to every request and its retries. */ private static function getRetryHeaders($invocationId, $attemptCount) { return [\sprintf('%s/%s', self::$INVOCATION_ID_HEADER, $invocationId), \sprintf('%s/%d', self::$ATTEMPT_COUNT_HEADER, $attemptCount)]; } }