rhod 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: deae1b7b10203a606f6ba69985c0b0520e3ed666
4
+ data.tar.gz: e7485d2cb082e4697c55ac0af77ad59a94f5e9d2
5
+ SHA512:
6
+ metadata.gz: fbb5a386678bbdb265d75323df5cbb8c8f77100a4224d814dbe7d3391367f7cbfeaee3b9e203de061a3d2a50eacbd0ce35d49c0d3ae4472c1427b113ea340c37
7
+ data.tar.gz: 69cab5ac043ac3806c37f47e55f39b5a42b91cc01b9015ddebda5ce91d7258dfe97a999a260225597405aef2ce082d99a4d671e6fd0daac79b7319d420d75580
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.pryrc ADDED
@@ -0,0 +1 @@
1
+ require './lib/rhod'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rhod.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Paul Bergeron
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # Rhod
2
+
3
+ A Lightweight High Avalibility framework for Ruby, inspired by [Hystrix](https://github.com/Netflix/Hystrix)
4
+
5
+ > Korben Dallas: You guard this with your life, or you're gonna look like this guy here! You green?
6
+ >
7
+ > DJ Ruby Rhod: G-green.
8
+ >
9
+ > Korben Dallas: Super green?
10
+ >
11
+ > DJ Ruby Rhod: Super green.
12
+
13
+ Rhod helps you handle failures gracefully, even during a firefight. When your code has to interact with other services, it also means writing code to keep it running in the event of failure. Failures can include exceptions, timeouts, downed hosts, and any number of issues that are caused by events outside of your application.
14
+
15
+ Rhod allows you to fully customize how your application reacts when it can't reach a service it needs. but by default it is configured for a 'fail fast' scenario. With some configuration, Rhod can support the following failure scenarios and variations on them:
16
+
17
+ - Fail Fast
18
+ - Retry N times before Fail
19
+ - Retry N times with progressive backoffs before Fail
20
+ - Fail Silent
21
+ - Fail w/ Fallback
22
+ - Primary / Secondary ("hot spare") switch over
23
+
24
+ ## Installation
25
+
26
+ Rhod requires Ruby 1.9.2 or greater.
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ gem 'rhod'
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ Or install it yourself as:
37
+
38
+ $ gem install rhod
39
+
40
+ ## Usage
41
+
42
+ Rhod has a very simple API. Design your application as you would normally, then enclose network accessing portions of your code with:
43
+
44
+ Rhod.execute do
45
+ ...
46
+ end
47
+
48
+ This implements the "Fail Fast" scenario by default.
49
+
50
+ Example, open a remote reasource, fail immediately if it fails:
51
+
52
+ require 'open-uri'
53
+ require 'rhod'
54
+
55
+ Rhod.execute { open("http://google.com").read }
56
+
57
+ ### Retries with and without backoffs
58
+
59
+ #### Idempotence Caution
60
+
61
+ Code within a `Rhod::Command` block with reties in use must be _idempotent_, i.e., safe to run multiple times.
62
+
63
+ Rhod supports retying up to N times. By default it uses a logarithmic backoff:
64
+
65
+ Rhod::Backoffs.default.take(5)
66
+ # [0.7570232465074598, 2.403267722339301, 3.444932048942182, 4.208673319629471, 4.811984719351674]
67
+
68
+ 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
+
70
+ Example, open a remote reasource, fail once it has failed 10 times, with the default (logarithmic) backoff:
71
+
72
+ require 'open-uri'
73
+ require 'rhod'
74
+
75
+ Rhod::Command.execute(:retries => 10) { open("http://google.com").read }
76
+
77
+ Example, open a remote reasource, fail once it has failed 10 times, waiting 0.2 seconds between attempts:
78
+
79
+ require 'open-uri'
80
+ require 'rhod'
81
+
82
+ Rhod.execute(:retries => 10, :backoffs => Rhod::Backoffs.constant_backoff(0.2)) do
83
+ open("http://google.com").read
84
+ end
85
+
86
+ Example, open a remote reasource, fail once it has failed 10 times, with an exponetially growing wait time between attempts:
87
+
88
+ require 'open-uri'
89
+ require 'rhod'
90
+
91
+ Rhod.execute(:retries => 10, :backoffs => Rhod::Backoffs.expoential_backoffs) do
92
+ open("http://google.com").read
93
+ end
94
+
95
+ Example, open a remote reasource, fail once it has failed 10 times, with waiting between attempts:
96
+
97
+ require 'open-uri'
98
+ require 'rhod'
99
+
100
+ Rhod.execute(:retries => 10, :backoffs => Rhod::Backoffs.constant_backoff(0)) do
101
+ open("http://google.com").read
102
+ end
103
+
104
+ ### Fail Silent
105
+
106
+ In the event of a failure, Rhod falls back to a `fallback`. The most basic case is to fall back to a constant value.
107
+
108
+ Example, open a remote reasource, if it fails return them empty string.
109
+
110
+ require 'open-uri'
111
+ require 'rhod'
112
+
113
+ Rhod.execute(:fallback => -> {""}) do
114
+ open("http://google.com").read
115
+ end
116
+
117
+ ### Fail w/ Fallback
118
+
119
+ 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
+
121
+ require 'open-uri'
122
+ require 'rhod'
123
+
124
+ search_engine_fallback = Rhod::Command.new(
125
+ :fallback => -> {""} # couldn't get anything
126
+ ) do
127
+ open("https://yahoo.com").read
128
+ end
129
+
130
+ Rhod.execute(:fallback => -> { search_engine_fallback.execute }) do
131
+ open("http://google.com").read
132
+ end
133
+
134
+ ### Primary / Secondary ("Hot Spare") switch over
135
+
136
+ Sometimes the fallback is just a part of normal operation. Just code in the state of which back end to access.
137
+
138
+ require 'open-uri'
139
+ require 'rhod'
140
+
141
+ class SearchEngineHTML
142
+ attr_accessor :secondary
143
+
144
+ def fetch
145
+ url = !@secondary ? "http://google.com" : "https://yahoo.com"
146
+
147
+ Rhod.execute(url, :fallback => Proc.new { @secondary = !@secondary; fetch }) do |url|
148
+ open(url).read
149
+ end
150
+ end
151
+ end
152
+
153
+ search_engine_html = SearchEngineHTML.new
154
+
155
+ search_engine_html.fetch
156
+
157
+ ## Contributing
158
+
159
+ 1. Fork it
160
+ 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
161
+ 3. Make your changes and add tests, verify they pass with (`bundle exec rake test`)
162
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
163
+ 5. Push to the branch (`git push origin feature/my-new-feature`)
164
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'test'
6
+ test.test_files = Dir['test/**/test_*.rb']
7
+ test.verbose = true
8
+ end
data/lib/rhod.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative "rhod/version"
2
+ require_relative "rhod/backoffs"
3
+ require_relative "rhod/command"
4
+
5
+ module Rhod
6
+ def self.execute(*args, &block)
7
+ Rhod::Command.execute(*args, &block)
8
+ end
9
+ end
@@ -0,0 +1,36 @@
1
+ module Rhod::Backoffs
2
+
3
+ extend self
4
+ # Returns a generator of a expoentially increasing series starting at 1
5
+ def expoential_backoffs
6
+ Enumerator.new do |yielder|
7
+ x = 0
8
+ loop do
9
+ x += 1
10
+ yielder << (1.0/2.0*(2.0**x - 1.0)).ceil
11
+ end
12
+ end
13
+ end
14
+
15
+ # Returns a generator of a logarithmicly increasing series starting at 0.3
16
+ def logarithmic_backoffs
17
+ Enumerator.new do |yielder|
18
+ x = 0.3
19
+ loop do
20
+ x += 1
21
+ yielder << Math.log2(x**2)
22
+ end
23
+ end
24
+ end
25
+
26
+ # Always the same backoff
27
+ def constant_backoff(i)
28
+ Enumerator.new do |yielder|
29
+ loop do
30
+ yielder << i
31
+ end
32
+ end
33
+ end
34
+
35
+ alias default logarithmic_backoffs
36
+ end
@@ -0,0 +1,46 @@
1
+ class Rhod::Command
2
+
3
+ EXCEPTIONS = [Exception, StandardError]
4
+
5
+ def initialize(*args, &block)
6
+ opts = args[-1].kind_of?(Hash) ? args.pop : {}
7
+ @args = args
8
+ @args ||= []
9
+
10
+ @request = block
11
+
12
+ @retries = opts[:retries]
13
+ @retries ||= 0
14
+ @attempts = 0
15
+
16
+ @backoffs = opts[:backoffs]
17
+ @backoffs ||= Rhod::Backoffs.default
18
+
19
+ @fallback = opts[:fallback]
20
+ end
21
+
22
+ ### Class methods
23
+
24
+ def self.execute(*args, &block)
25
+ this = self.new(*args, &block)
26
+ this.execute
27
+ end
28
+
29
+ ### Instance methods
30
+
31
+ def execute
32
+ begin
33
+ @request.call(*@args)
34
+ rescue *EXCEPTIONS
35
+ @attempts += 1
36
+ if @attempts <= @retries
37
+ sleep(@backoffs.next)
38
+ retry
39
+ else
40
+ return @fallback.call(*@args) if @fallback
41
+ raise
42
+ end
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,3 @@
1
+ module Rhod
2
+ VERSION = "0.0.1"
3
+ end
data/rhod.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rhod/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rhod"
8
+ spec.version = Rhod::VERSION
9
+ spec.authors = ["Paul Bergeron"]
10
+ spec.email = ["paul.d.bergeron@gmail.com"]
11
+ spec.summary = %q{A High Avalibility framework for Ruby}
12
+ spec.homepage = "https://github.com/dinedal/rhod"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.3"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "pry"
23
+ spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "turn"
25
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'rhod'
2
+ require 'turn'
3
+
4
+ Turn.config do |c|
5
+ # use one of output formats:
6
+ # :outline - turn's original case/test outline mode [default]
7
+ # :progress - indicates progress with progress bar
8
+ # :dotted - test/unit's traditional dot-progress mode
9
+ # :pretty - new pretty reporter
10
+ # :marshal - dump output as YAML (normal run mode only)
11
+ # :cue - interactive testing
12
+ c.format = :pretty
13
+ # turn on invoke/execute tracing, enable full backtrace
14
+ c.trace = 100
15
+ # use humanized test names (works only with :outline format)
16
+ c.natural = true
17
+ end
@@ -0,0 +1,54 @@
1
+ require 'minitest/autorun'
2
+ require File.expand_path(File.dirname(__FILE__) + '/helper')
3
+
4
+ describe Rhod::Command do
5
+ describe "self.execute" do
6
+ it "runs immediately and returns inner value" do
7
+ Rhod::Command.execute { 1 }.must_equal 1
8
+ end
9
+ end
10
+
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
+ it "takes args" do
27
+ Rhod::Command.new(1) {|a| 1 + a}.execute.must_equal 2
28
+ end
29
+
30
+ describe "fallbacks" do
31
+ it "triggers fallback on failure" do
32
+ Rhod::Command.new(:fallback => -> { 1 }) {raise StandardError}.execute.must_equal 1
33
+ end
34
+
35
+ it "passes args to fallbacks" do
36
+ Rhod::Command.new(1, :fallback => ->(a) { 1 + a }) {raise StandardError}.execute.must_equal 2
37
+ end
38
+
39
+ it "only uses fallback after all retries" do
40
+ val = 0
41
+
42
+ Rhod::Command.new(
43
+ :retries => 1,
44
+ :backoffs => Rhod::Backoffs.constant_backoff(0),
45
+ :fallback => -> { 1 }) do
46
+ val += 1
47
+ raise StandardError
48
+ end.execute
49
+
50
+ val.must_equal 2
51
+ end
52
+ end
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rhod
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Paul Bergeron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
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: minitest
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: turn
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
+ description:
84
+ email:
85
+ - paul.d.bergeron@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - .pryrc
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - lib/rhod.rb
97
+ - lib/rhod/backoffs.rb
98
+ - lib/rhod/command.rb
99
+ - lib/rhod/version.rb
100
+ - rhod.gemspec
101
+ - test/helper.rb
102
+ - test/test_command.rb
103
+ homepage: https://github.com/dinedal/rhod
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.0.3
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: A High Avalibility framework for Ruby
127
+ test_files:
128
+ - test/helper.rb
129
+ - test/test_command.rb