]> git.donarmstrong.com Git - dsa-puppet.git/blob - 3rdparty/modules/aviator/lib/puppet/feature/faraday/request/retry.rb
08bc83766118e21bde820efcda64f2e64c049be5
[dsa-puppet.git] / 3rdparty / modules / aviator / lib / puppet / feature / faraday / request / retry.rb
1 module Faraday
2   # Catches exceptions and retries each request a limited number of times.
3   #
4   # By default, it retries 2 times and handles only timeout exceptions. It can
5   # be configured with an arbitrary number of retries, a list of exceptions to
6   # handle, a retry interval, a percentage of randomness to add to the retry
7   # interval, and a backoff factor.
8   #
9   # Examples
10   #
11   #   Faraday.new do |conn|
12   #     conn.request :retry, max: 2, interval: 0.05,
13   #                          interval_randomness: 0.5, backoff_factor: 2
14   #                          exceptions: [CustomException, 'Timeout::Error']
15   #     conn.adapter ...
16   #   end
17   #
18   # This example will result in a first interval that is random between 0.05 and 0.075 and a second
19   # interval that is random between 0.1 and 0.15
20   #
21   class Request::Retry < Faraday::Middleware
22
23     IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
24
25     class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor, :exceptions, :retry_if)
26       DEFAULT_CHECK = lambda { |env,exception| false }
27
28       def self.from(value)
29         if Fixnum === value
30           new(value)
31         else
32           super(value)
33         end
34       end
35
36       def max
37         (self[:max] ||= 2).to_i
38       end
39
40       def interval
41         (self[:interval] ||= 0).to_f
42       end
43
44       def interval_randomness
45         (self[:interval_randomness] ||= 0).to_i
46       end
47
48       def backoff_factor
49         (self[:backoff_factor] ||= 1).to_f
50       end
51
52       def exceptions
53         Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
54                                      Error::TimeoutError])
55       end
56
57       def retry_if
58         self[:retry_if] ||= DEFAULT_CHECK
59       end
60
61     end
62
63     # Public: Initialize middleware
64     #
65     # Options:
66     # max                 - Maximum number of retries (default: 2)
67     # interval            - Pause in seconds between retries (default: 0)
68     # interval_randomness - The maximum random interval amount expressed
69     #                       as a float between 0 and 1 to use in addition to the
70     #                       interval. (default: 0)
71     # backoff_factor      - The amount to multiple each successive retry's
72     #                       interval amount by in order to provide backoff
73     #                       (default: 1)
74     # exceptions          - The list of exceptions to handle. Exceptions can be
75     #                       given as Class, Module, or String. (default:
76     #                       [Errno::ETIMEDOUT, Timeout::Error,
77     #                       Error::TimeoutError])
78     # retry_if            - block that will receive the env object and the exception raised
79     #                       and should decide if the code should retry still the action or
80     #                       not independent of the retry count. This would be useful
81     #                       if the exception produced is non-recoverable or if the
82     #                       the HTTP method called is not idempotent.
83     #                       (defaults to return false)
84     def initialize(app, options = nil)
85       super(app)
86       @options = Options.from(options)
87       @errmatch = build_exception_matcher(@options.exceptions)
88     end
89
90     def sleep_amount(retries)
91       retry_index = @options.max - retries
92       current_interval = @options.interval * (@options.backoff_factor ** retry_index)
93       random_interval  = rand * @options.interval_randomness.to_f * @options.interval
94       current_interval + random_interval
95     end
96
97     def call(env)
98       retries = @options.max
99       request_body = env[:body]
100       begin
101         env[:body] = request_body # after failure env[:body] is set to the response body
102         @app.call(env)
103       rescue @errmatch => exception
104         if retries > 0 && retry_request?(env, exception)
105           retries -= 1
106           sleep sleep_amount(retries + 1)
107           retry
108         end
109         raise
110       end
111     end
112
113     # Private: construct an exception matcher object.
114     #
115     # An exception matcher for the rescue clause can usually be any object that
116     # responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
117     def build_exception_matcher(exceptions)
118       matcher = Module.new
119       (class << matcher; self; end).class_eval do
120         define_method(:===) do |error|
121           exceptions.any? do |ex|
122             if ex.is_a? Module
123               error.is_a? ex
124             else
125               error.class.to_s == ex.to_s
126             end
127           end
128         end
129       end
130       matcher
131     end
132
133     private
134
135     def retry_request?(env, exception)
136       IDEMPOTENT_METHODS.include?(env[:method]) || @options.retry_if.call(env, exception)
137     end
138
139   end
140 end