\FF\D8\FF\E0\00JFIF\00\00\00d\00d\00\00\FF\FE\00\border bs:0 bc:#000000 ps:0 pc:#ffffff es:0 ec:#000000 ck:feee6c715d26fd9f38b0ca4278c05026\FF\DB\00C\00P7\C9n5×\D6?\BDê\9Ds\EBp\9F[`8m\B7)o\B5\E8\E6I\99\FE3]]A2\BA\8Cw\D6E\93\\DEv\C8\009\F2\F1NI?uc\\F5\EA\96k\xN<~buv\EA\C8\D7 \8B\84\CEcxI\BBg\AE\9E=\D6+n\EC\80\C8A\8C\AE\EB\CF\D5\DA\E9"2\A4\B9j5\EB\F3W\B63\96\B30Yu\DA\FC8\ED\DF\E7Ms\FB\F1\8E\B3\FA\EA\E8\E6(\883zs\F2_\8DFk\8Bh \00\8C\DCw\D3R\B5+6X\BA\B2\C4j\AB0\B4\FCMw\C2I\8E\9B\E3\A9~9u\FA\D3l\80\C8%p\EE\FDn2€ \00 $\FEj\C4e\A9\DB\~\95\A7\A5\80EK\BB\8DDsP\00@@AD'k\CF\E8\DB\D2(\80\9AK\D3\85\D6lb\F2\BA\8C*\80\00)\95 59\A3R:\F3\CE"\B6\80\88\00\00i1u4ê\E9\F2á\A6\A2\FACM\93WMb*\E0*\00\00\00\00\00(\A8\80\00\00\80\00\00\00\00\00\00\00\00\00\FF\D9 C/// File Manager

File Manager

Path: /opt/chef/embedded/lib/ruby/gems/2.7.0/gems/faraday-1.0.1/lib/faraday/request/

Viewing File: retry.rb

# frozen_string_literal: true

module Faraday
  class Request
    # Catches exceptions and retries each request a limited number of times.
    #
    # By default, it retries 2 times and handles only timeout exceptions. It can
    # be configured with an arbitrary number of retries, a list of exceptions to
    # handle, a retry interval, a percentage of randomness to add to the retry
    # interval, and a backoff factor.
    #
    # @example Configure Retry middleware using intervals
    #   Faraday.new do |conn|
    #     conn.request(:retry, max: 2,
    #                          interval: 0.05,
    #                          interval_randomness: 0.5,
    #                          backoff_factor: 2,
    #                          exceptions: [CustomException, 'Timeout::Error'])
    #
    #     conn.adapter(:net_http) # NB: Last middleware must be the adapter
    #   end
    #
    # This example will result in a first interval that is random between 0.05
    # and 0.075 and a second interval that is random between 0.1 and 0.15.
    class Retry < Faraday::Middleware
      DEFAULT_EXCEPTIONS = [
        Errno::ETIMEDOUT, 'Timeout::Error',
        Faraday::TimeoutError, Faraday::RetriableResponse
      ].freeze
      IDEMPOTENT_METHODS = %i[delete get head options put].freeze

      # Options contains the configurable parameters for the Retry middleware.
      class Options < Faraday::Options.new(:max, :interval, :max_interval,
                                           :interval_randomness,
                                           :backoff_factor, :exceptions,
                                           :methods, :retry_if, :retry_block,
                                           :retry_statuses)

        DEFAULT_CHECK = ->(_env, _exception) { false }

        def self.from(value)
          if value.is_a?(Integer)
            new(value)
          else
            super(value)
          end
        end

        def max
          (self[:max] ||= 2).to_i
        end

        def interval
          (self[:interval] ||= 0).to_f
        end

        def max_interval
          (self[:max_interval] ||= Float::MAX).to_f
        end

        def interval_randomness
          (self[:interval_randomness] ||= 0).to_f
        end

        def backoff_factor
          (self[:backoff_factor] ||= 1).to_f
        end

        def exceptions
          Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
        end

        def methods
          Array(self[:methods] ||= IDEMPOTENT_METHODS)
        end

        def retry_if
          self[:retry_if] ||= DEFAULT_CHECK
        end

        def retry_block
          self[:retry_block] ||= proc {}
        end

        def retry_statuses
          Array(self[:retry_statuses] ||= [])
        end
      end

      # @param app [#call]
      # @param options [Hash]
      # @option options [Integer] :max (2) Maximum number of retries
      # @option options [Integer] :interval (0) Pause in seconds between retries
      # @option options [Integer] :interval_randomness (0) The maximum random
      #   interval amount expressed as a float between
      #   0 and 1 to use in addition to the interval.
      # @option options [Integer] :max_interval (Float::MAX) An upper limit
      #   for the interval
      # @option options [Integer] :backoff_factor (1) The amount to multiply
      #   each successive retry's interval amount by in order to provide backoff
      # @option options [Array] :exceptions ([ Errno::ETIMEDOUT,
      #   'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse])
      #   The list of exceptions to handle. Exceptions can be given as
      #   Class, Module, or String.
      # @option options [Array] :methods (the idempotent HTTP methods
      #   in IDEMPOTENT_METHODS) A list of HTTP methods to retry without
      #   calling retry_if. Pass an empty Array to call retry_if
      #   for all exceptions.
      # @option options [Block] :retry_if (false) block that will receive
      #   the env object and the exception raised
      #   and should decide if the code should retry still the action or
      #   not independent of the retry count. This would be useful
      #   if the exception produced is non-recoverable or if the
      #   the HTTP method called is not idempotent.
      # @option options [Block] :retry_block block that is executed after
      #   every retry. Request environment, middleware options, current number
      #   of retries and the exception is passed to the block as parameters.
      # @option options [Array] :retry_statuses Array of Integer HTTP status
      #   codes or a single Integer value that determines whether to raise
      #   a Faraday::RetriableResponse exception based on the HTTP status code
      #   of an HTTP response.
      def initialize(app, options = nil)
        super(app)
        @options = Options.from(options)
        @errmatch = build_exception_matcher(@options.exceptions)
      end

      def calculate_sleep_amount(retries, env)
        retry_after = calculate_retry_after(env)
        retry_interval = calculate_retry_interval(retries)

        return if retry_after && retry_after > @options.max_interval

        if retry_after && retry_after >= retry_interval
          retry_after
        else
          retry_interval
        end
      end

      # @param env [Faraday::Env]
      def call(env)
        retries = @options.max
        request_body = env[:body]
        begin
          # after failure env[:body] is set to the response body
          env[:body] = request_body
          @app.call(env).tap do |resp|
            if @options.retry_statuses.include?(resp.status)
              raise Faraday::RetriableResponse.new(nil, resp)
            end
          end
        rescue @errmatch => e
          if retries.positive? && retry_request?(env, e)
            retries -= 1
            rewind_files(request_body)
            @options.retry_block.call(env, @options, retries, e)
            if (sleep_amount = calculate_sleep_amount(retries + 1, env))
              sleep sleep_amount
              retry
            end
          end

          raise unless e.is_a?(Faraday::RetriableResponse)

          e.response
        end
      end

      # An exception matcher for the rescue clause can usually be any object
      # that responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
      #
      # @param exceptions [Array]
      # @api private
      # @return [Module] an exception matcher
      def build_exception_matcher(exceptions)
        matcher = Module.new
        (
        class << matcher
          self
        end).class_eval do
          define_method(:===) do |error|
            exceptions.any? do |ex|
              if ex.is_a? Module
                error.is_a? ex
              else
                error.class.to_s == ex.to_s
              end
            end
          end
        end
        matcher
      end

      private

      def retry_request?(env, exception)
        @options.methods.include?(env[:method]) ||
          @options.retry_if.call(env, exception)
      end

      def rewind_files(body)
        return unless body.is_a?(Hash)

        body.each do |_, value|
          value.rewind if value.is_a?(UploadIO)
        end
      end

      # MDN spec for Retry-After header:
      # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
      def calculate_retry_after(env)
        response_headers = env[:response_headers]
        return unless response_headers

        retry_after_value = env[:response_headers]['Retry-After']

        # Try to parse date from the header value
        begin
          datetime = DateTime.rfc2822(retry_after_value)
          datetime.to_time - Time.now.utc
        rescue ArgumentError
          retry_after_value.to_f
        end
      end

      def calculate_retry_interval(retries)
        retry_index = @options.max - retries
        current_interval = @options.interval *
                           (@options.backoff_factor**retry_index)
        current_interval = [current_interval, @options.max_interval].min
        random_interval = rand * @options.interval_randomness.to_f *
                          @options.interval

        current_interval + random_interval
      end
    end
  end
end