rhod 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/README.md +75 -53
- data/lib/rhod/backoffs.rb +33 -9
- data/lib/rhod/command.rb +1 -1
- data/lib/rhod/version.rb +1 -1
- data/test/test_backoffs.rb +34 -0
- data/test/test_command.rb +52 -30
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48eaf0e214a15e8bbedf592f83cdac4f5ad24af8
|
4
|
+
data.tar.gz: c58dedc35eb28bd9247516b64dee33d8083e69ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67aca7d1a71afb19bc469c07ad193054c7df1ab1aa0e31dae5ed522bd9b04eb91cec3fea0f6d259c8f3c3ef0673e46ee8f82468ae50ecbb64029c269ee9a1f3f
|
7
|
+
data.tar.gz: 8cb15b2867eb6b00c1574841508e31317b8791b0f16e545b4a7df6da7ed5759c2a56b4a24ae0b2d088e795d3bde087c02960bd2e29f68b54369098aa7bc1547a
|
data/README.md
CHANGED
@@ -27,7 +27,9 @@ Rhod requires Ruby 1.9.2 or greater.
|
|
27
27
|
|
28
28
|
Add this line to your application's Gemfile:
|
29
29
|
|
30
|
-
|
30
|
+
```ruby
|
31
|
+
gem 'rhod'
|
32
|
+
```
|
31
33
|
|
32
34
|
And then execute:
|
33
35
|
|
@@ -41,18 +43,22 @@ Or install it yourself as:
|
|
41
43
|
|
42
44
|
Rhod has a very simple API. Design your application as you would normally, then enclose network accessing portions of your code with:
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
46
|
+
```ruby
|
47
|
+
Rhod.execute do
|
48
|
+
...
|
49
|
+
end
|
50
|
+
```
|
47
51
|
|
48
52
|
This implements the "Fail Fast" scenario by default.
|
49
53
|
|
50
54
|
Example, open a remote reasource, fail immediately if it fails:
|
51
55
|
|
52
|
-
|
53
|
-
|
56
|
+
```ruby
|
57
|
+
require 'open-uri'
|
58
|
+
require 'rhod'
|
54
59
|
|
55
|
-
|
60
|
+
Rhod.execute { open("http://google.com").read }
|
61
|
+
```
|
56
62
|
|
57
63
|
### Retries with and without backoffs
|
58
64
|
|
@@ -62,44 +68,54 @@ Code within a `Rhod::Command` block with reties in use must be _idempotent_, i.e
|
|
62
68
|
|
63
69
|
Rhod supports retying up to N times. By default it uses a logarithmic backoff:
|
64
70
|
|
65
|
-
|
66
|
-
|
71
|
+
```ruby
|
72
|
+
Rhod::Backoffs.default.take(5)
|
73
|
+
# [0.7570232465074598, 2.403267722339301, 3.444932048942182, 4.208673319629471, 4.811984719351674]
|
74
|
+
```
|
67
75
|
|
68
76
|
Rhod also comes with exponential and constant (always the same value) backoffs. You can also supply any Enumerator that produces a series of numbers. See `lib/rhod/backoffs.rb` for examples.
|
69
77
|
|
70
78
|
Example, open a remote reasource, fail once it has failed 10 times, with the default (logarithmic) backoff:
|
71
79
|
|
72
|
-
|
73
|
-
|
80
|
+
```ruby
|
81
|
+
require 'open-uri'
|
82
|
+
require 'rhod'
|
74
83
|
|
75
|
-
|
84
|
+
Rhod::Command.execute(:retries => 10) { open("http://google.com").read }
|
85
|
+
```
|
76
86
|
|
77
87
|
Example, open a remote reasource, fail once it has failed 10 times, waiting 0.2 seconds between attempts:
|
78
88
|
|
79
|
-
|
80
|
-
|
89
|
+
```ruby
|
90
|
+
require 'open-uri'
|
91
|
+
require 'rhod'
|
81
92
|
|
82
|
-
Rhod.execute(:retries => 10, :backoffs =>
|
93
|
+
Rhod.execute(:retries => 10, :backoffs => 0.2) do
|
83
94
|
open("http://google.com").read
|
84
95
|
end
|
96
|
+
```
|
85
97
|
|
86
98
|
Example, open a remote reasource, fail once it has failed 10 times, with an exponetially growing wait time between attempts:
|
87
99
|
|
88
|
-
|
89
|
-
|
100
|
+
```ruby
|
101
|
+
require 'open-uri'
|
102
|
+
require 'rhod'
|
90
103
|
|
91
|
-
|
92
|
-
|
93
|
-
|
104
|
+
Rhod.execute(:retries => 10, :backoffs => :^) do
|
105
|
+
open("http://google.com").read
|
106
|
+
end
|
107
|
+
```
|
94
108
|
|
95
|
-
Example, open a remote reasource, fail once it has failed 10 times, with waiting between attempts:
|
109
|
+
Example, open a remote reasource, fail once it has failed 10 times, with no waiting between attempts:
|
96
110
|
|
97
|
-
|
98
|
-
|
111
|
+
```ruby
|
112
|
+
require 'open-uri'
|
113
|
+
require 'rhod'
|
99
114
|
|
100
|
-
|
101
|
-
|
102
|
-
|
115
|
+
Rhod.execute(:retries => 10, :backoffs => 0) do
|
116
|
+
open("http://google.com").read
|
117
|
+
end
|
118
|
+
```
|
103
119
|
|
104
120
|
### Fail Silent
|
105
121
|
|
@@ -107,52 +123,58 @@ In the event of a failure, Rhod falls back to a `fallback`. The most basic case
|
|
107
123
|
|
108
124
|
Example, open a remote reasource, if it fails return them empty string.
|
109
125
|
|
110
|
-
|
111
|
-
|
126
|
+
```ruby
|
127
|
+
require 'open-uri'
|
128
|
+
require 'rhod'
|
112
129
|
|
113
|
-
|
114
|
-
|
115
|
-
|
130
|
+
Rhod.execute(:fallback => -> {""}) do
|
131
|
+
open("http://google.com").read
|
132
|
+
end
|
133
|
+
```
|
116
134
|
|
117
135
|
### Fail w/ Fallback
|
118
136
|
|
119
137
|
If there is another network call that can be used to fetch the reasource, it's possible to use another `Rhod::Command` once a failure has occurred.
|
120
138
|
|
121
|
-
|
122
|
-
|
139
|
+
```ruby
|
140
|
+
require 'open-uri'
|
141
|
+
require 'rhod'
|
123
142
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
143
|
+
search_engine_fallback = Rhod::Command.new(
|
144
|
+
:fallback => -> {""} # couldn't get anything
|
145
|
+
) do
|
146
|
+
open("https://yahoo.com").read
|
147
|
+
end
|
129
148
|
|
130
|
-
|
131
|
-
|
132
|
-
|
149
|
+
Rhod.execute(:fallback => -> { search_engine_fallback.execute }) do
|
150
|
+
open("http://google.com").read
|
151
|
+
end
|
152
|
+
```
|
133
153
|
|
134
154
|
### Primary / Secondary ("Hot Spare") switch over
|
135
155
|
|
136
156
|
Sometimes the fallback is just a part of normal operation. Just code in the state of which back end to access.
|
137
157
|
|
138
|
-
|
139
|
-
|
158
|
+
```ruby
|
159
|
+
require 'open-uri'
|
160
|
+
require 'rhod'
|
140
161
|
|
141
|
-
|
142
|
-
|
162
|
+
class SearchEngineHTML
|
163
|
+
attr_accessor :secondary
|
143
164
|
|
144
|
-
|
145
|
-
|
165
|
+
def fetch
|
166
|
+
url = !@secondary ? "http://google.com" : "https://yahoo.com"
|
146
167
|
|
147
|
-
|
148
|
-
|
149
|
-
end
|
150
|
-
end
|
168
|
+
Rhod.execute(url, :fallback => Proc.new { @secondary = !@secondary; fetch }) do |url|
|
169
|
+
open(url).read
|
151
170
|
end
|
171
|
+
end
|
172
|
+
end
|
152
173
|
|
153
|
-
|
174
|
+
search_engine_html = SearchEngineHTML.new
|
154
175
|
|
155
|
-
|
176
|
+
search_engine_html.fetch
|
177
|
+
```
|
156
178
|
|
157
179
|
## Contributing
|
158
180
|
|
data/lib/rhod/backoffs.rb
CHANGED
@@ -1,21 +1,45 @@
|
|
1
1
|
module Rhod::Backoffs
|
2
2
|
|
3
3
|
extend self
|
4
|
-
|
5
|
-
def
|
4
|
+
|
5
|
+
def backoff_sugar_to_enumerator(backoff)
|
6
|
+
if backoff.is_a?(Enumerator)
|
7
|
+
backoff
|
8
|
+
elsif backoff.is_a?(Numeric)
|
9
|
+
constant_backoff(backoff)
|
10
|
+
elsif backoff.is_a?(String)
|
11
|
+
n = (backoff[1..-1].to_i)
|
12
|
+
case backoff[0]
|
13
|
+
when "^"
|
14
|
+
expoential_backoffs(n)
|
15
|
+
when "l"
|
16
|
+
logarithmic_backoffs(n)
|
17
|
+
end
|
18
|
+
elsif backoff.is_a?(Symbol)
|
19
|
+
case backoff
|
20
|
+
when :^
|
21
|
+
expoential_backoffs
|
22
|
+
when :l
|
23
|
+
logarithmic_backoffs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a generator of a expoentially increasing series starting at n
|
29
|
+
def expoential_backoffs(n=1)
|
6
30
|
Enumerator.new do |yielder|
|
7
|
-
x =
|
31
|
+
x = (n - 1)
|
8
32
|
loop do
|
9
33
|
x += 1
|
10
|
-
yielder <<
|
34
|
+
yielder << 2.0**x
|
11
35
|
end
|
12
36
|
end
|
13
37
|
end
|
14
38
|
|
15
|
-
# Returns a generator of a logarithmicly increasing series starting at
|
16
|
-
def logarithmic_backoffs
|
39
|
+
# Returns a generator of a logarithmicly increasing series starting at n
|
40
|
+
def logarithmic_backoffs(n=0.3)
|
17
41
|
Enumerator.new do |yielder|
|
18
|
-
x =
|
42
|
+
x = n
|
19
43
|
loop do
|
20
44
|
x += 1
|
21
45
|
yielder << Math.log2(x**2)
|
@@ -24,10 +48,10 @@ module Rhod::Backoffs
|
|
24
48
|
end
|
25
49
|
|
26
50
|
# Always the same backoff
|
27
|
-
def constant_backoff(
|
51
|
+
def constant_backoff(n)
|
28
52
|
Enumerator.new do |yielder|
|
29
53
|
loop do
|
30
|
-
yielder <<
|
54
|
+
yielder << n
|
31
55
|
end
|
32
56
|
end
|
33
57
|
end
|
data/lib/rhod/command.rb
CHANGED
data/lib/rhod/version.rb
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/helper')
|
3
|
+
|
4
|
+
describe Rhod::Backoffs do
|
5
|
+
describe "backoff_sugar_to_enumerator" do
|
6
|
+
it "returns enumerators as is" do
|
7
|
+
e = Rhod::Backoffs.constant_backoff(0)
|
8
|
+
Rhod::Backoffs.backoff_sugar_to_enumerator(e).must_equal e
|
9
|
+
end
|
10
|
+
|
11
|
+
it "generates constant backoffs from a Numeric" do
|
12
|
+
Rhod::Backoffs.backoff_sugar_to_enumerator(2.0).next.must_equal 2.0
|
13
|
+
Rhod::Backoffs.backoff_sugar_to_enumerator(5).next.must_equal 5
|
14
|
+
end
|
15
|
+
|
16
|
+
it "generates expoential backoffs with '^' syntax" do
|
17
|
+
Rhod::Backoffs.backoff_sugar_to_enumerator("^2.0").take(3).must_equal [4.0, 8.0, 16.0]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "generates logarithmic backoffs with 'l' syntax" do
|
21
|
+
Rhod::Backoffs.backoff_sugar_to_enumerator("l2.0").
|
22
|
+
take(3).must_equal [3.169925001442312, 4.0, 4.643856189774724]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "generates expoential backoffs with :^ syntax" do
|
26
|
+
Rhod::Backoffs.backoff_sugar_to_enumerator(:^).take(3).must_equal [2.0, 4.0, 8.0]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "generates logarithmic backoffs with :l syntax" do
|
30
|
+
Rhod::Backoffs.backoff_sugar_to_enumerator(:l).
|
31
|
+
take(3).must_equal [0.7570232465074598, 2.403267722339301, 3.444932048942182]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/test/test_command.rb
CHANGED
@@ -9,45 +9,67 @@ describe Rhod::Command do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
describe "execute" do
|
12
|
-
it "retries requests" do
|
13
|
-
val = 0
|
14
|
-
|
15
|
-
begin
|
16
|
-
Rhod::Command.new(:retries => 1, :backoffs => Rhod::Backoffs.constant_backoff(0)) do
|
17
|
-
val += 1
|
18
|
-
raise StandardError
|
19
|
-
end.execute
|
20
|
-
rescue
|
21
|
-
end
|
22
|
-
|
23
|
-
val.must_equal 2
|
24
|
-
end
|
25
|
-
|
26
12
|
it "takes args" do
|
27
13
|
Rhod::Command.new(1) {|a| 1 + a}.execute.must_equal 2
|
28
14
|
end
|
29
15
|
|
30
|
-
describe "
|
31
|
-
|
32
|
-
|
33
|
-
|
16
|
+
describe "with failures" do
|
17
|
+
|
18
|
+
describe "retrying" do
|
19
|
+
it "retries" do
|
20
|
+
val = 0
|
21
|
+
|
22
|
+
begin
|
23
|
+
Rhod::Command.new(:retries => 1, :backoffs => 0) do
|
24
|
+
val += 1
|
25
|
+
raise StandardError
|
26
|
+
end.execute
|
27
|
+
rescue
|
28
|
+
end
|
29
|
+
|
30
|
+
val.must_equal 2
|
31
|
+
end
|
32
|
+
|
33
|
+
it "uses backoffs" do
|
34
|
+
backoff = MiniTest::Mock.new
|
35
|
+
backoff.expect(:next, 0)
|
36
|
+
|
37
|
+
Rhod::Backoffs.stub(:constant_backoff, backoff) do
|
38
|
+
begin
|
39
|
+
Rhod::Command.new(:retries => 1, :backoffs => 0) do
|
40
|
+
val += 1
|
41
|
+
raise StandardError
|
42
|
+
end.execute
|
43
|
+
rescue
|
44
|
+
end
|
45
|
+
end
|
46
|
+
backoff.verify
|
47
|
+
end
|
34
48
|
|
35
|
-
it "passes args to fallbacks" do
|
36
|
-
Rhod::Command.new(1, :fallback => ->(a) { 1 + a }) {raise StandardError}.execute.must_equal 2
|
37
49
|
end
|
38
50
|
|
39
|
-
|
40
|
-
|
51
|
+
describe "it uses fallbacks" do
|
52
|
+
it "triggers fallback on failure" do
|
53
|
+
Rhod::Command.new(:fallback => -> { 1 }) {raise StandardError}.execute.must_equal 1
|
54
|
+
end
|
55
|
+
|
56
|
+
it "passes args to fallbacks" do
|
57
|
+
Rhod::Command.new(1, :fallback => ->(a) { 1 + a }) {raise StandardError}.execute.must_equal 2
|
58
|
+
end
|
59
|
+
|
60
|
+
it "only uses fallback after all retries" do
|
61
|
+
val = 0
|
41
62
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
63
|
+
Rhod::Command.new(
|
64
|
+
:retries => 1,
|
65
|
+
:backoffs => 0,
|
66
|
+
:fallback => -> { 1 }) do
|
67
|
+
val += 1
|
68
|
+
raise StandardError
|
69
|
+
end.execute
|
49
70
|
|
50
|
-
|
71
|
+
val.must_equal 2
|
72
|
+
end
|
51
73
|
end
|
52
74
|
end
|
53
75
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rhod
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Bergeron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-04-
|
11
|
+
date: 2013-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -99,6 +99,7 @@ files:
|
|
99
99
|
- lib/rhod/version.rb
|
100
100
|
- rhod.gemspec
|
101
101
|
- test/helper.rb
|
102
|
+
- test/test_backoffs.rb
|
102
103
|
- test/test_command.rb
|
103
104
|
homepage: https://github.com/dinedal/rhod
|
104
105
|
licenses:
|
@@ -126,4 +127,5 @@ specification_version: 4
|
|
126
127
|
summary: A High Avalibility framework for Ruby
|
127
128
|
test_files:
|
128
129
|
- test/helper.rb
|
130
|
+
- test/test_backoffs.rb
|
129
131
|
- test/test_command.rb
|