resque-retry 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.0.6 (2010-07-11)
2
+
3
+ * Feature: Added support for custom retry criteria check callbacks.
4
+
1
5
  ## 0.0.5 (2010-06-27)
2
6
 
3
7
  * Handle our own dependancies.
data/README.md CHANGED
@@ -105,6 +105,36 @@ it so only specific exceptions are retried using `retry_exceptions`:
105
105
  The above modification will **only** retry if a `NetworkError` (or subclass)
106
106
  exception is thrown.
107
107
 
108
+ ### Custom Retry Criteria Check Callbacks
109
+
110
+ You may define custom retry criteria callbacks:
111
+
112
+ class TurkWorker
113
+ extend Resque::Plugins::Retry
114
+ @queue = :turk_job_processor
115
+
116
+ @retry_exceptions = [NetworkError]
117
+
118
+ retry_criteria_check do |exception, *args|
119
+ if exception.message =~ /InvalidJobId/
120
+ false # don't retry if we got passed a invalid job id.
121
+ else
122
+ true # its okay for a retry attempt to continue.
123
+ end
124
+ end
125
+
126
+ def self.perform(job_id)
127
+ heavy_lifting
128
+ end
129
+ end
130
+
131
+ Similar to the previous example, this job will retry if either a
132
+ `NetworkError` (or subclass) exception is thrown **or** any of the callbacks
133
+ return true.
134
+
135
+ Use `@retry_exceptions = []` to **only** use callbacks, to determine if the
136
+ job should retry.
137
+
108
138
  Customise & Extend
109
139
  ------------------
110
140
 
@@ -1,7 +1,6 @@
1
1
  module Resque
2
2
  module Plugins
3
3
 
4
- ##
5
4
  # If you want your job to retry on failure using a varying delay, simply
6
5
  # extend your module/class with this module:
7
6
  #
@@ -38,7 +37,6 @@ module Resque
38
37
  module ExponentialBackoff
39
38
  include Resque::Plugins::Retry
40
39
 
41
- ##
42
40
  # Defaults to the number of delays in the backoff strategy.
43
41
  #
44
42
  # @return [Number] maximum number of retries
@@ -46,7 +44,6 @@ module Resque
46
44
  @retry_limit ||= backoff_strategy.length
47
45
  end
48
46
 
49
- ##
50
47
  # Selects the delay from the backoff strategy.
51
48
  #
52
49
  # @return [Number] seconds to delay until the next retry.
@@ -54,7 +51,6 @@ module Resque
54
51
  backoff_strategy[retry_attempt] || backoff_strategy.last
55
52
  end
56
53
 
57
- ##
58
54
  # @abstract
59
55
  # The backoff strategy is used to vary the delay between retry attempts.
60
56
  #
@@ -1,7 +1,6 @@
1
1
  module Resque
2
2
  module Plugins
3
3
 
4
- ##
5
4
  # If you want your job to retry on failure, simply extend your module/class
6
5
  # with this module:
7
6
  #
@@ -34,7 +33,13 @@ module Resque
34
33
  # end
35
34
  #
36
35
  module Retry
37
- ##
36
+
37
+ # Copy retry criteria checks on inheritance.
38
+ def inherited(subclass)
39
+ super(subclass)
40
+ subclass.instance_variable_set("@retry_criteria_checks", retry_criteria_checks.dup)
41
+ end
42
+
38
43
  # @abstract You may override to implement a custom identifier,
39
44
  # you should consider doing this if your job arguments
40
45
  # are many/long or may not cleanly cleanly to strings.
@@ -49,7 +54,6 @@ module Resque
49
54
  args_string.empty? ? nil : args_string
50
55
  end
51
56
 
52
- ##
53
57
  # Builds the redis key to be used for keeping state of the job
54
58
  # attempts.
55
59
  #
@@ -58,7 +62,6 @@ module Resque
58
62
  ['resque-retry', name, identifier(*args)].compact.join(":").gsub(/\s/, '')
59
63
  end
60
64
 
61
- ##
62
65
  # Maximum number of retrys we can attempt to successfully perform the job.
63
66
  # A retry limit of 0 or below will retry forever.
64
67
  #
@@ -67,7 +70,6 @@ module Resque
67
70
  @retry_limit ||= 1
68
71
  end
69
72
 
70
- ##
71
73
  # Number of retry attempts used to try and perform the job.
72
74
  #
73
75
  # The real value is kept in Redis, it is accessed and incremented using
@@ -78,7 +80,6 @@ module Resque
78
80
  @retry_attempt ||= 0
79
81
  end
80
82
 
81
- ##
82
83
  # @abstract
83
84
  # Number of seconds to delay until the job is retried.
84
85
  #
@@ -87,7 +88,6 @@ module Resque
87
88
  @retry_delay ||= 0
88
89
  end
89
90
 
90
- ##
91
91
  # @abstract
92
92
  # Modify the arguments used to retry the job. Use this to do something
93
93
  # other than try the exact same job again.
@@ -97,7 +97,6 @@ module Resque
97
97
  args
98
98
  end
99
99
 
100
- ##
101
100
  # Convenience method to test whether you may retry on a given exception.
102
101
  #
103
102
  # @return [Boolean]
@@ -106,7 +105,6 @@ module Resque
106
105
  !! retry_exceptions.any? { |ex| ex >= exception }
107
106
  end
108
107
 
109
- ##
110
108
  # @abstract
111
109
  # Controls what exceptions may be retried.
112
110
  #
@@ -117,21 +115,68 @@ module Resque
117
115
  @retry_exceptions ||= nil
118
116
  end
119
117
 
120
- ##
121
118
  # Test if the retry criteria is valid.
122
119
  #
123
120
  # @param [Exception] exception
124
121
  # @param [Array] args job arguments
125
122
  # @return [Boolean]
126
123
  def retry_criteria_valid?(exception, *args)
127
- # FIXME: let people extend retry criteria, give them a chance to say no.
124
+ # if the retry limit was reached, dont bother checking anything else.
125
+ return false if retry_limit_reached?
126
+
127
+ # We always want to retry if the exception matches.
128
+ should_retry = retry_exception?(exception.class)
129
+
130
+ # call user retry criteria check blocks.
131
+ retry_criteria_checks.each do |criteria_check|
132
+ should_retry ||= !!criteria_check.call(exception, *args)
133
+ end
134
+
135
+ should_retry
136
+ end
137
+
138
+ # Retry criteria checks.
139
+ #
140
+ # @return [Array]
141
+ def retry_criteria_checks
142
+ @retry_criteria_checks ||= []
143
+ @retry_criteria_checks
144
+ end
145
+
146
+ # Test if the retry limit has been reached.
147
+ #
148
+ # @return [Boolean]
149
+ def retry_limit_reached?
128
150
  if retry_limit > 0
129
- return false if retry_attempt >= retry_limit
151
+ return true if retry_attempt >= retry_limit
130
152
  end
131
- retry_exception?(exception.class)
153
+ false
154
+ end
155
+
156
+ # Register a retry criteria check callback to be run before retrying
157
+ # the job again.
158
+ #
159
+ # If any callback returns `true`, the job will be retried.
160
+ #
161
+ # @example Using a custom retry criteria check.
162
+ #
163
+ # retry_criteria_check do |exception, *args|
164
+ # if exception.message =~ /InvalidJobId/
165
+ # # don't retry if we got passed a invalid job id.
166
+ # false
167
+ # else
168
+ # true
169
+ # end
170
+ # end
171
+ #
172
+ # @yield [exception, *args]
173
+ # @yieldparam exception [Exception] the exception that was raised
174
+ # @yieldparam args [Array] job arguments
175
+ # @yieldreturn [Boolean] false == dont retry, true = can retry
176
+ def retry_criteria_check(&block)
177
+ retry_criteria_checks << block
132
178
  end
133
179
 
134
- ##
135
180
  # Will retry the job.
136
181
  def try_again(*args)
137
182
  if retry_delay <= 0
@@ -142,7 +187,6 @@ module Resque
142
187
  end
143
188
  end
144
189
 
145
- ##
146
190
  # Resque before_perform hook.
147
191
  #
148
192
  # Increments and sets the `@retry_attempt` count.
@@ -152,7 +196,6 @@ module Resque
152
196
  @retry_attempt = Resque.redis.incr(retry_key) # increment by 1.
153
197
  end
154
198
 
155
- ##
156
199
  # Resque after_perform hook.
157
200
  #
158
201
  # Deletes retry attempt count from Redis.
@@ -160,7 +203,6 @@ module Resque
160
203
  Resque.redis.del(redis_retry_key(*args))
161
204
  end
162
205
 
163
- ##
164
206
  # Resque on_failure hook.
165
207
  #
166
208
  # Checks if our retry criteria is valid, if it is we try again.
@@ -172,7 +214,7 @@ module Resque
172
214
  Resque.redis.del(redis_retry_key(*args))
173
215
  end
174
216
  end
175
- end
176
217
 
218
+ end
177
219
  end
178
- end
220
+ end
@@ -38,22 +38,22 @@ class ExponentialBackoffTest < Test::Unit::TestCase
38
38
  assert_equal now.to_i + 10_800, delayed[3], '5th delay'
39
39
  assert_equal now.to_i + 21_600, delayed[4], '6th delay'
40
40
  end
41
-
41
+
42
42
  def test_custom_backoff_strategy
43
43
  now = Time.now
44
44
  4.times do
45
45
  Resque.enqueue(CustomExponentialBackoffJob, 'http://lividpenguin.com', 1305, 'cd8079192d379dc612f17c660591a6cfb05f1dda')
46
46
  perform_next_job @worker
47
47
  end
48
-
48
+
49
49
  delayed = Resque.delayed_queue_peek(0, 3)
50
50
  assert_equal now.to_i + 10, delayed[0], '1st delay'
51
51
  assert_equal now.to_i + 20, delayed[1], '2nd delay'
52
52
  assert_equal now.to_i + 30, delayed[2], '3rd delay'
53
53
  assert_equal 2, Resque.delayed_timestamp_size(delayed[2]), '4th delay should share delay with 3rd'
54
-
54
+
55
55
  assert_equal 4, Resque.info[:processed], 'processed jobs'
56
56
  assert_equal 4, Resque.info[:failed], 'failed jobs'
57
57
  assert_equal 0, Resque.info[:pending], 'pending jobs'
58
58
  end
59
- end
59
+ end
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RetryCriteriaTest < Test::Unit::TestCase
4
+ def setup
5
+ Resque.redis.flushall
6
+ @worker = Resque::Worker.new(:testing)
7
+ @worker.register_worker
8
+ end
9
+
10
+ def test_retry_criteria_check_should_retry
11
+ Resque.enqueue(RetryModuleCustomRetryCriteriaCheck)
12
+ 3.times do
13
+ perform_next_job(@worker)
14
+ end
15
+
16
+ assert_equal 0, Resque.info[:pending], 'pending jobs'
17
+ assert_equal 2, Resque.info[:failed], 'failed jobs'
18
+ assert_equal 2, Resque.info[:processed], 'processed job'
19
+ end
20
+
21
+ def test_retry_criteria_check_hierarchy_should_not_retry
22
+ Resque.enqueue(CustomRetryCriteriaCheckDontRetry)
23
+ 3.times do
24
+ perform_next_job(@worker)
25
+ end
26
+
27
+ assert_equal 0, Resque.info[:pending], 'pending jobs'
28
+ assert_equal 1, Resque.info[:failed], 'failed jobs'
29
+ assert_equal 1, Resque.info[:processed], 'processed job'
30
+ end
31
+
32
+ def test_retry_criteria_check_hierarchy_should_retry
33
+ Resque.enqueue(CustomRetryCriteriaCheckDoRetry)
34
+ 3.times do
35
+ perform_next_job(@worker)
36
+ end
37
+
38
+ assert_equal 0, Resque.info[:pending], 'pending jobs'
39
+ assert_equal 2, Resque.info[:failed], 'failed jobs'
40
+ assert_equal 2, Resque.info[:processed], 'processed job'
41
+ end
42
+
43
+ def test_retry_criteria_check_multiple_never_retry
44
+ Resque.enqueue(CustomRetryCriteriaCheckMultipleFailTwice, 'dont')
45
+ 6.times do
46
+ perform_next_job(@worker)
47
+ end
48
+
49
+ assert_equal 0, Resque.info[:pending], 'pending jobs'
50
+ assert_equal 1, Resque.info[:failed], 'failed jobs'
51
+ assert_equal 1, Resque.info[:processed], 'processed job'
52
+ end
53
+
54
+ def test_retry_criteria_check_multiple_do_retry
55
+ Resque.enqueue(CustomRetryCriteriaCheckMultipleFailTwice, 'do')
56
+ 6.times do
57
+ perform_next_job(@worker)
58
+ end
59
+
60
+ assert_equal 0, Resque.info[:pending], 'pending jobs'
61
+ assert_equal 2, Resque.info[:failed], 'failed jobs'
62
+ assert_equal 3, Resque.info[:processed], 'processed job'
63
+ end
64
+
65
+ def test_retry_criteria_check_multiple_do_retry_again
66
+ Resque.enqueue(CustomRetryCriteriaCheckMultipleFailTwice, 'do_again')
67
+ 6.times do
68
+ perform_next_job(@worker)
69
+ end
70
+
71
+ assert_equal 0, Resque.info[:pending], 'pending jobs'
72
+ assert_equal 2, Resque.info[:failed], 'failed jobs'
73
+ assert_equal 3, Resque.info[:processed], 'processed job'
74
+ end
75
+ end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RetryInheritingChecksTest < Test::Unit::TestCase
4
+ def setup
5
+ Resque.redis.flushall
6
+ @worker = Resque::Worker.new(:testing)
7
+ @worker.register_worker
8
+ end
9
+
10
+ def test_default_job_has_one_exception
11
+ assert_equal 0, RetryDefaultsJob.retry_criteria_checks.size
12
+ end
13
+
14
+ def test_inheriting_copies_exceptions
15
+ assert_equal RetryDefaultsJob.retry_criteria_checks, InheritTestJob.retry_criteria_checks
16
+ end
17
+
18
+ def test_inheriting_adds_exceptions
19
+ assert_equal 1, InheritTestWithExtraJob.retry_criteria_checks.size
20
+ end
21
+
22
+ def test_extending_with_resque_retry_doesnt_override_previously_defined_inherited_hook
23
+ klass = InheritOrderingJobExtendLastSubclass
24
+ assert_equal 1, klass.retry_criteria_checks.size
25
+ assert_equal 'test', klass.test_value
26
+ end
27
+
28
+ def test_extending_with_resque_retry_then_defining_inherited_does_not_override_previous_hook
29
+ klass = InheritOrderingJobExtendFirstSubclass
30
+ assert_equal 1, klass.retry_criteria_checks.size
31
+ assert_equal 'test', klass.test_value
32
+ end
33
+ end
data/test/retry_test.rb CHANGED
@@ -31,6 +31,17 @@ class RetryTest < Test::Unit::TestCase
31
31
  assert_equal 2, Resque.info[:processed], 'processed job'
32
32
  end
33
33
 
34
+ def test_module_retry_defaults
35
+ Resque.enqueue(RetryModuleDefaultsJob)
36
+ 3.times do
37
+ perform_next_job(@worker)
38
+ end
39
+
40
+ assert_equal 0, Resque.info[:pending], 'pending jobs'
41
+ assert_equal 2, Resque.info[:failed], 'failed jobs'
42
+ assert_equal 2, Resque.info[:processed], 'processed job'
43
+ end
44
+
34
45
  def test_job_args_are_maintained
35
46
  test_args = ['maiow', 'cat', [42, 84]]
36
47
 
@@ -142,4 +153,5 @@ class RetryTest < Test::Unit::TestCase
142
153
  def test_redis_retry_key_removes_whitespace
143
154
  assert_equal 'resque-retry:GoodJob:arg1-removespace', GoodJob.redis_retry_key('arg1', 'remove space')
144
155
  end
156
+
145
157
  end
data/test/test_helper.rb CHANGED
@@ -10,7 +10,6 @@ require 'resque-retry'
10
10
  require dir + '/test_jobs'
11
11
 
12
12
 
13
- ##
14
13
  # make sure we can run redis
15
14
  if !system("which redis-server")
16
15
  puts '', "** can't find `redis-server` in your path"
@@ -19,7 +18,6 @@ if !system("which redis-server")
19
18
  end
20
19
 
21
20
 
22
- ##
23
21
  # start our own redis when the tests start,
24
22
  # kill it when they end
25
23
  at_exit do
@@ -42,7 +40,6 @@ puts "Starting redis for testing at localhost:9736..."
42
40
  `redis-server #{dir}/redis-test.conf`
43
41
  Resque.redis = '127.0.0.1:9736'
44
42
 
45
- ##
46
43
  # Test helpers
47
44
  class Test::Unit::TestCase
48
45
  def perform_next_job(worker, &block)
data/test/test_jobs.rb CHANGED
@@ -18,6 +18,25 @@ class RetryDefaultsJob
18
18
  end
19
19
  end
20
20
 
21
+ class InheritTestJob < RetryDefaultsJob
22
+ end
23
+
24
+ class InheritTestWithExtraJob < InheritTestJob
25
+ retry_criteria_check do |exception, *args|
26
+ false
27
+ end
28
+ end
29
+
30
+ class InheritTestWithMoreExtraJob < InheritTestWithExtraJob
31
+ retry_criteria_check do |exception, *args|
32
+ false
33
+ end
34
+
35
+ retry_criteria_check do |exception, *args|
36
+ false
37
+ end
38
+ end
39
+
21
40
  class RetryWithModifiedArgsJob < RetryDefaultsJob
22
41
  @queue = :testing
23
42
 
@@ -48,10 +67,10 @@ end
48
67
  class CustomExponentialBackoffJob
49
68
  extend Resque::Plugins::ExponentialBackoff
50
69
  @queue = :testing
51
-
70
+
52
71
  @retry_limit = 4
53
72
  @backoff_strategy = [10, 20, 30]
54
-
73
+
55
74
  def self.perform(url, hook_id, hmac_key)
56
75
  raise
57
76
  end
@@ -71,4 +90,119 @@ class RetryCustomExceptionsJob < RetryDefaultsJob
71
90
  else raise StandardError
72
91
  end
73
92
  end
74
- end
93
+ end
94
+
95
+ module RetryModuleDefaultsJob
96
+ extend Resque::Plugins::Retry
97
+ @queue = :testing
98
+
99
+ def self.perform(*args)
100
+ raise
101
+ end
102
+ end
103
+
104
+ module RetryModuleCustomRetryCriteriaCheck
105
+ extend Resque::Plugins::Retry
106
+ @queue = :testing
107
+
108
+ # make sure the retry exceptions check will return false.
109
+ @retry_exceptions = [CustomException]
110
+
111
+ retry_criteria_check do |exception, *args|
112
+ true
113
+ end
114
+
115
+ def self.perform(*args)
116
+ raise
117
+ end
118
+ end
119
+
120
+ class CustomRetryCriteriaCheckDontRetry < RetryDefaultsJob
121
+ @queue = :testing
122
+
123
+ # make sure the retry exceptions check will return false.
124
+ @retry_exceptions = [CustomException]
125
+
126
+ retry_criteria_check do |exception, *args|
127
+ false
128
+ end
129
+ end
130
+
131
+ class CustomRetryCriteriaCheckDoRetry < CustomRetryCriteriaCheckDontRetry
132
+ @queue = :testing
133
+
134
+ # make sure the retry exceptions check will return false.
135
+ @retry_exceptions = [CustomException]
136
+
137
+ retry_criteria_check do |exception, *args|
138
+ true
139
+ end
140
+ end
141
+
142
+ # A job using multiple custom retry criteria checks.
143
+ # It always fails 2 times.
144
+ class CustomRetryCriteriaCheckMultipleFailTwice
145
+ extend Resque::Plugins::Retry
146
+ @retry_limit = 6
147
+ @queue = :testing
148
+
149
+ # make sure we dont retry due to default exception behaviour.
150
+ @retry_exceptions = []
151
+
152
+ retry_criteria_check do |exception, *args|
153
+ exception.message == 'dont' ? false : true
154
+ end
155
+
156
+ retry_criteria_check do |exception, *args|
157
+ exception.message == 'do' ? true : false
158
+ end
159
+
160
+ retry_criteria_check do |exception, *args|
161
+ exception.message == 'do_again' ? true : false
162
+ end
163
+
164
+ def self.perform(msg)
165
+ if retry_attempt < 2 # always fail twice please.
166
+ raise StandardError, msg
167
+ end
168
+ end
169
+ end
170
+
171
+ # A job to test whether self.inherited is respected
172
+ # when added by other modules.
173
+ class InheritOrderingJobExtendFirst
174
+ extend Resque::Plugins::Retry
175
+
176
+ retry_criteria_check do |exception, *args|
177
+ false
178
+ end
179
+
180
+ class << self
181
+ attr_accessor :test_value
182
+ end
183
+
184
+ def self.inherited(subclass)
185
+ super(subclass)
186
+ subclass.test_value = 'test'
187
+ end
188
+ end
189
+
190
+ class InheritOrderingJobExtendLast
191
+ class << self
192
+ attr_accessor :test_value
193
+ end
194
+
195
+ def self.inherited(subclass)
196
+ super(subclass)
197
+ subclass.test_value = 'test'
198
+ end
199
+
200
+ extend Resque::Plugins::Retry
201
+
202
+ retry_criteria_check do |exception, *args|
203
+ false
204
+ end
205
+ end
206
+
207
+ class InheritOrderingJobExtendFirstSubclass < InheritOrderingJobExtendFirst; end
208
+ class InheritOrderingJobExtendLastSubclass < InheritOrderingJobExtendLast; end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 5
9
- version: 0.0.5
8
+ - 6
9
+ version: 0.0.6
10
10
  platform: ruby
11
11
  authors:
12
12
  - Luke Antins
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-06-27 00:00:00 +01:00
18
+ date: 2010-07-12 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -86,6 +86,8 @@ files:
86
86
  - test/exponential_backoff_test.rb
87
87
  - test/redis-test.conf
88
88
  - test/resque_test.rb
89
+ - test/retry_criteria_test.rb
90
+ - test/retry_inheriting_checks_test.rb
89
91
  - test/retry_test.rb
90
92
  - test/test_helper.rb
91
93
  - test/test_jobs.rb