retriable 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +10 -0
- data/CHANGELOG.md +17 -0
- data/LICENSE +1 -1
- data/README.md +101 -60
- data/Rakefile +0 -1
- data/lib/retriable.rb +3 -32
- data/lib/retriable/core_ext/kernel.rb +9 -0
- data/lib/retriable/no_kernel.rb +4 -0
- data/lib/retriable/retriable.rb +64 -0
- data/lib/retriable/version.rb +1 -1
- data/retriable.gemspec +4 -1
- data/test/retriable_test.rb +20 -46
- metadata +20 -4
- data/CHANGELOG +0 -1
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
## 1.3.0
|
2
|
+
|
3
|
+
* Rewrote a lot of the code with inspiration from [attempt](https://rubygems.org/gems/attempt).
|
4
|
+
* Add timeout option to the code block.
|
5
|
+
* Include in Kernel by default, but allow require 'retriable/no_kernel' to load a non kernel version.
|
6
|
+
* Renamed `:times` option to `:tries`.
|
7
|
+
* Renamed `:sleep` option to `:interval`.
|
8
|
+
* Renamed `:then` option to `:on_retry`.
|
9
|
+
* Removed other callbacks, you can wrap retriable in a begin/rescue/else/ensure block if you need that functionality. It avoids the need to define multiple Procs and makes the code more readable.
|
10
|
+
* Rewrote most of the README
|
11
|
+
|
12
|
+
## 1.2.0
|
13
|
+
|
14
|
+
* Forked the retryable-rb repo.
|
15
|
+
* Extend the Kernel module with the retriable method so you can use it anywhere without having to include it in every class.
|
16
|
+
* Update gemspec, Gemfile, and Raketask.
|
17
|
+
* Remove echoe dependency.
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,84 +1,125 @@
|
|
1
|
-
|
1
|
+
Retriable
|
2
2
|
============
|
3
3
|
|
4
|
-
|
4
|
+
[](http://travis-ci.org/kamui/retriable)
|
5
|
+
|
6
|
+
Retriable is an simple DSL to retry code if an exception is raised. This is especially useful when interacting external api/services or file system calls.
|
5
7
|
|
6
8
|
Installation
|
7
9
|
------------
|
10
|
+
Via command line:
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
require 'retriable'
|
13
|
-
|
14
|
-
# In your Gemfile
|
15
|
-
gem 'retriable'
|
16
|
-
|
17
|
-
Using Retriable
|
18
|
-
---------------
|
19
|
-
|
20
|
-
Code wrapped in a retriable block will be retried if a failure occurs. As such, code attempted once, will be retried again for another attempt if it fails to run.
|
21
|
-
|
22
|
-
require 'retriable'
|
23
|
-
|
24
|
-
class Api
|
25
|
-
# Use it in methods that interact with unreliable services
|
26
|
-
def get
|
27
|
-
retriable do
|
28
|
-
# code here...
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
By default, Retriable will rescue any exception inherited from `Exception`, retry once (for a total of two attempts) and sleep for a random amount time (between 0 to 100 milliseconds, in 10 millisecond increments). You can choose additional options by passing them via an options `Hash`.
|
34
|
-
|
35
|
-
retriable :on => Timeout::Error, :times => 3, :sleep => 1 do
|
36
|
-
# code here...
|
37
|
-
end
|
38
|
-
|
39
|
-
This example will only retry on a `Timeout::Error`, retry 3 times (for a total of 4 attempts) and sleep for a full second before each retry. You can also specify multiple errors to retry on by passing an array.
|
40
|
-
|
41
|
-
retriable :on => [Timeout::Error, Errno::ECONNRESET] do
|
42
|
-
# code here...
|
43
|
-
end
|
44
|
-
|
45
|
-
You can also have Ruby retry immediately after a failure by passing `false` as the sleep option.
|
12
|
+
```ruby
|
13
|
+
gem install retriable
|
14
|
+
```
|
46
15
|
|
47
|
-
|
48
|
-
# code here...
|
49
|
-
end
|
16
|
+
In your ruby script:
|
50
17
|
|
51
|
-
|
18
|
+
```ruby
|
19
|
+
require 'retriable'
|
20
|
+
```
|
52
21
|
|
53
|
-
|
22
|
+
In your Gemfile:
|
54
23
|
|
55
|
-
|
24
|
+
```ruby
|
25
|
+
gem 'retriable'
|
26
|
+
```
|
56
27
|
|
57
|
-
|
28
|
+
By default, requiring 'retriable' will include the #retriable method into th Kernel so that you can use it anywhere. If you don't want this behaviour, you can load a non-kernel included version:
|
58
29
|
|
59
|
-
|
30
|
+
```ruby
|
31
|
+
gem 'retriable', require => 'retriable/no_kernel'
|
32
|
+
```
|
60
33
|
|
61
|
-
|
34
|
+
Or in your ruby script:
|
62
35
|
|
63
|
-
|
64
|
-
|
65
|
-
|
36
|
+
```ruby
|
37
|
+
require 'retriable/no_kernel'
|
38
|
+
```
|
66
39
|
|
67
|
-
|
68
|
-
|
69
|
-
end
|
40
|
+
Usage
|
41
|
+
---------------
|
70
42
|
|
71
|
-
|
72
|
-
log "total time for #{attempts} attempts: #{Time.now - handler[:start]}"
|
73
|
-
end
|
43
|
+
Code in a retriable block will be retried if an exception is raised. By default, Retriable will rescue any exception inherited from `Exception` and make 3 retry attempts before raising the last exception.
|
74
44
|
|
75
|
-
|
76
|
-
|
45
|
+
```ruby
|
46
|
+
require 'retriable'
|
77
47
|
|
48
|
+
class Api
|
49
|
+
# Use it in methods that interact with unreliable services
|
50
|
+
def get
|
51
|
+
retriable do
|
78
52
|
# code here...
|
79
53
|
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
Here are the available options:
|
59
|
+
|
60
|
+
`tries` (default: 3) - Number of attempts to make at running your code block
|
61
|
+
`interval` (default: 0) - Number of seconds to sleep between attempts
|
62
|
+
`timeout` (default: 0) - Number of seconds to allow the code block to run before raising a Timeout::Error
|
63
|
+
`on` (default: Exception) - Exception or array of exceptions to rescue for each attempt
|
64
|
+
`on_retry` - (default: nil) - Proc to call after each attempt is rescued
|
65
|
+
|
66
|
+
You can pass options via an options `Hash`. This example will only retry on a `Timeout::Error`, retry 3 times and sleep for a full second before each attempt.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
retriable :on => Timeout::Error, :tries => 3, :interval => 1 do
|
70
|
+
# code here...
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
You can also specify multiple errors to retry on by passing an array of exceptions.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
retriable :on => [Timeout::Error, Errno::ECONNRESET] do
|
78
|
+
# code here...
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
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.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
retriable :timeout => 1 do
|
86
|
+
# code here...
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
If you need millisecond units of time for the sleep or the timeout:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
retriable :interval => (200/1000.0), :timeout => (500/1000.0) do
|
94
|
+
# code here...
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
Retriable also provides a callback called `:on_retry` that will run after an exception is rescued. This callback provides the number of `tries`, and the `exception` that was raised in the current attempt. As these are specified in a `Proc`, unnecessary variables can be left out of the parameter list.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
on_retry = Proc.new do |exception, tries|
|
102
|
+
log "#{exception.class}: '#{exception.message}' - #{tries} attempts."}
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
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:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
begin
|
110
|
+
retriable do
|
111
|
+
# some code
|
112
|
+
end
|
113
|
+
rescue Exception => e
|
114
|
+
# run this if retriable ends up re-rasing the exception
|
115
|
+
else
|
116
|
+
# run this if retriable doesn't raise any exceptions
|
117
|
+
ensure
|
118
|
+
# run this no matter what, exception or no exception
|
119
|
+
end
|
120
|
+
```
|
80
121
|
|
81
122
|
Credits
|
82
123
|
-------
|
83
124
|
|
84
|
-
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).
|
125
|
+
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
data/lib/retriable.rb
CHANGED
@@ -1,33 +1,4 @@
|
|
1
|
-
|
1
|
+
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
opts = {:on => Exception, :times => 1}.merge(options)
|
6
|
-
handler = {}
|
7
|
-
|
8
|
-
retry_exception = opts[:on].is_a?(Array) ? opts[:on] : [opts[:on]]
|
9
|
-
times = retries = opts[:times]
|
10
|
-
attempts = 0
|
11
|
-
|
12
|
-
begin
|
13
|
-
attempts += 1
|
14
|
-
|
15
|
-
return yield(handler)
|
16
|
-
rescue *retry_exception => exception
|
17
|
-
opts[:then].call(exception, handler, attempts, retries, times) if opts[:then]
|
18
|
-
|
19
|
-
if attempts <= times
|
20
|
-
sleep(opts[:sleep] || (rand(11) / 100.0)) unless opts[:sleep] == false
|
21
|
-
retries -= 1
|
22
|
-
retry
|
23
|
-
else
|
24
|
-
opts[:finally].call(exception, handler, attempts, retries, times) if opts[:finally]
|
25
|
-
raise exception
|
26
|
-
end
|
27
|
-
ensure
|
28
|
-
opts[:always].call(handler, attempts, retries, times) if opts[:always]
|
29
|
-
end
|
30
|
-
|
31
|
-
yield(handler)
|
32
|
-
end
|
33
|
-
end
|
3
|
+
require 'retriable/no_kernel'
|
4
|
+
require 'retriable/core_ext/kernel'
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module Retriable
|
6
|
+
class Retry
|
7
|
+
attr_accessor :tries
|
8
|
+
attr_accessor :interval
|
9
|
+
attr_accessor :timeout
|
10
|
+
attr_accessor :on
|
11
|
+
attr_accessor :on_retry
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@tries = 3
|
15
|
+
@interval = 0
|
16
|
+
@timeout = nil
|
17
|
+
@on = Exception
|
18
|
+
@on_retry = nil
|
19
|
+
|
20
|
+
yield self if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform
|
24
|
+
count = 0
|
25
|
+
begin
|
26
|
+
if @timeout
|
27
|
+
Timeout::timeout(@timeout) { yield }
|
28
|
+
else
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
rescue *[*on] => exception
|
32
|
+
@tries -= 1
|
33
|
+
if @tries > 0
|
34
|
+
count += 1
|
35
|
+
sleep @interval if @interval > 0
|
36
|
+
@on_retry.call(exception, count) if @on_retry
|
37
|
+
retry
|
38
|
+
else
|
39
|
+
raise
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def retriable(options = {}, &block)
|
46
|
+
opts = {
|
47
|
+
:tries => 3,
|
48
|
+
:on => Exception,
|
49
|
+
:interval => 1
|
50
|
+
}
|
51
|
+
|
52
|
+
opts.merge!(options)
|
53
|
+
|
54
|
+
raise 'No block given' unless block_given?
|
55
|
+
|
56
|
+
Retry.new do |r|
|
57
|
+
r.tries = opts[:tries]
|
58
|
+
r.on = opts[:on]
|
59
|
+
r.interval = opts[:interval]
|
60
|
+
r.timeout = opts[:timeout] if opts[:timeout]
|
61
|
+
r.on_retry = opts[:on_retry] if opts[:on_retry]
|
62
|
+
end.perform(&block)
|
63
|
+
end
|
64
|
+
end
|
data/lib/retriable/version.rb
CHANGED
data/retriable.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# encoding: utf-8
|
2
|
+
|
2
3
|
$:.push File.expand_path("../lib", __FILE__)
|
3
4
|
require "retriable/version"
|
4
5
|
|
@@ -18,4 +19,6 @@ Gem::Specification.new do |s|
|
|
18
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
21
|
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_development_dependency 'minitest'
|
21
24
|
end
|
data/test/retriable_test.rb
CHANGED
@@ -1,72 +1,46 @@
|
|
1
|
-
|
1
|
+
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'test/unit'
|
4
3
|
require 'retriable'
|
4
|
+
require 'minitest/autorun'
|
5
5
|
|
6
|
-
class RetriableTest <
|
6
|
+
class RetriableTest < MiniTest::Unit::TestCase
|
7
7
|
def test_without_arguments
|
8
8
|
i = 0
|
9
9
|
|
10
10
|
retriable do
|
11
11
|
i += 1
|
12
|
-
|
13
12
|
raise Exception.new
|
14
13
|
end
|
15
14
|
rescue Exception
|
16
|
-
assert_equal
|
15
|
+
assert_equal 3, i
|
17
16
|
end
|
18
17
|
|
19
|
-
def
|
18
|
+
def test_with_one_exception_and_two_tries
|
20
19
|
i = 0
|
21
20
|
|
22
|
-
retriable :on => EOFError, :
|
21
|
+
retriable :on => EOFError, :tries => 2 do
|
23
22
|
i += 1
|
24
|
-
|
25
23
|
raise EOFError.new
|
26
24
|
end
|
27
25
|
|
28
26
|
rescue EOFError
|
29
|
-
assert_equal i,
|
27
|
+
assert_equal i, 2
|
30
28
|
end
|
31
29
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
then_cb = Proc.new do |e, h, a, r, t|
|
36
|
-
assert_equal e.class, ArgumentError
|
37
|
-
assert h[:value]
|
38
|
-
|
39
|
-
assert_equal a, i
|
40
|
-
assert_equal r, 6 - a
|
41
|
-
assert_equal t, 5
|
42
|
-
end
|
43
|
-
|
44
|
-
finally_cb = Proc.new do |e, h, a, r, t|
|
45
|
-
assert_equal e.class, ArgumentError
|
46
|
-
assert h[:value]
|
47
|
-
|
48
|
-
assert_equal a, 6
|
49
|
-
assert_equal r, 0
|
50
|
-
assert_equal t, 5
|
51
|
-
end
|
52
|
-
|
53
|
-
always_cb = Proc.new do |h, a, r, t|
|
54
|
-
assert h[:value]
|
55
|
-
|
56
|
-
assert_equal a, 6
|
57
|
-
assert_equal r, 0
|
58
|
-
assert_equal t, 5
|
59
|
-
end
|
60
|
-
|
61
|
-
retriable :on => [EOFError, ArgumentError], :then => then_cb, :finally => finally_cb, :always => always_cb, :times => 5, :sleep => 0.2 do |h|
|
62
|
-
i += 1
|
63
|
-
|
64
|
-
h[:value] = true
|
30
|
+
def test_with_arguments
|
31
|
+
i = 0
|
65
32
|
|
66
|
-
|
67
|
-
|
33
|
+
on_retry = Proc.new do |exception, tries|
|
34
|
+
assert_equal exception.class, ArgumentError
|
35
|
+
assert_equal i, tries
|
36
|
+
end
|
68
37
|
|
69
|
-
|
70
|
-
|
38
|
+
retriable :on => [EOFError, ArgumentError], :on_retry => on_retry, :tries => 5, :sleep => 0.2 do |h|
|
39
|
+
i += 1
|
40
|
+
raise ArgumentError.new
|
71
41
|
end
|
42
|
+
|
43
|
+
rescue ArgumentError
|
44
|
+
assert_equal 5, i
|
45
|
+
end
|
72
46
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: retriable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,19 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
13
|
-
dependencies:
|
12
|
+
date: 2012-02-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: &2163262320 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2163262320
|
14
25
|
description: Easy to use DSL to retry code if an exception is raised.
|
15
26
|
email:
|
16
27
|
- jack@jackchu.com
|
@@ -19,12 +30,16 @@ extensions: []
|
|
19
30
|
extra_rdoc_files: []
|
20
31
|
files:
|
21
32
|
- .gitignore
|
22
|
-
-
|
33
|
+
- .travis.yml
|
34
|
+
- CHANGELOG.md
|
23
35
|
- Gemfile
|
24
36
|
- LICENSE
|
25
37
|
- README.md
|
26
38
|
- Rakefile
|
27
39
|
- lib/retriable.rb
|
40
|
+
- lib/retriable/core_ext/kernel.rb
|
41
|
+
- lib/retriable/no_kernel.rb
|
42
|
+
- lib/retriable/retriable.rb
|
28
43
|
- lib/retriable/version.rb
|
29
44
|
- retriable.gemspec
|
30
45
|
- test/retriable_test.rb
|
@@ -54,3 +69,4 @@ specification_version: 3
|
|
54
69
|
summary: Easy to use DSL to retry code if an exception is raised.
|
55
70
|
test_files:
|
56
71
|
- test/retriable_test.rb
|
72
|
+
has_rdoc:
|
data/CHANGELOG
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
v1.2.0. Fork the retryable-rb repo. Extend the Kernel module with the retriable method so you can use it anywhere without having to include it in every class. Update gemspec, Gemfile, and Raketask. Remove echoe dependency.
|