retriable 3.1.1 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ba6b290804243a8b08f6f4542f1516ca38f52205
4
- data.tar.gz: 618793b8cdb2566ff83fa037252a17874d490936
2
+ SHA256:
3
+ metadata.gz: b43ddaf802607e93a68f1f2862c4a951b10f98ff3447f70da26ca72582f5c2ec
4
+ data.tar.gz: 6893753a86f08975dd6781ce504ef5e373c3ce06ccc56e1fabbb46a701893bfb
5
5
  SHA512:
6
- metadata.gz: bb9a7cc6d5661743f636d8a2aa82d5cc12f03e41e2ffe93721af71081199f696d7cf6d4dcc830da3dc0cf3bd87b0fd9fd43f1f807b173fa4389f00358132cd48
7
- data.tar.gz: 6434a4c6910b02caf3f1a8d822ceb543c511168052eb6bcd0e3c496d99931951143946456d212fb0f13193e88724ce7ff3d9cac9f242a8b5910fb8a7a2d225a8
6
+ metadata.gz: 67c9f7740db524df4f4b5301c9c66fbecfc70a98413d4e2dc830f19ecc8365d507373f481a678e53c4ca23063069319d2198bac4f89e6a9641b7c6e0487928f8
7
+ data.tar.gz: 76f62f0a03ac6e7c43c5fd6e6eeaaa3223edae83e55414bff6ec24464ab1991e6c405e569a7c17cfe0b3904bcec52867e69d9743440e14fb0a7d56ab7ff080fd
@@ -0,0 +1,2 @@
1
+ ruby:
2
+ config_file: .rubocop.yml
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --format documentation
@@ -7,16 +7,13 @@ Style/Documentation:
7
7
  Style/TrailingCommaInArguments:
8
8
  EnforcedStyleForMultiline: comma
9
9
 
10
- Style/TrailingCommaInLiteral:
11
- EnforcedStyleForMultiline: comma
12
-
13
- Style/InheritException:
10
+ Lint/InheritException:
14
11
  Enabled: false
15
12
 
16
- Style/IndentArray:
13
+ Layout/IndentArray:
17
14
  Enabled: false
18
15
 
19
- Style/IndentHash:
16
+ Layout/IndentHash:
20
17
  Enabled: false
21
18
 
22
19
  Style/NegatedIf:
@@ -29,7 +26,7 @@ Metrics/ModuleLength:
29
26
  Enabled: false
30
27
 
31
28
  Metrics/LineLength:
32
- Enabled: false
29
+ Max: 120
33
30
 
34
31
  Metrics/MethodLength:
35
32
  Enabled: false
@@ -1,24 +1,25 @@
1
1
  # Send builds to container-based infrastructure
2
2
  # http://docs.travis-ci.com/user/workers/container-based-infrastructure/
3
+ dist: trusty
3
4
  sudo: false
4
5
  language: ruby
5
6
  rvm:
6
7
  - 2.0.0
7
8
  - 2.1.10
8
- - 2.2.7
9
- - 2.3.4
10
- - 2.4.1
11
- - rbx
12
- - jruby-9.0.5.0
13
- - jruby-9.1.12.0
9
+ - 2.2.10
10
+ - 2.3.7
11
+ - 2.4.4
12
+ - 2.5.1
14
13
  - ruby-head
14
+ - jruby-9.2.0.0
15
15
  - jruby-head
16
+ - rbx-3.99
17
+ script:
18
+ - bundle exec rspec
16
19
 
17
20
  matrix:
18
21
  allow_failures:
19
- - rvm: rbx
20
- - rvm: ruby-head
21
- - rvm: jruby-head
22
+ - rvm: rbx-3.99
22
23
 
23
24
  before_install:
24
25
  - gem update --system
@@ -1,5 +1,11 @@
1
1
  ## HEAD
2
2
 
3
+ ## 3.1.2
4
+
5
+ * Replace `minitest` gem with `rspec`
6
+ * Fancier README
7
+ * Remove unnecessary short circuit in `randomize` method
8
+
3
9
  ## 3.1.1
4
10
 
5
11
  * Fix typo in contexts exception message.
data/Gemfile CHANGED
@@ -1,12 +1,10 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in retriable.gemspec
4
3
  gemspec
5
4
 
6
5
  group :test do
7
- # gem "ruby_gntp"
8
6
  gem "codeclimate-test-reporter", require: false
9
- gem "minitest-focus"
7
+ gem "rspec"
10
8
  gem "simplecov", require: false
11
9
  end
12
10
 
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Code Climate](https://codeclimate.com/github/kamui/retriable/badges/gpa.svg)](https://codeclimate.com/github/kamui/retriable)
5
5
  [![Test Coverage](https://codeclimate.com/github/kamui/retriable/badges/coverage.svg)](https://codeclimate.com/github/kamui/retriable)
6
6
 
7
- Retriable is a simple DSL to retry failed code blocks with randomized [exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff) time intervals. This is especially useful when interacting external api/services or file system calls.
7
+ Retriable is a simple DSL to retry failed code blocks with randomized [exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff) time intervals. This is especially useful when interacting external APIs, remote services, or file system calls.
8
8
 
9
9
  ## Requirements
10
10
 
@@ -16,7 +16,7 @@ If you need ruby 1.8.x to 1.9.2 support, use the [1.x branch](https://github.com
16
16
 
17
17
  ## Installation
18
18
 
19
- via command line:
19
+ Via command line:
20
20
 
21
21
  ```ruby
22
22
  gem install retriable
@@ -35,21 +35,7 @@ gem 'retriable', '~> 3.1'
35
35
  ```
36
36
 
37
37
  ## Usage
38
-
39
- Code in a `Retriable.retriable` block will be retried if an exception is raised. By default, Retriable will rescue any exception inherited from `StandardError`, make 3 tries (including the initial attempt) before raising the last exception, and also use randomized exponential backoff to calculate each succeeding try interval. The default interval table with 10 tries looks like this (in seconds):
40
-
41
- | retry# | retry interval | randomized interval |
42
- | -------- | -------------- | ------------------------------- |
43
- | 1 | 0.5 | [0.25, 0.75] |
44
- | 2 | 0.75 | [0.375, 1.125] |
45
- | 3 | 1.125 | [0.5625, 1.6875] |
46
- | 4 | 1.6875 | [0.84375, 2.53125] |
47
- | 5 | 2.53125 | [1.265625, 3.796875] |
48
- | 6 | 3.796875 | [1.8984375, 5.6953125] |
49
- | 7 | 5.6953125 | [2.84765625, 8.54296875] |
50
- | 8 | 8.54296875 | [4.271484375, 12.814453125] |
51
- | 9 | 12.814453125 | [6.4072265625, 19.2216796875] |
52
- | 10 | 19.2216796875 | stop |
38
+ Code in a `Retriable.retriable` block will be retried if an exception is raised.
53
39
 
54
40
  ```ruby
55
41
  require 'retriable'
@@ -64,35 +50,57 @@ class Api
64
50
  end
65
51
  ```
66
52
 
67
- ### Options
68
-
69
- Here are the available options:
53
+ ### Defaults
54
+ By default, `Retriable` will:
55
+ * rescue any exception inherited from `StandardError`
56
+ * make 3 tries (including the initial attempt) before raising the last exception
57
+ * use randomized exponential backoff to calculate each succeeding try interval.
70
58
 
71
- `tries` (default: 3) - Number of attempts to make at running your code block (includes intial attempt).
59
+ The default interval table with 10 tries looks like this (in seconds, rounded to the nearest millisecond):
72
60
 
73
- `base_interval` (default: 0.5) - The initial interval in seconds between tries.
61
+ | Retry # | Min | Average | Max |
62
+ | -------- | -------- | -------- | -------- |
63
+ | 1 | `0.25` | `0.5` | `0.75` |
64
+ | 2 | `0.375` | `0.75` | `1.125` |
65
+ | 3 | `0.563` | `1.125` | `1.688` |
66
+ | 4 | `0.844` | `1.688` | `2.531` |
67
+ | 5 | `1.266` | `2.531` | `3.797` |
68
+ | 6 | `1.898` | `3.797` | `5.695` |
69
+ | 7 | `2.848` | `5.695` | `8.543` |
70
+ | 8 | `4.271` | `8.543` | `12.814` |
71
+ | 9 | `6.407` | `12.814` | `19.222` |
72
+ | 10 | **stop** | **stop** | **stop** |
74
73
 
75
- `max_interval` (default: 60) - The maximum interval in seconds that any try can reach.
76
74
 
77
- `rand_factor` (default: 0.25) - The percent range above and below the next interval is randomized between. The calculation is calculated like this:
78
-
79
- ```
80
- randomized_interval = retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])
81
- ```
82
-
83
- `multiplier` (default: 1.5) - Each successive interval grows by this factor. A multipler of 1.5 means the next interval will be 1.5x the current interval.
75
+ ### Options
84
76
 
85
- `max_elapsed_time` (default: 900 (15 min)) - The maximum amount of total time that code is allowed to keep being retried.
77
+ Here are the available options, in some vague order of relevance to most common use patterns:
86
78
 
87
- `intervals` (default: nil) - Skip generated intervals and provide your own array of intervals in seconds. Setting this option will ignore `tries`, `base_interval`, `max_interval`, `rand_factor`, and `multiplier` values.
79
+ | Option | Default | Definition |
80
+ | ------ | ------- | ---------- |
81
+ | **`tries`** | `3` | Number of attempts to make at running your code block (includes initial attempt). |
82
+ | **`on`** | `[StandardError]` | Type of exceptions to retry. [Read more](#configuring-which-options-to-retry-with-on). |
83
+ | **`on_retry`** | `nil` | `Proc` to call after each try is rescued. [Read more](#callbacks). |
84
+ | **`base_interval`** | `0.5` | The initial interval in seconds between tries. |
85
+ | **`max_elapsed_time`** | `900` (15 min) | The maximum amount of total time in seconds that code is allowed to keep being retried. |
86
+ | **`max_interval`** | `60` | The maximum interval in seconds that any individual retry can reach. |
87
+ | **`multiplier`** | `1.5` | Each successive interval grows by this factor. A multipler of 1.5 means the next interval will be 1.5x the current interval. |
88
+ | **`timeout`** | `nil` | Number of seconds to allow the code block to run before raising a `Timeout::Error` inside each try. `nil` means the code block can run forever without raising error. (You may want to read up on [the dangers of using Ruby `Timeout`](https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/) before using this feature.) |
89
+ | **`rand_factor`** | `0.25` | The percentage to randomize the next retry interval time. The next interval calculation is `randomized_interval = retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])` |
90
+ | **`intervals`** | `nil` | Skip generated intervals and provide your own array of intervals in seconds. [Read more](#custom-interval-array). |
88
91
 
89
- `timeout` (default: nil) - Number of seconds to allow the code block to run before raising a `Timeout::Error` inside each try. Default is `nil` means the code block can run forever without raising error.
92
+ #### Configuring Which Options to Retry With :on
93
+ **`:on`** Can take the form:
90
94
 
91
- `on` (default: [StandardError]) - An `Array` of exceptions to rescue for each try, a `Hash` where the keys are `Exception` classes and the values can be a single `Regexp` pattern or a list of patterns, or a single `Exception` type. Subclasses of the listed exceptions will be retried and have their messages matched in the same way.
95
+ - An `Exception` class (retry every exception of this type, including subclasses)
96
+ - An `Array` of `Exception` classes (retry any exception of one of these types, including subclasses)
97
+ - A `Hash` where the keys are `Exception` classes and the values are one of:
98
+ - `nil` (retry every exception of the key's type, including subclasses)
99
+ - A single `Regexp` pattern (retries exceptions ONLY if their `message` matches the pattern)
100
+ - An array of patterns (retries exceptions ONLY if their `message` matches at least one of the patterns)
92
101
 
93
- `on_retry` - (default: nil) - Proc to call after each try is rescued.
94
102
 
95
- ### Config
103
+ ### Configuration
96
104
 
97
105
  You can change the global defaults with a `#configure` block:
98
106
 
@@ -103,12 +111,12 @@ Retriable.configure do |c|
103
111
  end
104
112
  ```
105
113
 
106
- ### Examples
114
+ ### Example Usage
107
115
 
108
- `Retriable.retriable` accepts custom arguments. This example will only retry on a `Timeout::Error`, retry 3 times and sleep for a full second before each try.
116
+ This example will only retry on a `Timeout::Error`, retry 3 times and sleep for a full second before each try.
109
117
 
110
118
  ```ruby
111
- Retriable.retriable on: Timeout::Error, tries: 3, base_interval: 1 do
119
+ Retriable.retriable(on: Timeout::Error, tries: 3, base_interval: 1) do
112
120
  # code here...
113
121
  end
114
122
  ```
@@ -116,27 +124,27 @@ end
116
124
  You can also specify multiple errors to retry on by passing an array of exceptions.
117
125
 
118
126
  ```ruby
119
- Retriable.retriable on: [Timeout::Error, Errno::ECONNRESET] do
127
+ Retriable.retriable(on: [Timeout::Error, Errno::ECONNRESET]) do
120
128
  # code here...
121
129
  end
122
130
  ```
123
131
 
124
- You can also specify a Hash of exceptions where the values are a list or single Regexp pattern.
132
+ You can also use a hash to specify that you only want to retry exceptions with certain messages (see [the documentation above](#configuring-which-options-to-retry-with-on)). This example will retry all `ActiveRecord::RecordNotUnique` exceptions, `ActiveRecord::RecordInvalid` exceptions where the message matches either `/Parent must exist/` or `/Username has already been taken/`, or `Mysql2::Error` exceptions where the message matches `/Duplicate entry/`.
125
133
 
126
134
  ```ruby
127
- Retriable.retriable on: {
135
+ Retriable.retriable(on: {
128
136
  ActiveRecord::RecordNotUnique => nil,
129
- ActiveRecord::RecordInvalid => [/Email has already been taken/, /Username has already been taken/],
137
+ ActiveRecord::RecordInvalid => [/Parent must exist/, /Username has already been taken/],
130
138
  Mysql2::Error => /Duplicate entry/
131
- } do
139
+ }) do
132
140
  # code here...
133
141
  end
134
142
  ```
135
143
 
136
- You can also specify a timeout if you want the code block to only try for X amount of seconds. This timeout is per try.
144
+ You can also specify a timeout if you want the code block to only try for X amount of seconds. This timeout is per try. (You may want to read up on [the dangers of using Ruby `Timeout`](https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/) before using this feature.)
137
145
 
138
146
  ```ruby
139
- Retriable.retriable timeout: 60 do
147
+ Retriable.retriable(timeout: 60) do
140
148
  # code here...
141
149
  end
142
150
  ```
@@ -144,7 +152,7 @@ end
144
152
  If you need millisecond units of time for the sleep or the timeout:
145
153
 
146
154
  ```ruby
147
- Retriable.retriable base_interval: (200/1000.0), timeout: (500/1000.0) do
155
+ Retriable.retriable(base_interval: (200 / 1000.0), timeout: (500 / 1000.0)) do
148
156
  # code here...
149
157
  end
150
158
  ```
@@ -154,39 +162,39 @@ end
154
162
  You can also bypass the built-in interval generation and provide your own array of intervals. Supplying your own intervals overrides the `tries`, `base_interval`, `max_interval`, `rand_factor`, and `multiplier` parameters.
155
163
 
156
164
  ```ruby
157
- Retriable.retriable intervals: [0.5, 1.0, 2.0, 2.5] do
165
+ Retriable.retriable(intervals: [0.5, 1.0, 2.0, 2.5]) do
158
166
  # code here...
159
167
  end
160
168
  ```
161
169
 
162
- This example makes 5 total attempts, if the first attempt fails, the 2nd attempt occurs 0.5 seconds later.
170
+ This example makes 5 total attempts. If the first attempt fails, the 2nd attempt occurs 0.5 seconds later.
163
171
 
164
172
  ### Turn off Exponential Backoff
165
173
 
166
- Exponential backoff is enabled by default, if you want to simply retry code every second, 5 times maximum, you can do this:
174
+ Exponential backoff is enabled by default. If you want to simply retry code every second, 5 times maximum, you can do this:
167
175
 
168
176
  ```ruby
169
- Retriable.retriable tries: 5, base_interval: 1.0, multiplier: 1.0, rand_factor: 0.0 do
177
+ Retriable.retriable(tries: 5, base_interval: 1.0, multiplier: 1.0, rand_factor: 0.0) do
170
178
  # code here...
171
179
  end
172
180
  ```
173
181
 
174
- This works by starting at a 1 second interval (`base_interval`), setting the `multipler` to 1.0 means each subsequent try will increase 1x, which is still `1.0` seconds, and then a `rand_factor` of 0.0 means that there's no randomization of that interval. By default, it would randomize 0.25 seconds, which would mean normally the intervals would randomize between 0.75 and 1.25 seconds, but in this case `rand_factor` is basically being disabled.
182
+ This works by starting at a 1 second `base_interval`. Setting the `multipler` to 1.0 means each subsequent try will increase 1x, which is still `1.0` seconds, and then a `rand_factor` of 0.0 means that there's no randomization of that interval. (By default, it would randomize 0.25 seconds, which would mean normally the intervals would randomize between 0.75 and 1.25 seconds, but in this case `rand_factor` is basically being disabled.)
175
183
 
176
184
  Another way to accomplish this would be to create an array with a fixed interval. In this example, `Array.new(5, 1)` creates an array with 5 elements, all with the value 1. The code block will retry up to 5 times, and wait 1 second between each attempt.
177
185
 
178
186
  ```ruby
179
187
  # Array.new(5, 1) # => [1, 1, 1, 1, 1]
180
188
 
181
- Retriable.retriable intervals: Array.new(5, 1) do
189
+ Retriable.retriable(intervals: Array.new(5, 1)) do
182
190
  # code here...
183
191
  end
184
192
  ```
185
193
 
186
- If you don't want exponential backoff, but you still want some randomization between intervals, this code will run every 1 seconds with a randomization factor of 0.2, which means each interval will be a random value between 0.8 and 1.2 (1 second +/- 0.2):
194
+ If you don't want exponential backoff but you still want some randomization between intervals, this code will run every 1 seconds with a randomization factor of 0.2, which means each interval will be a random value between 0.8 and 1.2 (1 second +/- 0.2):
187
195
 
188
196
  ```ruby
189
- Retriable.retriable base_interval: 1.0, multiplier: 1.0, rand_factor: 0.2 do
197
+ Retriable.retriable(base_interval: 1.0, multiplier: 1.0, rand_factor: 0.2) do
190
198
  # code here...
191
199
  end
192
200
  ```
@@ -200,14 +208,14 @@ do_this_on_each_retry = Proc.new do |exception, try, elapsed_time, next_interval
200
208
  log "#{exception.class}: '#{exception.message}' - #{try} tries in #{elapsed_time} seconds and #{next_interval} seconds until the next try."
201
209
  end
202
210
 
203
- Retriable.retriable on_retry: do_this_on_each_retry do
211
+ Retriable.retriable(on_retry: do_this_on_each_retry) do
204
212
  # code here...
205
213
  end
206
214
  ```
207
215
 
208
216
  ### Ensure/Else
209
217
 
210
- What if I want to execute a code block at the end, whether or not an exception was rescued ([ensure](http://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-ensure))? Or, what if I want to execute a code block if no exception is raised ([else](http://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-else))? Instead of providing more callbacks, I recommend you just wrap retriable in a begin/retry/else/ensure block:
218
+ What if I want to execute a code block at the end, whether or not an exception was rescued ([ensure](http://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-ensure))? Or what if I want to execute a code block if no exception is raised ([else](http://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-else))? Instead of providing more callbacks, I recommend you just wrap retriable in a begin/retry/else/ensure block:
211
219
 
212
220
  ```ruby
213
221
  begin
@@ -262,7 +270,7 @@ You can even temporarily override individual options for a configured context:
262
270
 
263
271
  ```ruby
264
272
  Retriable.with_context(:mysql, tries: 30) do
265
- # write_to_table
273
+ # write_to_table with :mysql context, except with 30 tries instead of 10
266
274
  end
267
275
  ```
268
276
 
@@ -295,6 +303,69 @@ retriable_with_context(:api) do
295
303
  end
296
304
  ```
297
305
 
306
+ ## Short Circuiting Retriable While Testing Your App
307
+
308
+ When you are running tests for your app it often takes a long time to retry blocks that fail. This is because Retriable will default to 3 tries with exponential backoff. Ideally your tests will run as quickly as possible.
309
+
310
+ You can disable retrying by setting `tries` to 1 in the test environment. If you want to test that the code is retrying an error, you want to [turn off exponential backoff](#turn-off-exponential-backoff).
311
+
312
+ Under Rails, you could change your initializer to have different options in test, as follows:
313
+
314
+ ```ruby
315
+ # config/initializers/retriable.rb
316
+ Retriable.configure do |c|
317
+ # ... default configuration
318
+
319
+ if Rails.env.test?
320
+ c.tries = 1
321
+ end
322
+ end
323
+ ```
324
+
325
+ Alternately, if you are using RSpec, you could override the Retriable confguration in your `spec_helper`.
326
+
327
+ ```ruby
328
+ # spec/spec_helper.rb
329
+ Retriable.configure do |c|
330
+ c.tries = 1
331
+ end
332
+ ```
333
+
334
+ If you have defined contexts for your configuration, you'll need to change values for each context, because those values take precedence over the default configured value.
335
+
336
+ For example assuming you have configured a `google_api` context:
337
+ ```ruby
338
+ # config/initializers/retriable.rb
339
+ Retriable.configure do |c|
340
+ c.contexts[:google_api] = {
341
+ tries: 5,
342
+ base_interval: 3,
343
+ on: [
344
+ Net::ReadTimeout,
345
+ Signet::AuthorizationError,
346
+ Errno::ECONNRESET,
347
+ OpenSSL::SSL::SSLError
348
+ ]
349
+ }
350
+ end
351
+ ```
352
+
353
+ Then in your test environment, you would need to set each context and the default value:
354
+
355
+ ```ruby
356
+ # spec/spec_helper.rb
357
+ Retriable.configure do |c|
358
+ c.multiplier = 1.0
359
+ c.rand_factor = 0.0
360
+ c.base_interval = 0
361
+
362
+ c.contexts.keys.each do |context|
363
+ c.contexts[context][:tries] = 1
364
+ c.contexts[context][:base_interval] = 0
365
+ end
366
+ end
367
+ ```
368
+
298
369
  ## Proxy Wrapper Object
299
370
 
300
371
  [@julik](https://github.com/julik) has created a gem called [retriable_proxy](https://github.com/julik/retriable_proxy) that extends `retriable` with the ability to wrap objects and specify which methods you want to be retriable, like so:
@@ -307,3 +378,9 @@ RetriableProxy.for_object(api_endpoint, on: Net::TimeoutError)
307
378
  ## Credits
308
379
 
309
380
  The randomized exponential backoff implementation was inspired by the one used in Google's [google-http-java-client](https://code.google.com/p/google-http-java-client/wiki/ExponentialBackoff) project.
381
+
382
+ ## Development
383
+ ### Running Specs
384
+ ```bash
385
+ bundle exec rspec
386
+ ```
@@ -37,6 +37,7 @@ module Retriable
37
37
  on_retry = local_config.on_retry
38
38
  sleep_disabled = local_config.sleep_disabled
39
39
 
40
+ exception_list = on.is_a?(Hash) ? on.keys : on
40
41
  start_time = Time.now
41
42
  elapsed_time = -> { Time.now - start_time }
42
43
 
@@ -52,10 +53,9 @@ module Retriable
52
53
  ).intervals
53
54
  end
54
55
 
55
- exception_list = on.is_a?(Hash) ? on.keys : on
56
-
57
56
  tries.times do |index|
58
57
  try = index + 1
58
+
59
59
  begin
60
60
  return Timeout.timeout(timeout) { return yield(try) } if timeout
61
61
  return yield(try)