retryable-rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1 @@
1
+ v1.0.0. First release.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011 Robert Sosinski (http://www.robertsosinski.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,9 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ README.markdown
5
+ Rakefile
6
+ lib/retryable.rb
7
+ retryable-rb.gemspec
8
+ retryable.gemspec
9
+ test/retryable_test.rb
data/README.markdown ADDED
@@ -0,0 +1,82 @@
1
+ Introduction
2
+ ============
3
+
4
+ Retryable is an easy to use DSL to retry code if an exception is raised. This is especially useful when interacting with unreliable services that fail randomly.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ gem install retryable
10
+
11
+ # In your ruby application
12
+ require 'retryable'
13
+
14
+ Using Retryable
15
+ ---------------
16
+
17
+ Code wrapped in a Retryable 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.
18
+
19
+ # Include Retryable into your class
20
+ class Api
21
+ include Retryable
22
+
23
+ # Use it in methods that interact with unreliable services
24
+ def get
25
+ retryable do
26
+ # code here...
27
+ end
28
+ end
29
+ end
30
+
31
+ By default, Retryable will rescue any exception inherited from `Exception`, retry once (for a total of two attempts) and sleep for 100 milliseconds. You can choose additional options by passing them via an options `Hash`.
32
+
33
+ retryable :on => Timeout::Error, :times => 3, :sleep => 1 do
34
+ # code here...
35
+ end
36
+
37
+ 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.
38
+
39
+ retryable :on => [Timeout::Error, Errno::ECONNRESET] do
40
+ # code here...
41
+ end
42
+
43
+ Retryable also allows for callbacks to be defined, which is useful to log failures for analytics purposes or cleanup after repeated failures. Retryable has three types of callbacks: `then`, `finally`, and `always`.
44
+
45
+ `then`: Run every time a failure occurs.
46
+
47
+ `finally`: Run when the number of retries runs out.
48
+
49
+ `always`: Run when the code wrapped in a Retryable block passes or when the number of retries runs out.
50
+
51
+ The `then` and `finally` callbacks pass the exception raised, which can be used for logging or error control. All three callbacks also have a `handler`, which provides an interface to pass data between the code wrapped in the Retryable block and the callbacks defined.
52
+
53
+ Furthermore, each callback provides the number of `attempts`, `retries` and `times` that the wrapped code should be retried. As these are specified in a `Proc`, unnecessary variables can be left out of the parameter list.
54
+
55
+ then_cb = Proc.new do |exception, handler, attempts, retries, times|
56
+ log "#{exception.class}: '#{exception.message}' - #{attempts} attempts, #{retries} out of #{times} retries left."}
57
+ end
58
+
59
+ finally_cb = Proc.new do |exception, handler|
60
+ log "#{exception.class} raised too many times. First attempt at #{handler[:start]} and final attempt at #{Time.now}"
61
+ end
62
+
63
+ always_cb = Proc.new do |handler, attempts|
64
+ log "total time for #{attempts} attempts: #{Time.now - handler[:start]}"
65
+ end
66
+
67
+ retryable :then => then_cb do, :finally => finally_cb, :always => always_cb |handler|
68
+ handler[:start] ||= Time.now
69
+
70
+ # code here...
71
+ end
72
+
73
+ If you are using Retryable once or outside of a class, you can also use it via its module method as well.
74
+
75
+ Retryable.retryable do
76
+ # code here...
77
+ end
78
+
79
+ Credits
80
+ -------
81
+
82
+ Retryable was inspired by code written by [Michael Celona](http://github.com/mcelona) and later assisted by [David Malin](http://github.com/dmalin).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'echoe'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ end
8
+
9
+ Echoe.new("retryable-rb") do |p|
10
+ p.author = "Robert Sosinski"
11
+ p.email = "email@robertsosinski.com"
12
+ p.url = "http://github.com/robertsosinski/retryable"
13
+ p.description = p.summary = "Easy to use DSL to retry code if an exception is raised."
14
+ p.runtime_dependencies = []
15
+ p.development_dependencies = ["echoe >=4.3.1"]
16
+ end
data/lib/retryable.rb ADDED
@@ -0,0 +1,33 @@
1
+ module Retryable
2
+ extend self
3
+
4
+ def retryable(options = {})
5
+ opts = {:on => Exception, :times => 1, :sleep => 0.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]
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
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{retryable-rb}
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Robert Sosinski"]
9
+ s.date = %q{2011-04-12}
10
+ s.description = %q{Easy to use DSL to retry code if an exception is raised.}
11
+ s.email = %q{email@robertsosinski.com}
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.markdown", "lib/retryable.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.markdown", "Rakefile", "lib/retryable.rb", "retryable-rb.gemspec", "retryable.gemspec", "test/retryable_test.rb"]
14
+ s.homepage = %q{http://github.com/robertsosinski/retryable}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Retryable-rb", "--main", "README.markdown"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{retryable-rb}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Easy to use DSL to retry code if an exception is raised.}
20
+ s.test_files = ["test/retryable_test.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ s.add_development_dependency(%q<echoe>, [">= 4.3.1"])
28
+ else
29
+ s.add_dependency(%q<echoe>, [">= 4.3.1"])
30
+ end
31
+ else
32
+ s.add_dependency(%q<echoe>, [">= 4.3.1"])
33
+ end
34
+ end
data/retryable.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{retryable}
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Robert Sosinski"]
9
+ s.date = %q{2011-04-11}
10
+ s.description = %q{Easy to use DSL to retry code if an exception is raised.}
11
+ s.email = %q{email@robertsosinski.com}
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.markdown", "lib/retryable.rb"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.markdown", "Rakefile", "lib/retryable.rb", "retryable.gemspec", "test/retryable_test.rb"]
14
+ s.homepage = %q{http://github.com/robertsosinski/retryable}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Retryable", "--main", "README.markdown"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{retryable}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Easy to use DSL to retry code if an exception is raised.}
20
+ s.test_files = ["test/retryable_test.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ s.add_development_dependency(%q<echoe>, [">= 4.3.1"])
28
+ else
29
+ s.add_dependency(%q<echoe>, [">= 4.3.1"])
30
+ end
31
+ else
32
+ s.add_dependency(%q<echoe>, [">= 4.3.1"])
33
+ end
34
+ end
@@ -0,0 +1,72 @@
1
+ $:.unshift(File.dirname(__FILE__) + "../lib")
2
+
3
+ require 'test/unit'
4
+ require 'retryable'
5
+
6
+ class RetryableTest < Test::Unit::TestCase
7
+ def test_without_arguments
8
+ i = 0
9
+
10
+ Retryable.retryable do
11
+ i += 1
12
+
13
+ raise Exception.new
14
+ end
15
+ rescue Exception
16
+ assert_equal i, 2
17
+ end
18
+
19
+ def test_with_one_exception_and_two_times
20
+ i = 0
21
+
22
+ Retryable.retryable :on => EOFError, :times => 2 do
23
+ i += 1
24
+
25
+ raise EOFError.new
26
+ end
27
+
28
+ rescue EOFError
29
+ assert_equal i, 3
30
+ end
31
+
32
+ def test_with_arguments_and_handler
33
+ i = 0
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
+ Retryable.retryable :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
65
+
66
+ raise ArgumentError.new
67
+ end
68
+
69
+ rescue ArgumentError
70
+ assert_equal i, 6
71
+ end
72
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: retryable-rb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Robert Sosinski
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-12 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: echoe
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 49
30
+ segments:
31
+ - 4
32
+ - 3
33
+ - 1
34
+ version: 4.3.1
35
+ type: :development
36
+ version_requirements: *id001
37
+ description: Easy to use DSL to retry code if an exception is raised.
38
+ email: email@robertsosinski.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - CHANGELOG
45
+ - LICENSE
46
+ - README.markdown
47
+ - lib/retryable.rb
48
+ files:
49
+ - CHANGELOG
50
+ - LICENSE
51
+ - Manifest
52
+ - README.markdown
53
+ - Rakefile
54
+ - lib/retryable.rb
55
+ - retryable-rb.gemspec
56
+ - retryable.gemspec
57
+ - test/retryable_test.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/robertsosinski/retryable
60
+ licenses: []
61
+
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --line-numbers
65
+ - --inline-source
66
+ - --title
67
+ - Retryable-rb
68
+ - --main
69
+ - README.markdown
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 11
87
+ segments:
88
+ - 1
89
+ - 2
90
+ version: "1.2"
91
+ requirements: []
92
+
93
+ rubyforge_project: retryable-rb
94
+ rubygems_version: 1.3.7
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: Easy to use DSL to retry code if an exception is raised.
98
+ test_files:
99
+ - test/retryable_test.rb