resque-retry 0.0.5 → 0.0.6

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/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