retriable 1.4.1 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -4
- data/CHANGELOG.md +7 -0
- data/Guardfile +14 -0
- data/README.md +78 -29
- data/Rakefile +3 -3
- data/lib/retriable.rb +61 -9
- data/lib/retriable/config.rb +27 -0
- data/lib/retriable/core_ext/kernel.rb +3 -2
- data/lib/retriable/version.rb +1 -1
- data/retriable.gemspec +7 -2
- data/spec/retriable_spec.rb +111 -0
- data/spec/spec_helper.rb +5 -0
- data/wercker.yml +27 -0
- metadata +79 -20
- data/lib/retriable/retry.rb +0 -44
- data/test/retriable_test.rb +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75d7bf8633f7cf7fe492aa7d1b001d68a2389b34
|
4
|
+
data.tar.gz: 92a103a316a6c12a01e5374f4656e7be57cc3c76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6bb179a0ae1696e706d42b3a1769e67396238011363a5c152cd48a376fca099bf03db84413ca3e982cbadb13a14693a271d5eb2aa91dc4587301e2570490ada4
|
7
|
+
data.tar.gz: 9f4de38db13683ead4e8b78abf5be5ce200b942e453cbc94c6a934bacd3b5e31e4060be9803b03590c856ab41062c82cfe4a94924489eb7582027236e6df3d28
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## 2.0.0.beta1
|
2
|
+
* Require ruby 2.0+.
|
3
|
+
* Default to random exponential backoff, removes the `interval` option. Exponential backoff is configurable via arguments.
|
4
|
+
* Allow configurable defaults via `#configure`.
|
5
|
+
* Change `Retriable.retriable` to `Retriable.retry`
|
6
|
+
* Support `max_elapsed_time` termination.
|
7
|
+
|
1
8
|
## 1.4.1
|
2
9
|
* Fixes non kernel mode bug. Remove DSL class, move `#retriable` into Retriable module. Thanks @mkrogemann.
|
3
10
|
|
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :minitest do
|
5
|
+
# with Minitest::Unit
|
6
|
+
watch(%r{^test/(.*)\/?test_(.*)\.rb})
|
7
|
+
watch(%r{^lib/retriable/(.*/)?([^/]+)\.rb}) { |m| "test/#{m[1]}/test_#{m[2]}.rb" }
|
8
|
+
watch(%r{^test/test_helper\.rb}) { 'test' }
|
9
|
+
|
10
|
+
# with Minitest::Spec
|
11
|
+
watch(%r{^spec/(.*)_spec\.rb})
|
12
|
+
watch(%r{^lib/retriable/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
|
13
|
+
watch(%r{^spec/spec_helper\.rb}) { 'spec' }
|
14
|
+
end
|
data/README.md
CHANGED
@@ -2,9 +2,15 @@
|
|
2
2
|
|
3
3
|
[![Build Status](https://secure.travis-ci.org/kamui/retriable.png)](http://travis-ci.org/kamui/retriable)
|
4
4
|
|
5
|
-
Retriable is an simple DSL to retry
|
5
|
+
Retriable is an simple DSL to retry failed code blocks with randomized [exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff). This is especially useful when interacting external api/services or file system calls.
|
6
6
|
|
7
|
-
##
|
7
|
+
## Requirements
|
8
|
+
|
9
|
+
Ruby 2.0+
|
10
|
+
|
11
|
+
If you need 1.9.x support, use the [1.x branch](https://github.com/kamui/retriable/tree/1.x).
|
12
|
+
|
13
|
+
## Installation
|
8
14
|
|
9
15
|
via command line:
|
10
16
|
|
@@ -24,9 +30,22 @@ In your Gemfile:
|
|
24
30
|
gem 'retriable'
|
25
31
|
```
|
26
32
|
|
27
|
-
##Usage
|
33
|
+
## Usage
|
28
34
|
|
29
|
-
Code in a
|
35
|
+
Code in a `Retriable.retry` block will be retried if an exception is raised. By default, Retriable will rescue any exception inherited from `StandardError`, make 3 retry attempts before raising the last exception, and also use randomized exponential backoff to calculate each succeeding attempt interval. The default interval table with 10 attempts looks like this (in seconds):
|
36
|
+
|
37
|
+
| request# | retry interval | randomized interval |
|
38
|
+
| -------- | -------------- | ------------------- |
|
39
|
+
| 1 | 0.5 | [0.25, 0.75] |
|
40
|
+
| 2 | 0.75 | [0.375, 1.125] |
|
41
|
+
| 3 | 1.125 | [0.562, 1.687] |
|
42
|
+
| 4 | 1.687 | [0.8435, 2.53] |
|
43
|
+
| 5 | 2.53 | [1.265, 3.795] |
|
44
|
+
| 6 | 3.795 | [1.897, 5.692] |
|
45
|
+
| 7 | 5.692 | [2.846, 8.538] |
|
46
|
+
| 8 | 8.538 | [4.269, 12.807] |
|
47
|
+
| 9 | 12.807 | [6.403, 19.210] |
|
48
|
+
| 10 | 19.210 | stop |
|
30
49
|
|
31
50
|
```ruby
|
32
51
|
require 'retriable'
|
@@ -34,31 +53,53 @@ require 'retriable'
|
|
34
53
|
class Api
|
35
54
|
# Use it in methods that interact with unreliable services
|
36
55
|
def get
|
37
|
-
Retriable.
|
56
|
+
Retriable.retry do
|
38
57
|
# code here...
|
39
58
|
end
|
40
59
|
end
|
41
60
|
end
|
42
61
|
```
|
43
62
|
|
44
|
-
###Options
|
63
|
+
### Options
|
45
64
|
|
46
65
|
Here are the available options:
|
47
66
|
|
48
|
-
`
|
67
|
+
`max_tries` (default: 3) - Number of attempts to make at running your code block.
|
68
|
+
|
69
|
+
`base_interval` (default: 0.5) - The initial interval in seconds between attempts.
|
70
|
+
|
71
|
+
`max_interval` (default: 60) - The maximum interval in seconds that any attempt can climb to.
|
49
72
|
|
50
|
-
`
|
73
|
+
`rand_factor` (default: 0.25) - The percent range above and below the next interval is randomized between. The calculation is calculated like this: `randomized_interval =
|
74
|
+
retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])`
|
75
|
+
|
76
|
+
`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.
|
77
|
+
|
78
|
+
`max_elapsed_time` (default: 900 (15 min)) - The maximum amount of total time that code is allowed to keep being retried
|
51
79
|
|
52
80
|
`timeout` (default: 0) - Number of seconds to allow the code block to run before raising a Timeout::Error
|
53
81
|
|
54
|
-
`on` (default: [StandardError
|
82
|
+
`on` (default: [StandardError]) - An array of exceptions to rescue for each attempt, also accepts a single Exception type
|
55
83
|
|
56
84
|
`on_retry` - (default: nil) - Proc to call after each attempt is rescued
|
57
85
|
|
58
|
-
|
86
|
+
### Config
|
87
|
+
|
88
|
+
You can change the global defaults with a `#configure` block:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
Retriable.configure do |c|
|
92
|
+
c.max_tries = 5
|
93
|
+
c.max_elapsed_time = 3600 # 1 hour
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
### Examples
|
98
|
+
|
99
|
+
`Retriable.retry` accepts custom arguments. This example will only retry on a `Timeout::Error`, retry 3 times and sleep for a full second before each attempt.
|
59
100
|
|
60
101
|
```ruby
|
61
|
-
Retriable.
|
102
|
+
Retriable.retry on: Timeout::Error, max_tries: 3, base_interval: 1 do
|
62
103
|
# code here...
|
63
104
|
end
|
64
105
|
```
|
@@ -66,7 +107,7 @@ end
|
|
66
107
|
You can also specify multiple errors to retry on by passing an array of exceptions.
|
67
108
|
|
68
109
|
```ruby
|
69
|
-
Retriable.
|
110
|
+
Retriable.retry on: [Timeout::Error, Errno::ECONNRESET] do
|
70
111
|
# code here...
|
71
112
|
end
|
72
113
|
```
|
@@ -74,7 +115,7 @@ end
|
|
74
115
|
You can also specify a timeout if you want the code block to only make an attempt for X amount of seconds. This timeout is per attempt.
|
75
116
|
|
76
117
|
```ruby
|
77
|
-
Retriable.
|
118
|
+
Retriable.retry timeout: 60 do
|
78
119
|
# code here...
|
79
120
|
end
|
80
121
|
```
|
@@ -82,42 +123,50 @@ end
|
|
82
123
|
If you need millisecond units of time for the sleep or the timeout:
|
83
124
|
|
84
125
|
```ruby
|
85
|
-
Retriable.
|
126
|
+
Retriable.retry base_interval: (200/1000.0), timeout: (500/1000.0) do
|
86
127
|
# code here...
|
87
128
|
end
|
88
129
|
```
|
89
130
|
|
90
|
-
###Exponential Backoff
|
131
|
+
### Turn off Exponential Backoff
|
91
132
|
|
92
|
-
|
133
|
+
Exponential backoff is enabled by default, if you want to simply execute code every second, you can do this:
|
93
134
|
|
94
135
|
```ruby
|
95
|
-
|
96
|
-
Retriable.retryable :times => 4, :interval => lambda {|attempts| 4 ** attempts} do
|
136
|
+
Retriable.retryable base_interval: 1.0, multiplier: 1.0, rand_factor: 0.0 do
|
97
137
|
# code here...
|
98
138
|
end
|
99
139
|
```
|
100
|
-
###Callbacks
|
101
140
|
|
102
|
-
|
141
|
+
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):
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
Retriable.retryable base_interval: 1.0, multiplier: 1.0, rand_factor: 0.2 do
|
145
|
+
# code here...
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
### Callbacks
|
150
|
+
|
151
|
+
Retriable.retry also provides a callback called `:on_retry` that will run after an exception is rescued. This callback provides the `exception` that was raised in the current attempt, the `try_number`, the `elapsed_time` for all attempts so far, and the time in seconds of the `next_interval`. As these are specified in a `Proc`, unnecessary variables can be left out of the parameter list.
|
103
152
|
|
104
153
|
```ruby
|
105
|
-
do_this_on_each_retry = Proc.new do |exception,
|
106
|
-
log "#{exception.class}: '#{exception.message}' - #{
|
154
|
+
do_this_on_each_retry = Proc.new do |exception, try_number, elapsed_time, next_interval|
|
155
|
+
log "#{exception.class}: '#{exception.message}' - #{try_number} attempts in #{elapsed_time} seconds and #{next_interval} seconds until the next attempt."}
|
107
156
|
end
|
108
157
|
|
109
|
-
Retriable.
|
158
|
+
Retriable.retry on_retry: do_this_on_each_retry do
|
110
159
|
# code here...
|
111
160
|
end
|
112
161
|
```
|
113
162
|
|
114
|
-
###Ensure/Else
|
163
|
+
### Ensure/Else
|
115
164
|
|
116
165
|
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:
|
117
166
|
|
118
167
|
```ruby
|
119
168
|
begin
|
120
|
-
Retriable.
|
169
|
+
Retriable.retry do
|
121
170
|
# some code
|
122
171
|
end
|
123
172
|
rescue => e
|
@@ -129,9 +178,9 @@ ensure
|
|
129
178
|
end
|
130
179
|
```
|
131
180
|
|
132
|
-
##Kernel Extension
|
181
|
+
## Kernel Extension
|
133
182
|
|
134
|
-
If you want to call `Retriable.
|
183
|
+
If you want to call `Retriable.retry` without the `Retriable` module prefix and you don't mind extending `Kernel`,
|
135
184
|
there is a kernel extension available for this.
|
136
185
|
|
137
186
|
In your ruby script:
|
@@ -146,7 +195,7 @@ or in your Gemfile:
|
|
146
195
|
gem 'retriable', require: 'retriable/core_ext/kernel'
|
147
196
|
```
|
148
197
|
|
149
|
-
and then you can call
|
198
|
+
and then you can call `#retriable` in any context like this:
|
150
199
|
|
151
200
|
```ruby
|
152
201
|
retriable do
|
@@ -154,6 +203,6 @@ retriable do
|
|
154
203
|
end
|
155
204
|
```
|
156
205
|
|
157
|
-
##Credits
|
206
|
+
## Credits
|
158
207
|
|
159
208
|
Retriable was originally forked from the retryable-rb gem by [Robert Sosinski](https://github.com/robertsosinski), which in turn originally inspired by code written by [Michael Celona](http://github.com/mcelona) and later assisted by [David Malin](http://github.com/dmalin). The [attempt](https://rubygems.org/gems/attempt) gem by Daniel J. Berger was also an inspiration.
|
data/Rakefile
CHANGED
@@ -4,13 +4,13 @@ require 'bundler'
|
|
4
4
|
Bundler::GemHelper.install_tasks
|
5
5
|
|
6
6
|
require 'rake/testtask'
|
7
|
-
task :
|
7
|
+
task default: :test
|
8
8
|
|
9
9
|
desc "Run tests"
|
10
10
|
task :test do
|
11
11
|
Rake::TestTask.new do |t|
|
12
|
-
t.libs << 'lib' << '
|
13
|
-
t.pattern = '
|
12
|
+
t.libs << 'lib' << 'spec'
|
13
|
+
t.pattern = 'spec/**/*_spec.rb'
|
14
14
|
t.verbose = true
|
15
15
|
end
|
16
16
|
end
|
data/lib/retriable.rb
CHANGED
@@ -1,17 +1,69 @@
|
|
1
|
-
require '
|
1
|
+
require 'timeout'
|
2
|
+
require 'retriable/config'
|
3
|
+
require 'retriable/version'
|
2
4
|
|
3
5
|
module Retriable
|
4
6
|
extend self
|
5
7
|
|
6
|
-
|
8
|
+
attr_accessor :config
|
9
|
+
|
10
|
+
def self.configure
|
11
|
+
self.config ||= Config.new
|
12
|
+
yield(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def retry(
|
16
|
+
max_tries: config.max_tries,
|
17
|
+
base_interval: config.base_interval,
|
18
|
+
max_interval: config.max_interval,
|
19
|
+
rand_factor: config.rand_factor,
|
20
|
+
multiplier: config.multiplier,
|
21
|
+
max_elapsed_time: config.max_elapsed_time,
|
22
|
+
timeout: config.timeout,
|
23
|
+
on: config.on,
|
24
|
+
on_retry: config.on_retry,
|
25
|
+
&block)
|
26
|
+
|
7
27
|
raise LocalJumpError unless block_given?
|
8
28
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
29
|
+
attempt = 0
|
30
|
+
start_time = Time.now
|
31
|
+
interval = base_interval
|
32
|
+
|
33
|
+
begin
|
34
|
+
attempt += 1
|
35
|
+
if timeout
|
36
|
+
Timeout::timeout(timeout) { return block.call(attempt) }
|
37
|
+
else
|
38
|
+
return block.call(attempt)
|
39
|
+
end
|
40
|
+
rescue *[*on] => exception
|
41
|
+
raise if attempt >= max_tries
|
42
|
+
|
43
|
+
return if (Time.now - start_time) > max_elapsed_time
|
44
|
+
|
45
|
+
interval = randomized_interval(rand_factor, interval)
|
46
|
+
|
47
|
+
on_retry.call(exception, attempt, Time.now - start_time, interval) if on_retry
|
48
|
+
|
49
|
+
sleep interval if interval > 0 && config.sleep_disabled != true
|
50
|
+
|
51
|
+
interval = if interval >= (max_interval / multiplier)
|
52
|
+
max_interval
|
53
|
+
else
|
54
|
+
interval * multiplier
|
55
|
+
end
|
56
|
+
|
57
|
+
retry
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def randomized_interval(rand_factor, interval)
|
63
|
+
return interval if rand_factor == 0
|
64
|
+
delta = rand_factor * interval * 1.0
|
65
|
+
min_interval = interval - delta
|
66
|
+
max_interval = interval + delta
|
67
|
+
rand(min_interval..max_interval)
|
16
68
|
end
|
17
69
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Retriable
|
2
|
+
class Config
|
3
|
+
attr_accessor :sleep_disabled
|
4
|
+
attr_accessor :max_tries
|
5
|
+
attr_accessor :base_interval
|
6
|
+
attr_accessor :max_interval
|
7
|
+
attr_accessor :rand_factor
|
8
|
+
attr_accessor :multiplier
|
9
|
+
attr_accessor :max_elapsed_time
|
10
|
+
attr_accessor :timeout
|
11
|
+
attr_accessor :on
|
12
|
+
attr_accessor :on_retry
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@sleep_disabled = false
|
16
|
+
@max_tries = 3
|
17
|
+
@base_interval = 0.5
|
18
|
+
@max_interval = 60
|
19
|
+
@rand_factor = 0.5
|
20
|
+
@multiplier = 1.5
|
21
|
+
@max_elapsed_time = 900 # 15 minn
|
22
|
+
@timeout = nil
|
23
|
+
@on = [StandardError]
|
24
|
+
@on_retry = nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/retriable/version.rb
CHANGED
data/retriable.gemspec
CHANGED
@@ -10,8 +10,9 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.authors = ["Jack Chu"]
|
11
11
|
s.email = ["jack@jackchu.com"]
|
12
12
|
s.homepage = %q{http://github.com/kamui/retriable}
|
13
|
-
s.summary = %q{Retriable is an simple DSL to retry
|
14
|
-
s.description = %q{Retriable is an simple DSL to retry
|
13
|
+
s.summary = %q{Retriable is an simple DSL to retry failed code blocks with randomized exponential backoff}
|
14
|
+
s.description = %q{Retriable is an simple DSL to retry failed code blocks with randomized exponential backoff. This is especially useful when interacting external api/services or file system calls.
|
15
|
+
}
|
15
16
|
s.license = "MIT"
|
16
17
|
|
17
18
|
s.rubyforge_project = "retriable"
|
@@ -23,4 +24,8 @@ Gem::Specification.new do |s|
|
|
23
24
|
|
24
25
|
s.add_development_dependency 'rake'
|
25
26
|
s.add_development_dependency 'minitest', '>= 5.0'
|
27
|
+
s.add_development_dependency 'minitest-focus'
|
28
|
+
s.add_development_dependency 'pry'
|
29
|
+
s.add_development_dependency 'guard'
|
30
|
+
s.add_development_dependency 'guard-minitest'
|
26
31
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
class TestError < Exception; end
|
4
|
+
|
5
|
+
describe Retriable do
|
6
|
+
subject do
|
7
|
+
Retriable
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
Retriable.configure do |c|
|
12
|
+
c.sleep_disabled = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'raises a LocalJumpError if retry is not given a block' do
|
17
|
+
lambda do
|
18
|
+
subject.retry on: EOFError
|
19
|
+
end.must_raise LocalJumpError
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'retry block of code raising EOFError with no arguments' do
|
23
|
+
before do
|
24
|
+
@attempts = 0
|
25
|
+
|
26
|
+
subject.retry do
|
27
|
+
@attempts += 1
|
28
|
+
raise EOFError.new if @attempts < 3
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'uses exponential backoff' do
|
33
|
+
@attempts.must_equal 3
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'retry on custom exception and re-raises the exception' do
|
38
|
+
lambda do
|
39
|
+
subject.retry on: TestError do
|
40
|
+
raise TestError.new
|
41
|
+
end
|
42
|
+
end.must_raise TestError
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'retry with 10 max tries' do
|
46
|
+
attempts = 0
|
47
|
+
|
48
|
+
subject.retry(
|
49
|
+
max_tries: 10
|
50
|
+
) do
|
51
|
+
attempts += 1
|
52
|
+
raise EOFError.new if attempts < 10
|
53
|
+
end
|
54
|
+
|
55
|
+
attempts.must_equal 10
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'retries with an on_retry handler, 6 max retries, and a 0.0 rand_factor' do
|
59
|
+
before do
|
60
|
+
max_tries = 6
|
61
|
+
@attempts = 0
|
62
|
+
@time_table = {}
|
63
|
+
|
64
|
+
handler = Proc.new do |exception, attempt, elapsed_time, next_interval|
|
65
|
+
exception.class.must_equal ArgumentError
|
66
|
+
@time_table[attempt] = next_interval
|
67
|
+
end
|
68
|
+
|
69
|
+
Retriable.retry(
|
70
|
+
on: [EOFError, ArgumentError],
|
71
|
+
on_retry: handler,
|
72
|
+
rand_factor: 0.0,
|
73
|
+
max_tries: max_tries
|
74
|
+
) do
|
75
|
+
@attempts += 1
|
76
|
+
raise ArgumentError.new if @attempts < max_tries
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'makes 6 attempts' do
|
81
|
+
@attempts.must_equal 6
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'applies a non-randomized exponential backoff to each attempt' do
|
85
|
+
@time_table.must_equal({
|
86
|
+
1 => 0.5,
|
87
|
+
2 => 0.75,
|
88
|
+
3 => 1.125,
|
89
|
+
4 => 1.6875,
|
90
|
+
5 => 2.53125
|
91
|
+
})
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'can call #retriable in the global' do
|
96
|
+
lambda do
|
97
|
+
retriable do
|
98
|
+
puts 'should raise NoMethodError'
|
99
|
+
end
|
100
|
+
end.must_raise NoMethodError
|
101
|
+
|
102
|
+
require_relative '../lib/retriable/core_ext/kernel'
|
103
|
+
|
104
|
+
i = 0
|
105
|
+
retriable do
|
106
|
+
i += 1
|
107
|
+
raise EOFError.new if i < 3
|
108
|
+
end
|
109
|
+
i.must_equal 3
|
110
|
+
end
|
111
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/wercker.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
box: wercker/rvm
|
2
|
+
# Build definition
|
3
|
+
build:
|
4
|
+
# The steps that will be executed on build
|
5
|
+
# See the Ruby section on the wercker devcenter:
|
6
|
+
# http://devcenter.wercker.com/articles/languages/ruby.html
|
7
|
+
steps:
|
8
|
+
# Uncomment this to force RVM to use a specific Ruby version
|
9
|
+
# - rvm-use:
|
10
|
+
# version: 2.1.0
|
11
|
+
|
12
|
+
# A step that executes `bundle install` command
|
13
|
+
- bundle-install
|
14
|
+
|
15
|
+
# A custom script step, name value is used in the UI
|
16
|
+
# and the code value contains the command that get executed
|
17
|
+
- script:
|
18
|
+
name: echo ruby information
|
19
|
+
code: |
|
20
|
+
echo "ruby version $(ruby --version) running"
|
21
|
+
echo "from location $(which ruby)"
|
22
|
+
echo -p "gem list: $(gem list)"
|
23
|
+
|
24
|
+
# Add more steps here:
|
25
|
+
- script:
|
26
|
+
name: rake
|
27
|
+
code: bundle exec rake
|
metadata
CHANGED
@@ -1,65 +1,123 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: retriable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jack Chu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '5.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '5.0'
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-focus
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: |
|
98
|
+
Retriable is an simple DSL to retry failed code blocks with randomized exponential backoff. This is especially useful when interacting external api/services or file system calls.
|
44
99
|
email:
|
45
100
|
- jack@jackchu.com
|
46
101
|
executables: []
|
47
102
|
extensions: []
|
48
103
|
extra_rdoc_files: []
|
49
104
|
files:
|
50
|
-
- .gitignore
|
51
|
-
- .travis.yml
|
105
|
+
- ".gitignore"
|
106
|
+
- ".travis.yml"
|
52
107
|
- CHANGELOG.md
|
53
108
|
- Gemfile
|
109
|
+
- Guardfile
|
54
110
|
- LICENSE
|
55
111
|
- README.md
|
56
112
|
- Rakefile
|
57
113
|
- lib/retriable.rb
|
114
|
+
- lib/retriable/config.rb
|
58
115
|
- lib/retriable/core_ext/kernel.rb
|
59
|
-
- lib/retriable/retry.rb
|
60
116
|
- lib/retriable/version.rb
|
61
117
|
- retriable.gemspec
|
62
|
-
-
|
118
|
+
- spec/retriable_spec.rb
|
119
|
+
- spec/spec_helper.rb
|
120
|
+
- wercker.yml
|
63
121
|
homepage: http://github.com/kamui/retriable
|
64
122
|
licenses:
|
65
123
|
- MIT
|
@@ -70,20 +128,21 @@ require_paths:
|
|
70
128
|
- lib
|
71
129
|
required_ruby_version: !ruby/object:Gem::Requirement
|
72
130
|
requirements:
|
73
|
-
- -
|
131
|
+
- - ">="
|
74
132
|
- !ruby/object:Gem::Version
|
75
133
|
version: '0'
|
76
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
135
|
requirements:
|
78
|
-
- -
|
136
|
+
- - ">"
|
79
137
|
- !ruby/object:Gem::Version
|
80
|
-
version:
|
138
|
+
version: 1.3.1
|
81
139
|
requirements: []
|
82
140
|
rubyforge_project: retriable
|
83
|
-
rubygems_version: 2.
|
141
|
+
rubygems_version: 2.2.2
|
84
142
|
signing_key:
|
85
143
|
specification_version: 4
|
86
|
-
summary: Retriable is an simple DSL to retry
|
87
|
-
|
144
|
+
summary: Retriable is an simple DSL to retry failed code blocks with randomized exponential
|
145
|
+
backoff
|
88
146
|
test_files:
|
89
|
-
-
|
147
|
+
- spec/retriable_spec.rb
|
148
|
+
- spec/spec_helper.rb
|
data/lib/retriable/retry.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'timeout'
|
2
|
-
|
3
|
-
module Retriable
|
4
|
-
class Retry
|
5
|
-
attr_accessor :tries
|
6
|
-
attr_accessor :interval
|
7
|
-
attr_accessor :timeout
|
8
|
-
attr_accessor :on
|
9
|
-
attr_accessor :on_retry
|
10
|
-
|
11
|
-
def initialize
|
12
|
-
@tries = 3
|
13
|
-
@interval = 0
|
14
|
-
@timeout = nil
|
15
|
-
@on = [StandardError, Timeout::Error]
|
16
|
-
@on_retry = nil
|
17
|
-
|
18
|
-
yield self if block_given?
|
19
|
-
end
|
20
|
-
|
21
|
-
def perform
|
22
|
-
count = 0
|
23
|
-
begin
|
24
|
-
if @timeout
|
25
|
-
Timeout::timeout(@timeout) { yield }
|
26
|
-
else
|
27
|
-
yield
|
28
|
-
end
|
29
|
-
rescue *[*on] => exception
|
30
|
-
@tries -= 1
|
31
|
-
if @tries > 0
|
32
|
-
count += 1
|
33
|
-
@on_retry.call(exception, count) if @on_retry
|
34
|
-
sleep_for = @interval.respond_to?(:call) ? @interval.call(count) : @interval
|
35
|
-
sleep sleep_for if sleep_for > 0
|
36
|
-
|
37
|
-
retry
|
38
|
-
else
|
39
|
-
raise
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
data/test/retriable_test.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
require 'retriable'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
|
4
|
-
class RetriableTest < Minitest::Test
|
5
|
-
def test_raise_no_block
|
6
|
-
assert_raises LocalJumpError do
|
7
|
-
Retriable.retriable :on => StandardError
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_without_arguments
|
12
|
-
i = 0
|
13
|
-
|
14
|
-
Retriable.retriable do
|
15
|
-
i += 1
|
16
|
-
raise StandardError.new
|
17
|
-
end
|
18
|
-
rescue StandardError
|
19
|
-
assert_equal 3, i
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_with_one_exception_and_two_tries
|
23
|
-
i = 0
|
24
|
-
|
25
|
-
Retriable.retriable :on => EOFError, :tries => 2 do
|
26
|
-
i += 1
|
27
|
-
raise EOFError.new
|
28
|
-
end
|
29
|
-
|
30
|
-
rescue EOFError
|
31
|
-
assert_equal i, 2
|
32
|
-
end
|
33
|
-
|
34
|
-
def test_with_arguments
|
35
|
-
i = 0
|
36
|
-
|
37
|
-
on_retry = Proc.new do |exception, tries|
|
38
|
-
assert_equal exception.class, ArgumentError
|
39
|
-
assert_equal i, tries
|
40
|
-
end
|
41
|
-
|
42
|
-
Retriable.retriable :on => [EOFError, ArgumentError], :on_retry => on_retry, :tries => 5, :sleep => 0.2 do |h|
|
43
|
-
i += 1
|
44
|
-
raise ArgumentError.new
|
45
|
-
end
|
46
|
-
|
47
|
-
rescue ArgumentError
|
48
|
-
assert_equal 5, i
|
49
|
-
end
|
50
|
-
|
51
|
-
def test_with_interval_proc
|
52
|
-
was_called = false
|
53
|
-
|
54
|
-
sleeper = Proc.new do |attempts|
|
55
|
-
was_called = true
|
56
|
-
attempts
|
57
|
-
end
|
58
|
-
|
59
|
-
Retriable.retriable :on => EOFError, :interval => sleeper do |h|
|
60
|
-
raise EOFError.new
|
61
|
-
end
|
62
|
-
rescue
|
63
|
-
assert_equal was_called, true
|
64
|
-
end
|
65
|
-
|
66
|
-
def test_kernel_ext
|
67
|
-
assert_raises NoMethodError do
|
68
|
-
retriable do
|
69
|
-
puts 'should raise NoMethodError'
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
require 'retriable/core_ext/kernel'
|
74
|
-
i = 0
|
75
|
-
|
76
|
-
retriable do
|
77
|
-
i += 1
|
78
|
-
raise StandardError.new
|
79
|
-
end
|
80
|
-
|
81
|
-
rescue StandardError
|
82
|
-
assert_equal 3, i
|
83
|
-
end
|
84
|
-
end
|