resque-retry 1.5.2 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d298842f059d9cd8ee69821e345e60d8a792e68f8f7b9fb2041508cd57e540c3
4
- data.tar.gz: 203fc1782c1bdcf08858075f110e34421d1e5b805869514a36b3122739f2487f
3
+ metadata.gz: 6aa034bb302b1946fcc3900ef412f35ab3d56aa18718304b6ec91ed997020d29
4
+ data.tar.gz: ead7a803c822ec57b55994621f13fa8a788b17743e1f682eb72b361b0f59eadc
5
5
  SHA512:
6
- metadata.gz: 69bf34cb35ac34bf9975f4f2798dcb78dec39cfd0d373add776026aeaf5dbb594b70bfe2c0f8352701138a61095274413c3087dc14e251b96b117eea57c56207
7
- data.tar.gz: b6602597785e8d13e1bb33883e852aece52d4b1e7ffc14f82843e42042c4219bfaa5cfeeec7a9a94176652b3bc59664535c218860127e7591ccd2f113ee80f83
6
+ metadata.gz: a71f62fe40bd8cda477a7a17071e738b16425b8b97f7122dec69eb28808167ad82ac148b58d25824b0dc554377e490c4ca7aa92df652913fb9504bf1a69054ef
7
+ data.tar.gz: f087615ff9a87816f9bda22ac9258f70aad0d564e54afe9342796ca5f59b0cee40c8b8be6cc905617987e58682d33496465bf611d6caf8a30d38ab52c322cff5
data/HISTORY.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 1.5.3 (2018-11-26)
2
+
3
+ * Monkey patch in the `requeue_queue` method onto the `Resque::Failure` backend (this _should_ be a temporary fix)
4
+
1
5
  # 1.5.2 (2018-07-16)
2
6
 
3
7
  * Remove build support for `ruby < 2.3`, `jruby < 9.1` and `rbx`
data/README.md CHANGED
@@ -224,7 +224,48 @@ end
224
224
  The above modification will allow your job to retry up to 10 times, with a delay
225
225
  of 120 seconds, or 2 minutes between retry attempts.
226
226
 
227
- You can override the `retry_delay` method to set the delay value dynamically.
227
+ You can override the `retry_delay` method to set the delay value dynamically. For example:
228
+
229
+ ```ruby
230
+ class ExampleJob
231
+ extend Resque::Plugins::Retry
232
+ @queue = :testing
233
+
234
+ def self.retry_delay(exception)
235
+ if exception == SocketError
236
+ 10
237
+ else
238
+ 1
239
+ end
240
+ end
241
+
242
+ def self.perform(*args)
243
+ heavy_lifting
244
+ end
245
+ end
246
+ ```
247
+
248
+ Or, if you'd like the delay to be dependent on job arguments:
249
+
250
+ ```ruby
251
+ class ExampleJob
252
+ extend Resque::Plugins::Retry
253
+ @queue = :testing
254
+
255
+ def self.retry_delay(exception, *args)
256
+ # the delay is dependent on the arguments passed to the job
257
+ # in this case, "3" is passed as the arg and that is used as the delay
258
+ # make sure this method returns a integer
259
+ args.first.to_i
260
+ end
261
+
262
+ def self.perform(*args)
263
+ heavy_lifting
264
+ end
265
+ end
266
+
267
+ Resque.enqueue(ExampleJob, '3')
268
+ ```
228
269
 
229
270
  ### <a name="sleep"></a> Sleep After Requeuing
230
271
 
@@ -457,6 +498,47 @@ class DeliverViaSMSC
457
498
  end
458
499
  end
459
500
  ```
501
+
502
+ ### Custom Retry Queues
503
+
504
+ By default, when a job is retried, it is added to the `@queue` specified in the worker. However, you may want to push the job into another (lower or higher priority) queue when the job fails. You can do this by dynamically specifying the retry queue. For example:
505
+
506
+ ```ruby
507
+ class ExampleJob
508
+ extend Resque::Plugins::Retry
509
+ @queue = :testing
510
+ @retry_delay = 1
511
+
512
+ def self.work(*args)
513
+ user_id, user_mode, record_id = *args
514
+
515
+ Resque.enqueue_to(
516
+ target_queue_for_args(user_id, user_mode, record_id),
517
+ self,
518
+ *args
519
+ )
520
+ end
521
+
522
+ def self.retry_queue(exception, *args)
523
+ target_queue_for_args(*args)
524
+ end
525
+
526
+ def self.perform(*args)
527
+ heavy_lifting
528
+ end
529
+
530
+ def self.target_queue_for_args(*args)
531
+ user_id, user_mode, record_id = *args
532
+
533
+ if user_mode
534
+ 'high
535
+ else
536
+ 'low'
537
+ end
538
+ end
539
+ end
540
+ ```
541
+
460
542
  ### <a name="retry_key"></a> Job Retry Identifier/Key
461
543
 
462
544
  The retry attempt is incremented and stored in a Redis key. The key is built
@@ -594,6 +676,18 @@ Reminder: `@ignore_exceptions` should be a subset of `@retry_exceptions`.
594
676
  The inner-workings of the plugin are output to the Resque [Logger](https://github.com/resque/resque/wiki/Logging)
595
677
  when `Resque.logger.level` is set to `Logger::DEBUG`.
596
678
 
679
+ Add `VVERBOSE=true` as an environment variable to easily set the log level to debug.
680
+
681
+ ### Testing
682
+
683
+ To run a specific test and inspect logging output
684
+
685
+ ```
686
+ bundle exec rake TEST=the_test_file.rb VVERBOSE=true
687
+ ```
688
+
689
+ There are many example jobs implementing various use-cases for this gem in `test_jobs.rb`
690
+
597
691
  Contributing/Pull Requests
598
692
  --------------------------
599
693
 
@@ -1,3 +1,3 @@
1
1
  module ResqueRetry
2
- VERSION = '1.5.2'
2
+ VERSION = '1.5.3'
3
3
  end
@@ -3,10 +3,9 @@ require 'resque/plugins/retry/logging'
3
3
 
4
4
  module Resque
5
5
  module Failure
6
-
7
6
  # A multiple failure backend, with retry suppression
8
7
  #
9
- # For example: if you had a job that could retry 5 times, your failure
8
+ # For example: if you had a job that could retry 5 times, your failure
10
9
  # backends are not notified unless the _final_ retry attempt also fails.
11
10
  #
12
11
  # Example:
@@ -34,11 +33,21 @@ module Resque
34
33
  job_being_retried = retryable && retrying?
35
34
 
36
35
  if !job_being_retried
37
- log_message "#{retryable ? '' : 'non-'}retriable job is not being retried - sending failure to superclass", args_from(payload), exception
36
+ log_message(
37
+ "#{retryable ? '' : 'non-'}retryable job is not being retried - sending failure to superclass",
38
+ args_from(payload),
39
+ exception
40
+ )
41
+
38
42
  cleanup_retry_failure_log!
39
43
  super
40
44
  elsif retry_delay > 0
41
- log_message "retry_delay: #{retry_delay} > 0 - saving details in Redis", args_from(payload), exception
45
+ log_message(
46
+ "retry_delay: #{retry_delay} > 0 - saving details in Redis",
47
+ args_from(payload),
48
+ exception
49
+ )
50
+
42
51
  data = {
43
52
  :failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S"),
44
53
  :payload => payload,
@@ -48,10 +57,19 @@ module Resque
48
57
  :worker => worker.to_s,
49
58
  :queue => queue
50
59
  }
60
+ data = Resque.encode(data)
51
61
 
52
- Resque.redis.setex(failure_key, 2*retry_delay, Resque.encode(data))
62
+ Resque.redis.setex(
63
+ failure_key,
64
+ 2 * retry_delay,
65
+ data
66
+ )
53
67
  else
54
- log_message "retry_delay: #{retry_delay} <= 0 - ignoring", args_from(payload), exception
68
+ log_message(
69
+ "retry_delay: #{retry_delay} <= 0 - ignoring",
70
+ args_from(payload),
71
+ exception
72
+ )
55
73
  end
56
74
  end
57
75
 
@@ -62,9 +80,33 @@ module Resque
62
80
  'failure-' + retry_key
63
81
  end
64
82
 
83
+ # Monkey-patch this in for now since it is a hard requirement for the
84
+ # "retry-all" functionality to work via the Web UI.
85
+ #
86
+ # This can be removed when the following PR has been merged into `Resque`
87
+ # itself:
88
+ #
89
+ # https://github.com/resque/resque/pull/1659
90
+ #
91
+ # @api public
92
+ def self.requeue_queue(queue)
93
+ classes.first.requeue_queue(queue)
94
+ end
95
+
65
96
  protected
66
97
 
67
- # Return the class/module of the failed job.
98
+ def args_from(payload)
99
+ (payload || {})['args']
100
+ end
101
+
102
+ def cleanup_retry_failure_log!
103
+ Resque.redis.del(failure_key) if retryable?
104
+ end
105
+
106
+ def failure_key
107
+ self.class.failure_key(retry_key)
108
+ end
109
+
68
110
  def klass
69
111
  Resque::Job.new(nil, nil).constantize(payload['class'])
70
112
  end
@@ -77,10 +119,6 @@ module Resque
77
119
  klass.redis_retry_key(*payload['args'])
78
120
  end
79
121
 
80
- def failure_key
81
- self.class.failure_key(retry_key)
82
- end
83
-
84
122
  def retryable?
85
123
  klass.respond_to?(:redis_retry_key)
86
124
  rescue NameError
@@ -90,14 +128,6 @@ module Resque
90
128
  def retrying?
91
129
  Resque.redis.exists(retry_key)
92
130
  end
93
-
94
- def cleanup_retry_failure_log!
95
- Resque.redis.del(failure_key) if retryable?
96
- end
97
-
98
- def args_from(payload)
99
- (payload || {})['args']
100
- end
101
131
  end
102
132
  end
103
133
  end
@@ -54,23 +54,10 @@ module Resque
54
54
  def self.extended(receiver)
55
55
  retry_exceptions = receiver.instance_variable_get('@retry_exceptions')
56
56
  fatal_exceptions = receiver.instance_variable_get('@fatal_exceptions')
57
- ignore_exceptions = receiver.instance_variable_get('@ignore_exceptions')
58
57
 
59
58
  if fatal_exceptions && retry_exceptions
60
59
  raise AmbiguousRetryStrategyException.new(%{You can't define both "@fatal_exceptions" and "@retry_exceptions"})
61
60
  end
62
-
63
- # Check that ignore_exceptions is a subset of retry_exceptions
64
- if retry_exceptions.is_a?(Hash)
65
- exceptions = retry_exceptions.keys
66
- else
67
- exceptions = Array(retry_exceptions)
68
- end
69
-
70
- excess_exceptions = Array(ignore_exceptions) - exceptions
71
- unless excess_exceptions.empty?
72
- raise RetryConfigurationException, "The following exceptions are defined in @ignore_exceptions but not in @retry_exceptions: #{excess_exceptions.join(', ')}."
73
- end
74
61
  end
75
62
 
76
63
  # Copy retry criteria checks, try again callbacks, and give up callbacks
@@ -176,6 +163,16 @@ module Resque
176
163
  @retry_job_delegate ||= nil
177
164
  end
178
165
 
166
+ # @abstract
167
+ # Specify the queue that the job should be placed in upon failure
168
+ #
169
+ # @return [Symbol] Symbol representing queue that job should be placed in
170
+ #
171
+ # @api public
172
+ def retry_queue(exception, *args)
173
+ nil
174
+ end
175
+
179
176
  # @abstract
180
177
  # Modify the arguments used to retry the job. Use this to do something
181
178
  # other than try the exact same job again
@@ -388,10 +385,23 @@ module Resque
388
385
 
389
386
  # some plugins define retry_delay and have it take no arguments, so rather than break those,
390
387
  # we'll just check here to see whether it takes the additional exception class argument or not
391
- temp_retry_delay = ([-1, 1].include?(method(:retry_delay).arity) ? retry_delay(exception.class) : retry_delay)
388
+ # we also allow all job args to be passed to a custom `retry_delay` method
389
+ retry_delay_arity = method(:retry_delay).arity
390
+
391
+ temp_retry_delay = if [-2, 2].include?(retry_delay_arity)
392
+ retry_delay(exception.class, *args)
393
+ elsif [-1, 1].include?(retry_delay_arity)
394
+ retry_delay(exception.class)
395
+ else
396
+ retry_delay
397
+ end
398
+
399
+ retry_job_class = retry_job_delegate ? retry_job_delegate : self
400
+
401
+ retry_in_queue = retry_queue(exception, *args)
402
+ retry_in_queue ||= Resque.queue_from_class(retry_job_class)
392
403
 
393
- retry_in_queue = retry_job_delegate ? retry_job_delegate : self
394
- log_message "retry delay: #{temp_retry_delay} for class: #{retry_in_queue}", args, exception
404
+ log_message "retry delay: #{temp_retry_delay} for queue: #{retry_in_queue}", args, exception
395
405
 
396
406
  # remember that this job is now being retried. before_perform_retry will increment
397
407
  # this so it represents the retry count, and MultipleWithRetrySuppression uses
@@ -404,9 +414,9 @@ module Resque
404
414
 
405
415
  if temp_retry_delay <= 0
406
416
  # If the delay is 0, no point passing it through the scheduler
407
- Resque.enqueue(retry_in_queue, *retry_args)
417
+ Resque.enqueue_to(retry_in_queue, retry_job_class, *retry_args)
408
418
  else
409
- Resque.enqueue_in(temp_retry_delay, retry_in_queue, *retry_args)
419
+ Resque.enqueue_in_with_queue(retry_in_queue, temp_retry_delay, retry_job_class, *retry_args)
410
420
  end
411
421
 
412
422
  # remove retry key from redis if we handed retry off to another queue.
@@ -23,16 +23,4 @@ class IgnoreExceptionsTest < Minitest::Test
23
23
  perform_next_job(@worker)
24
24
  assert_equal '1', Resque.redis.get(retry_key), 'retry counter'
25
25
  end
26
-
27
- def test_ignore_exception_configuration_1
28
- assert_raises Resque::Plugins::Retry::RetryConfigurationException do
29
- IgnoreExceptionsImproperlyConfiguredJob1.extend(Resque::Plugins::Retry)
30
- end
31
- end
32
-
33
- def test_ignore_exception_configuration_2
34
- assert_raises Resque::Plugins::Retry::RetryConfigurationException do
35
- IgnoreExceptionsImproperlyConfiguredJob2.extend(Resque::Plugins::Retry)
36
- end
37
- end
38
26
  end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ class RetryCustomDelayTest < Minitest::Test
4
+ def setup
5
+ Resque.redis.flushall
6
+ @worker = Resque::Worker.new(:testing)
7
+ @worker.register_worker
8
+ end
9
+
10
+ def test_delay_with_exception
11
+ Resque.enqueue(DynamicDelayedJobOnException, 'arg1')
12
+ Resque.expects(:enqueue_in_with_queue).with(:testing, 4, DynamicDelayedJobOnException, 'arg1')
13
+
14
+ perform_next_job(@worker)
15
+ end
16
+
17
+ def test_delay_with_exception_and_args
18
+ Resque.enqueue(DynamicDelayedJobOnExceptionAndArgs, '3')
19
+ Resque.expects(:enqueue_in_with_queue).with(:testing, 3, DynamicDelayedJobOnExceptionAndArgs, '3')
20
+
21
+ perform_next_job(@worker)
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class RetryQueueTest < Minitest::Test
4
+ def setup
5
+ Resque.redis.flushall
6
+ @worker = Resque::Worker.new(:testing)
7
+ @worker.register_worker
8
+ end
9
+
10
+ def test_retry_delayed_failed_jobs_in_separate_queue
11
+ Resque.enqueue(DelayedJobWithRetryQueue, 'arg1')
12
+ Resque.expects(:enqueue_in_with_queue).with(:testing_retry_delegate, 1, JobRetryQueue, 'arg1')
13
+
14
+ perform_next_job(@worker)
15
+ end
16
+
17
+ def test_retry_delayed_failed_jobs_in_dynamic_queue
18
+ queue_name = "dynamic_queue_#{Time.now.to_i}"
19
+
20
+ Resque.enqueue(JobWithDynamicRetryQueue, queue_name)
21
+ Resque.expects(:enqueue_in_with_queue).with(queue_name, 1, JobWithDynamicRetryQueue, queue_name)
22
+
23
+ perform_next_job(@worker)
24
+ end
25
+ end
@@ -221,7 +221,7 @@ class RetryTest < Minitest::Test
221
221
 
222
222
  perform_next_job(@worker)
223
223
 
224
- assert job_from_retry_queue = Resque.pop(:testing_retry)
224
+ assert job_from_retry_queue = Resque.pop(:testing_retry_delegate)
225
225
  assert_equal ['arg1'], job_from_retry_queue['args']
226
226
  assert_nil Resque.redis.get(JobWithRetryQueue.redis_retry_key('arg1'))
227
227
  end
@@ -234,13 +234,6 @@ class RetryTest < Minitest::Test
234
234
  perform_next_job(@worker)
235
235
  end
236
236
 
237
- def test_retry_delayed_failed_jobs_in_separate_queue
238
- Resque.enqueue(DelayedJobWithRetryQueue, 'arg1')
239
- Resque.expects(:enqueue_in).with(1, JobRetryQueue, 'arg1')
240
-
241
- perform_next_job(@worker)
242
- end
243
-
244
237
  def test_delete_redis_key_when_job_is_successful
245
238
  Resque.enqueue(GoodJob, 'arg1')
246
239
 
@@ -2,13 +2,6 @@ dir = File.dirname(File.expand_path(__FILE__))
2
2
  $LOAD_PATH.unshift dir + '/../lib'
3
3
  $TESTING = true
4
4
 
5
- require 'rubygems'
6
- require 'timeout'
7
- require 'minitest/autorun'
8
- require 'minitest/pride'
9
- require 'rack/test'
10
- require 'mocha/setup'
11
-
12
5
  # Run code coverage in MRI 1.9 only.
13
6
  if RUBY_VERSION >= '1.9' && RUBY_ENGINE == 'ruby'
14
7
  require 'simplecov'
@@ -17,6 +10,13 @@ if RUBY_VERSION >= '1.9' && RUBY_ENGINE == 'ruby'
17
10
  end
18
11
  end
19
12
 
13
+ require 'rubygems'
14
+ require 'timeout'
15
+ require 'minitest/autorun'
16
+ require 'minitest/pride'
17
+ require 'rack/test'
18
+ require 'mocha/setup'
19
+
20
20
  require 'resque-retry'
21
21
  require dir + '/test_jobs'
22
22
 
@@ -56,7 +56,7 @@ end
56
56
 
57
57
  class JobRetryQueue
58
58
  extend Resque::Plugins::Retry
59
- @queue = :testing_retry
59
+ @queue = :testing_retry_delegate
60
60
 
61
61
  def self.perform(*args)
62
62
  end
@@ -72,6 +72,50 @@ class JobWithRetryQueue
72
72
  end
73
73
  end
74
74
 
75
+ class JobWithDynamicRetryQueue
76
+ extend Resque::Plugins::Retry
77
+ @queue = :testing
78
+ @retry_delay = 1
79
+
80
+ def self.retry_queue(exception, *args)
81
+ args.first
82
+ end
83
+
84
+ def self.perform(*args)
85
+ raise
86
+ end
87
+ end
88
+
89
+ class DynamicDelayedJobOnExceptionAndArgs
90
+ extend Resque::Plugins::Retry
91
+ @queue = :testing
92
+
93
+ def self.retry_delay(exception, *args)
94
+ args.first.to_i
95
+ end
96
+
97
+ def self.perform(*args)
98
+ raise
99
+ end
100
+ end
101
+
102
+ class DynamicDelayedJobOnException
103
+ extend Resque::Plugins::Retry
104
+ @queue = :testing
105
+
106
+ def self.retry_delay(exception)
107
+ if exception == SocketError
108
+ 4
109
+ else
110
+ 1
111
+ end
112
+ end
113
+
114
+ def self.perform(*args)
115
+ raise SocketError
116
+ end
117
+ end
118
+
75
119
  class DelayedJobWithRetryQueue
76
120
  extend Resque::Plugins::Retry
77
121
  @queue = :testing
@@ -591,16 +635,3 @@ class IgnoreExceptionsJob
591
635
  "Hello, World!"
592
636
  end
593
637
  end
594
-
595
- class IgnoreExceptionsImproperlyConfiguredJob1
596
- # Manually extend Resque::Plugins::Retry in the test code to catch the
597
- # exception.
598
- @ignore_exceptions = [CustomException]
599
- end
600
-
601
- class IgnoreExceptionsImproperlyConfiguredJob2
602
- # Manually extend Resque::Plugins::Retry in the test code to catch the
603
- # exception.
604
- @ignore_exceptions = [CustomException]
605
- @retry_exceptions = { AnotherCustomException => 0 }
606
- end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-retry
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luke Antins
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-07-16 00:00:00.000000000 Z
13
+ date: 2018-11-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: resque
@@ -188,8 +188,10 @@ files:
188
188
  - test/resque_test.rb
189
189
  - test/retry_callbacks_test.rb
190
190
  - test/retry_criteria_test.rb
191
+ - test/retry_custom_delay_test.rb
191
192
  - test/retry_exception_delay_test.rb
192
193
  - test/retry_inheriting_checks_test.rb
194
+ - test/retry_queue_test.rb
193
195
  - test/retry_test.rb
194
196
  - test/server_helpers_test.rb
195
197
  - test/server_test.rb