retryable-rb 1.0.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/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