durable_call 0.0.1 → 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/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .coveralls.yml
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in durable_call.gemspec
4
4
  gemspec
5
+
6
+ gem 'coveralls', :require => false
7
+
data/README.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  Invoke methods DRY and safely with parameterized retries, timeouts and logging
4
4
 
5
+ [![Travis CI](https://secure.travis-ci.org/AlexanderPavlenko/durable_call.png)](https://travis-ci.org/AlexanderPavlenko/durable_call)
6
+ [![Coverage Status](https://coveralls.io/repos/AlexanderPavlenko/durable_call/badge.png?branch=master)](https://coveralls.io/r/AlexanderPavlenko/durable_call)
7
+
5
8
  ## Installation
6
9
 
7
10
  Add this line to your application's Gemfile:
@@ -29,7 +32,7 @@ Multiple arguments and options:
29
32
  Where ```options``` may take:
30
33
 
31
34
  {
32
- :interval # 1. lambda, which takes retries number and returns seconds to sleep
35
+ :interval # 1. lambda, which takes retry number (min 1) and returns seconds to sleep
33
36
  # 2. just Float
34
37
  # 3. Symbol for built-in strategies, defaults to :rand
35
38
  :logger # Logger object, defaults to nil
@@ -4,6 +4,7 @@ require File.expand_path('../../lib/durable_call.rb', __FILE__)
4
4
  n = 500000
5
5
  Benchmark.bm do |bm|
6
6
  object = Object.new
7
+ caller = DurableCall::Caller.new(object)
7
8
 
8
9
  bm.report do
9
10
  n.times do
@@ -11,6 +12,12 @@ Benchmark.bm do |bm|
11
12
  end
12
13
  end
13
14
 
15
+ bm.report do
16
+ n.times do
17
+ caller.call(:object_id)
18
+ end
19
+ end
20
+
14
21
  bm.report do
15
22
  n.times do
16
23
  DurableCall.call(object, :object_id)
data/durable_call.gemspec CHANGED
@@ -21,4 +21,5 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "coveralls"
24
25
  end
@@ -13,9 +13,11 @@ module DurableCall
13
13
  }.freeze
14
14
 
15
15
  MESSAGES = {
16
- :new_retry => "Retry #%1$i",
17
- :failed_call => "Failed to call %1$s on %2$s: %3$s",
18
- :waiting_before_retry => "Waiting %1$.2f seconds before retry",
16
+ :new_retry => 'Retry #%1$i',
17
+ :failed_call => 'Failed to call %1$s on %2$s: %3$s',
18
+ :waiting_before_retry => 'Waiting %1$.2f seconds before retry',
19
+ :retries_error => 'Number of retries exceeded: %1$i',
20
+ :timeout_error => 'Timeout exceeded: %1$.2f',
19
21
  }.freeze
20
22
 
21
23
  attr_reader :subject
@@ -38,30 +40,19 @@ module DurableCall
38
40
  called = false
39
41
  result = nil
40
42
  (0..@retries).each do |retries_counter|
43
+ # @timeout may be exceeded here and exception will be raised
41
44
  begin
42
- @logger.info MESSAGES[:new_retry] % retries_counter if retries_counter > 0 if @logger
45
+ if retries_counter > 0
46
+ # first try isn't "retry"
47
+ log :info, :new_retry, retries_counter
48
+ end
43
49
  result = @subject.__send__ *args
44
50
  called = true
45
- rescue Timeout::Error => ex
46
- # just reraise exception if @timeout exceeded
47
- raise
48
51
  rescue => ex
49
- # @timeout may be exceeded here and exception will be raised
50
- @logger.warn MESSAGES[:failed_call] % [args.inspect, @subject, ex.inspect] if @logger
52
+ log :warn, :failed_call, args.inspect, @subject, ex.inspect
51
53
  if @interval && retries_counter < @retries
52
54
  # interval specified and it's not a last iteration
53
- seconds = if @interval.is_a? Symbol
54
- INTERVALS[@interval].call(retries_counter)
55
- elsif @interval.respond_to?(:call)
56
- @interval.call(retries_counter)
57
- else
58
- @interval
59
- end
60
- # sleep before next retry if needed
61
- if seconds > 0
62
- @logger.info MESSAGES[:waiting_before_retry] % seconds if @logger
63
- sleep seconds
64
- end
55
+ sleep_before_retry(retries_counter + 1)
65
56
  end
66
57
  else
67
58
  break
@@ -70,9 +61,35 @@ module DurableCall
70
61
  if called
71
62
  result
72
63
  else
73
- raise RetriesError, "Number of retries exceeded: #{@retries}"
64
+ log :error, :retries_error, @retries
65
+ raise RetriesError
74
66
  end
75
67
  end
68
+ rescue TimeoutError
69
+ log :error, :timeout_error, @timeout
70
+ raise
71
+ end
72
+
73
+ private
74
+
75
+ def sleep_before_retry(retries_counter)
76
+ seconds = if @interval.is_a? Symbol
77
+ INTERVALS[@interval].call(retries_counter)
78
+ elsif @interval.respond_to?(:call)
79
+ @interval.call(retries_counter)
80
+ else
81
+ @interval
82
+ end
83
+ # sleep before next retry if needed
84
+ if seconds > 0
85
+ log :info, :waiting_before_retry, seconds
86
+ sleep seconds
87
+ end
88
+ end
89
+
90
+ def log(level, message, *args)
91
+ return unless @logger
92
+ @logger.send level, MESSAGES[message] % args
76
93
  end
77
94
  end
78
95
  end
@@ -1,3 +1,3 @@
1
1
  module DurableCall
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -56,7 +56,9 @@ describe DurableCall do
56
56
  it 'invokes long method' do
57
57
  @wrapper = DurableCall::Caller.new(@subject, :timeout => 0.1, :logger => @logger)
58
58
  expect{ @wrapper.call(:long_method, 1) }.to raise_error(DurableCall::TimeoutError)
59
- @log.string.should == ''
59
+ valid_log?(@log.string, [
60
+ /E.*Timeout exceeded: 0.10/,
61
+ ]).should == true
60
62
  end
61
63
 
62
64
  it 'invokes not so long method' do
@@ -71,18 +73,48 @@ describe DurableCall do
71
73
  expect{ @wrapper.call(:failing_method, condition) }.to raise_error(DurableCall::RetriesError)
72
74
  end
73
75
 
74
- it 'invokes failing method with logging' do
76
+ it 'invokes failing method with constant intervals and logging' do
75
77
  @wrapper = DurableCall::Caller.new(@subject, :retries => 2, :interval => 0.0123, :logger => @logger)
76
78
  (condition = mock).should_receive(:call).exactly(3).times.and_return(true)
77
79
  expect{ @wrapper.call(:failing_method, condition) }.to raise_error(DurableCall::RetriesError)
78
80
  valid_log?(@log.string, [
79
- error = /W.*Failed to call \[\:failing_method, .*RuntimeError\: it happens/,
80
- waiting = /I.*Waiting 0\.01 seconds before retry/,
81
- /I.*Retry \#1/,
82
- error,
83
- waiting,
84
- /I.*Retry \#2/,
85
- error,
81
+ error = /W.*Failed to call \[\:failing_method, .*RuntimeError\: it happens/,
82
+ waiting = /I.*Waiting 0\.01 seconds before retry/,
83
+ /I.*Retry \#1/,
84
+ error,
85
+ waiting,
86
+ /I.*Retry \#2/,
87
+ error,
88
+ /E.*Number of retries exceeded: 2/,
89
+ ]).should == true
90
+ end
91
+
92
+ it 'invokes failing method with :rand intervals and logging' do
93
+ @wrapper = DurableCall::Caller.new(@subject, :retries => 1, :logger => @logger)
94
+ (condition = mock).should_receive(:call).exactly(2).times.and_return(true)
95
+ expect{ @wrapper.call(:failing_method, condition) }.to raise_error(DurableCall::RetriesError)
96
+ valid_log?(@log.string, [
97
+ error = /W.*Failed to call \[\:failing_method, .*RuntimeError\: it happens/,
98
+ /I.*Waiting [01]\.\d\d seconds before retry/,
99
+ /I.*Retry \#1/,
100
+ error,
101
+ /E.*Number of retries exceeded: 1/,
102
+ ]).should == true
103
+ end
104
+
105
+ it 'invokes failing method with custom intervals and logging' do
106
+ @wrapper = DurableCall::Caller.new(@subject, :retries => 2, :interval => lambda{|i| i / 100.0 }, :logger => @logger)
107
+ (condition = mock).should_receive(:call).exactly(3).times.and_return(true)
108
+ expect{ @wrapper.call(:failing_method, condition) }.to raise_error(DurableCall::RetriesError)
109
+ valid_log?(@log.string, [
110
+ error = /W.*Failed to call \[\:failing_method, .*RuntimeError\: it happens/,
111
+ /I.*Waiting 0\.01 seconds before retry/,
112
+ /I.*Retry \#1/,
113
+ error,
114
+ /I.*Waiting 0\.02 seconds before retry/,
115
+ /I.*Retry \#2/,
116
+ error,
117
+ /E.*Number of retries exceeded: 2/,
86
118
  ]).should == true
87
119
  end
88
120
 
@@ -91,9 +123,9 @@ describe DurableCall do
91
123
  (condition = mock).should_receive(:call).twice.and_return(true, false)
92
124
  @wrapper.call(:failing_method, condition).should == true
93
125
  valid_log?(@log.string, [
94
- /W.*Failed to call \[\:failing_method, .*RuntimeError\: it happens/,
95
- /I.*Waiting 0\.01 seconds before retry/,
96
- /I.*Retry \#1/,
126
+ /W.*Failed to call \[\:failing_method, .*RuntimeError\: it happens/,
127
+ /I.*Waiting 0\.01 seconds before retry/,
128
+ /I.*Retry \#1/,
97
129
  ]).should == true
98
130
  end
99
131
 
@@ -102,8 +134,9 @@ describe DurableCall do
102
134
  (condition = mock).should_receive(:call).once.and_return(true)
103
135
  expect{ @wrapper.call(:worse_method, 0.05, condition) }.to raise_error(DurableCall::TimeoutError)
104
136
  valid_log?(@log.string, [
105
- /W.*Failed to call \[\:worse_method, .*RuntimeError\: it happens/,
106
- /I.*Waiting 0\.05 seconds before retry/,
137
+ /W.*Failed to call \[\:worse_method, .*RuntimeError\: it happens/,
138
+ /I.*Waiting 0\.05 seconds before retry/,
139
+ /E.*Timeout exceeded: 0.10/,
107
140
  ]).should == true
108
141
  end
109
142
 
@@ -113,8 +146,8 @@ describe DurableCall do
113
146
  end
114
147
 
115
148
  def valid_log?(log, regexps)
149
+ puts "#{'-' * 20}\n", log
116
150
  raise ArgumentError if log.lines.count != regexps.size
117
- # puts "#{'-' * 20}\n", log
118
151
  log.lines.zip(regexps).all?{|(string, regexp)| string =~ regexp }
119
152
  end
120
153
  end
data/spec/spec_helper.rb CHANGED
@@ -1,2 +1,5 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'coveralls'
3
+ Coveralls.wear!
2
4
  require 'durable_call'
5
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: durable_call
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-22 00:00:00.000000000 Z
12
+ date: 2013-04-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -59,6 +59,22 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: coveralls
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
62
78
  description: Invoke methods DRY and safely with parameterized retries, timeouts and
63
79
  logging
64
80
  email:
@@ -94,12 +110,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
110
  - - ! '>='
95
111
  - !ruby/object:Gem::Version
96
112
  version: '0'
113
+ segments:
114
+ - 0
115
+ hash: -1232904255849708953
97
116
  required_rubygems_version: !ruby/object:Gem::Requirement
98
117
  none: false
99
118
  requirements:
100
119
  - - ! '>='
101
120
  - !ruby/object:Gem::Version
102
121
  version: '0'
122
+ segments:
123
+ - 0
124
+ hash: -1232904255849708953
103
125
  requirements: []
104
126
  rubyforge_project:
105
127
  rubygems_version: 1.8.25