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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'rescue_me'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -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