durable_call 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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