rescue_me 0.1.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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +26 -0
- data/README.rdoc +55 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/lib/rescue_me.rb +30 -0
- data/test/helper.rb +10 -0
- data/test/test_rescue_me.rb +86 -0
- metadata +90 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2010 Arild Shirazi. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without modification,
|
4
|
+
are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
1. Redistributions of source code must retain the above copyright notice,
|
7
|
+
this list of conditions and the following disclaimer.
|
8
|
+
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED BY ARILD SHIRAZI ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
14
|
+
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
15
|
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
16
|
+
SHALL ARILD SHIRAZI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
17
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
18
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
19
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
20
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
21
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
22
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
The views and conclusions contained in the software and documentation are those
|
25
|
+
of the authors and should not be interpreted as representing official policies,
|
26
|
+
either expressed or implied, of Arild Shirazi.
|
data/README.rdoc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
= rescue_me
|
2
|
+
|
3
|
+
Provides a convenience method to retry blocks of code that might fail due to
|
4
|
+
temporary errors, e.g. a network service that becomes temporarily unavailable.
|
5
|
+
The retries are timed to back-off exponentially (2^n seconds), hopefully giving
|
6
|
+
time for the remote server to recover. These are the default wait times between
|
7
|
+
consecutive attempts:
|
8
|
+
0, 1, 2, 4, 8, 16, 32, 64, 128, ... seconds
|
9
|
+
|
10
|
+
Usage:
|
11
|
+
rescue_and_retry(max_attempts, *temporary_exceptions) {
|
12
|
+
# your code
|
13
|
+
}
|
14
|
+
|
15
|
+
Example - retry my code up to 7 times (over about a minute) if I see the
|
16
|
+
following 2 network errors:
|
17
|
+
rescue_and_retry(7, Net::SMTPServerBusy, IOError) {
|
18
|
+
smtp.send_message(message, from_address, to_address )
|
19
|
+
}
|
20
|
+
|
21
|
+
Log output:
|
22
|
+
WARN -- : rescue and retry (attempt 1/5): ./mailer.rb:43, <SMTPServerBusy: 451 4.3.0 Mail server temporarily rejected message.>
|
23
|
+
WARN -- : rescue and retry (attempt 2/5): ./mailer.rb:43, <SMTPServerBusy: 451 4.3.0 Mail server temporarily rejected message.>
|
24
|
+
WARN -- : rescue and retry (attempt 3/5): ./mailer.rb:43, <SMTPServerBusy: 451 4.3.0 Mail server temporarily rejected message.>
|
25
|
+
# No further output or stacktrace. Block succeeded on 4th attempt.
|
26
|
+
|
27
|
+
|
28
|
+
== Copyright
|
29
|
+
|
30
|
+
Copyright (c) 2010 Arild Shirazi. All rights reserved.
|
31
|
+
|
32
|
+
Redistribution and use in source and binary forms, with or without modification,
|
33
|
+
are permitted provided that the following conditions are met:
|
34
|
+
|
35
|
+
1. Redistributions of source code must retain the above copyright notice,
|
36
|
+
this list of conditions and the following disclaimer.
|
37
|
+
|
38
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
39
|
+
this list of conditions and the following disclaimer in the documentation
|
40
|
+
and/or other materials provided with the distribution.
|
41
|
+
|
42
|
+
THIS SOFTWARE IS PROVIDED BY ARILD SHIRAZI ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
43
|
+
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
44
|
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
45
|
+
SHALL ARILD SHIRAZI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
46
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
47
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
48
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
49
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
50
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
51
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
52
|
+
|
53
|
+
The views and conclusions contained in the software and documentation are those
|
54
|
+
of the authors and should not be interpreted as representing official policies,
|
55
|
+
either expressed or implied, of Arild Shirazi.
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rescue_me"
|
8
|
+
gem.summary = %Q{Retry a block of code that might fail due to temporary errors.}
|
9
|
+
gem.description = %Q{Provides a convenience method to retry blocks of code \
|
10
|
+
that might fail due to temporary errors, e.g. a network service that becomes \
|
11
|
+
temporarily unavailable. The retries are timed to back-off exponentially (2^n \
|
12
|
+
seconds), hopefully giving time for the remote server to recover.}
|
13
|
+
gem.email = "as4@eshirazi.com"
|
14
|
+
gem.homepage = "http://github.com/ashirazi/rescue_me"
|
15
|
+
gem.authors = ["Arild Shirazi"]
|
16
|
+
# gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
17
|
+
gem.add_development_dependency "shoulda", ">= 0"
|
18
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
19
|
+
end
|
20
|
+
Jeweler::GemcutterTasks.new
|
21
|
+
rescue LoadError
|
22
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'rake/testtask'
|
26
|
+
Rake::TestTask.new(:test) do |test|
|
27
|
+
test.libs << 'lib' << 'test'
|
28
|
+
test.pattern = 'test/**/test_*.rb'
|
29
|
+
test.verbose = true
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
require 'rcov/rcovtask'
|
34
|
+
Rcov::RcovTask.new do |test|
|
35
|
+
test.libs << 'test'
|
36
|
+
test.pattern = 'test/**/test_*.rb'
|
37
|
+
test.verbose = true
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
task :rcov do
|
41
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
task :test => :check_dependencies
|
46
|
+
|
47
|
+
task :default => :test
|
48
|
+
|
49
|
+
require 'rake/rdoctask'
|
50
|
+
Rake::RDocTask.new do |rdoc|
|
51
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
52
|
+
|
53
|
+
rdoc.rdoc_dir = 'rdoc'
|
54
|
+
rdoc.title = "rescue_me #{version}"
|
55
|
+
rdoc.rdoc_files.include('README*')
|
56
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
57
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/rescue_me.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Kernel
|
2
|
+
|
3
|
+
# Reattempts to run code passed in to this block if a temporary exception occurs
|
4
|
+
# (e.g. Net::SMTPServerBusy), using an exponential back-off algorithm
|
5
|
+
# (e.g. retry immediately, then after 1, 2, 4, 8, 16, 32... sec).
|
6
|
+
# max_attempts - the maximum number of attempts to make trying to run the
|
7
|
+
# block successfully before giving up
|
8
|
+
# temporary_exceptions - temporary exceptions that are to be caught in your
|
9
|
+
# code. If no exceptions are provided will capture all Exceptions. You are
|
10
|
+
# strongly encouraged to provide arguments that capture temporary
|
11
|
+
# exceptional conditions that are likely to work upon a retry.
|
12
|
+
def rescue_and_retry(max_attempts=7, *temporary_exceptions)
|
13
|
+
retry_interval = 2 # A good initial start value. Tweak as needed.
|
14
|
+
temporary_exceptions << Exception if temporary_exceptions.empty?
|
15
|
+
begin
|
16
|
+
yield
|
17
|
+
rescue *temporary_exceptions => e
|
18
|
+
attempt = (attempt || 0) + 1
|
19
|
+
message = "rescue and retry (attempt #{attempt}/#{max_attempts}): " +
|
20
|
+
"#{caller.first}, <#{e.class}: #{e.message}>"
|
21
|
+
# TODO AS: Is there a better way to access and use a logger?
|
22
|
+
(defined? logger) ? logger.warn(message) : puts(message)
|
23
|
+
raise(e) if attempt >= max_attempts
|
24
|
+
# Retry immediately before exponential waits (1, 2, 4, 16, ... sec)
|
25
|
+
sleep retry_interval**(attempt - 2) if attempt >= 2
|
26
|
+
retry
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestRescueMe < Test::Unit::TestCase
|
4
|
+
|
5
|
+
class ExceptionCounter
|
6
|
+
|
7
|
+
require 'net/smtp'
|
8
|
+
attr_reader :method_called_count
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@method_called_count = 0;
|
12
|
+
end
|
13
|
+
|
14
|
+
def exception_free
|
15
|
+
@method_called_count += 1
|
16
|
+
"This method does not raise exceptions"
|
17
|
+
end
|
18
|
+
|
19
|
+
def raise_zero_division_error
|
20
|
+
@method_called_count += 1
|
21
|
+
12/0
|
22
|
+
end
|
23
|
+
|
24
|
+
# Will raise SMTPServerBusy the first x times this method is called,
|
25
|
+
# after which this method does nothing.
|
26
|
+
def raise_smtp_exception_until_call(times)
|
27
|
+
@method_called_count += 1
|
28
|
+
@smtp_exception_count = (@smtp_exception_count ||= 0) + 1
|
29
|
+
raise Net::SMTPServerBusy until times > @smtp_exception_count
|
30
|
+
end
|
31
|
+
|
32
|
+
end # ExceptionCounter
|
33
|
+
|
34
|
+
context "rescue_and_retry of code that might raise temporary exceptions" do
|
35
|
+
setup do
|
36
|
+
@exception_counter = ExceptionCounter.new
|
37
|
+
@previous_call_count = @exception_counter.method_called_count
|
38
|
+
end
|
39
|
+
|
40
|
+
should "run an exception-free block of code once" do
|
41
|
+
assert_nothing_raised do
|
42
|
+
rescue_and_retry {
|
43
|
+
@exception_counter.exception_free
|
44
|
+
}
|
45
|
+
end
|
46
|
+
assert 1, @exception_counter.method_called_count
|
47
|
+
end
|
48
|
+
|
49
|
+
should "attempt to run a block that raises an unexpected exception " +
|
50
|
+
"only once" do
|
51
|
+
assert_raise(ZeroDivisionError) do
|
52
|
+
rescue_and_retry(5, IOError) {
|
53
|
+
@exception_counter.raise_zero_division_error
|
54
|
+
}
|
55
|
+
end
|
56
|
+
assert 1, @exception_counter.method_called_count
|
57
|
+
end
|
58
|
+
|
59
|
+
should "re-run the block of code for exactly max_attempt number of times" do
|
60
|
+
assert_raise(ZeroDivisionError) do
|
61
|
+
rescue_and_retry(3, IOError) {
|
62
|
+
@exception_counter.raise_zero_division_error
|
63
|
+
}
|
64
|
+
end
|
65
|
+
assert 3, @exception_counter.method_called_count - @previous_call_count
|
66
|
+
|
67
|
+
assert_raise(ZeroDivisionError) do
|
68
|
+
rescue_and_retry(1, IOError) {
|
69
|
+
@exception_counter.raise_zero_division_error
|
70
|
+
}
|
71
|
+
end
|
72
|
+
assert 1, @exception_counter.method_called_count - @previous_call_count
|
73
|
+
end
|
74
|
+
|
75
|
+
should "not re-run the block of code after it has run successfully" do
|
76
|
+
assert_nothing_raised do
|
77
|
+
rescue_and_retry(5, Net::SMTPServerBusy) {
|
78
|
+
@exception_counter.raise_smtp_exception_until_call(3)
|
79
|
+
}
|
80
|
+
end
|
81
|
+
assert 3, @exception_counter.method_called_count
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rescue_me
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Arild Shirazi
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-24 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: shoulda
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: Provides a convenience method to retry blocks of code that might fail due to temporary errors, e.g. a network service that becomes temporarily unavailable. The retries are timed to back-off exponentially (2^n seconds), hopefully giving time for the remote server to recover.
|
36
|
+
email: as4@eshirazi.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- lib/rescue_me.rb
|
52
|
+
- test/helper.rb
|
53
|
+
- test/test_rescue_me.rb
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/ashirazi/rescue_me
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --charset=UTF-8
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_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
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.3.7
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Retry a block of code that might fail due to temporary errors.
|
88
|
+
test_files:
|
89
|
+
- test/helper.rb
|
90
|
+
- test/test_rescue_me.rb
|