resque-retry 1.7.2 → 1.7.6

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: 755ea6078469205b32e916d32d7dbb57a668e409d559b32ec7a5b21560a515c8
4
- data.tar.gz: 98e9eb8627d4641771f66e8811d29bfc2e1baab52b5a38809bb4a0005e465480
3
+ metadata.gz: bcd42f2b0761226dd2cfa5f9c9d4ff336cda1be0f6acf972ae15cd553f88fa0b
4
+ data.tar.gz: d65b4261a3ff1068f9880739ea1f4647c10be957511840203331f714cb8281dc
5
5
  SHA512:
6
- metadata.gz: 475ca16f0d0814aa68ca14ff1ba2ec30c037526b69432052e8b88940e26964cb6e25ce71282a8161796d91ccae1ebfff23b43956485f5ae6b820e83aa00bb06b
7
- data.tar.gz: 1d6870270be5df0c1a9820879ebfef192bbaa2a7cbe16199e5c8c319bd8b5f7a5a2207105646ebc0a3a99221a1defeab520727053649cfa310025caadc7d1520
6
+ metadata.gz: 78c8fccf7a670f1fc0246d7e8862941eae3fb2bda8ea4d11fb1bea2a5890ed1867f50ff9389a752f3383bf5383783b32e3f63c5ef623909a28aaeba62f4576a2
7
+ data.tar.gz: 8e3e0ed80d154282e6dddd85b8b7b95b22949d580ad6c0c7a0fc9c3a16475dca861320f919db0f2ddf6b8dca78e8bdd846a48b86db6a5761848d6d4fcb7012a4
@@ -0,0 +1,24 @@
1
+ on: [push, pull_request]
2
+
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ services:
7
+ redis:
8
+ image: redis
9
+ ports:
10
+ - 6379:6379
11
+ strategy:
12
+ matrix:
13
+ ruby-version: [2.7, 2.6]
14
+
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - name: Set up Ruby ${{ matrix.ruby-version }}
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.ruby-version }}
21
+ - name: Install dependencies
22
+ run: bundle install
23
+ - name: Run tests
24
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -14,3 +14,5 @@ tags.*
14
14
  *.gem
15
15
  .ruby-version
16
16
  .ruby-gemset
17
+ .tags
18
+ dump.rdb
data/HISTORY.md CHANGED
@@ -1,3 +1,23 @@
1
+ # 1.7.6 (2021-08-06)
2
+
3
+ * Fix Redis 4.3 breaking saving job failures with multiple failure backend
4
+ * Disable verbose logging by default during test-runs
5
+ * Remove support for ruby < 2.6
6
+
7
+ # 1.7.5 (2021-08-06)
8
+
9
+ * Support `retry_delay` methods with different arity
10
+ * Switch over to _GitHub_ CI for PR test-execution
11
+
12
+ # 1.7.4 (2020-10-09)
13
+
14
+ * Rework how the default `retry_limit` is calculated
15
+ * Numerous documentation updates
16
+
17
+ # 1.7.3 (2019-11-21)
18
+
19
+ * Do not crash if job class is not found on the resque-web server
20
+
1
21
  # 1.7.2 (2019-11-21)
2
22
 
3
23
  * Address part 2 of issue #158
data/README.md CHANGED
@@ -231,8 +231,8 @@ class ExampleJob
231
231
  extend Resque::Plugins::Retry
232
232
  @queue = :testing
233
233
 
234
- def self.retry_delay(exception)
235
- if exception == SocketError
234
+ def self.retry_delay(exception_class)
235
+ if exception_class == SocketError
236
236
  10
237
237
  else
238
238
  1
@@ -359,7 +359,12 @@ You may also want to specify different retry delays for different exception
359
359
  types. You may optionally set `@retry_exceptions` to a hash where the keys are
360
360
  your specific exception classes to retry on, and the values are your retry
361
361
  delays in seconds or an array of retry delays to be used similar to exponential
362
- backoff.
362
+ backoff. `resque-retry` will attempt to determine your retry strategy's
363
+ `@retry_limit` based on your specified `@retry_exceptions`. If, however, you
364
+ define `@retry_limit` explicitly, you should define `@retry_limit` such that it
365
+ allows for your retry strategies to complete. If your `@retry_limit` is less
366
+ than the number of desired retry attempts defined in `@retry_exceptions`, your
367
+ job will only retry `@retry_limit` times.
363
368
  ```ruby
364
369
  class DeliverSMS
365
370
  extend Resque::Plugins::Retry
@@ -374,9 +379,15 @@ end
374
379
  ```
375
380
 
376
381
  In the above example, Resque would retry any `DeliverSMS` jobs which throw a
377
- `NetworkError` or `SystemCallError`. If the job throws a `NetworkError` it
378
- will be retried 30 seconds later, if it throws `SystemCallError` it will first
379
- retry 120 seconds later then subsequent retry attempts 240 seconds later.
382
+ `NetworkError` or `SystemCallError`. The `@retry_limit` would be inferred to be
383
+ 2 based on the longest retry strategy defined in `@retry_exceptions`. If the job
384
+ throws a `NetworkError` it will be retried 30 seconds later with a subsequent
385
+ retry 30 seconds after that. If it throws a `SystemCallError` it will first
386
+ retry 120 seconds later then a subsequent retry attempt 240 seconds later. If
387
+ the job fails due to a `NetworkError`, Resque would retry the job in 30 seconds.
388
+ If the job fails a second time, this time due to a `SystemCallError`, the next
389
+ retry would occur 240 seconds later as specified in the `SystemCallError`
390
+ array defined in `@retry_exceptions`.
380
391
 
381
392
  ### <a name="fail_fast"></a> Fail Fast For Specific Exceptions
382
393
 
@@ -511,7 +522,7 @@ class ExampleJob
511
522
 
512
523
  def self.work(*args)
513
524
  user_id, user_mode, record_id = *args
514
-
525
+
515
526
  Resque.enqueue_to(
516
527
  target_queue_for_args(user_id, user_mode, record_id),
517
528
  self,
@@ -531,7 +542,7 @@ class ExampleJob
531
542
  user_id, user_mode, record_id = *args
532
543
 
533
544
  if user_mode
534
- 'high
545
+ 'high'
535
546
  else
536
547
  'low'
537
548
  end
@@ -585,7 +596,7 @@ end
585
596
 
586
597
  This saves you from having to run a "house cleaning" or "errand" job.
587
598
 
588
- The expiary timeout is "pushed forward" or "touched" after each failure to
599
+ The expiry timeout is "pushed forward" or "touched" after each failure to
589
600
  ensure it's not expired too soon.
590
601
 
591
602
  ### <a name="callbacks"></a> Try Again and Give Up Callbacks
@@ -66,15 +66,23 @@ module ResqueRetry
66
66
  # cancels job retry
67
67
  def cancel_retry(job)
68
68
  klass = get_class(job)
69
- retry_key = retry_key_for_job(job)
70
- Resque.remove_delayed(klass, *job['args'])
71
- Resque.redis.del("failure-#{retry_key}")
72
- Resque.redis.del(retry_key)
69
+ if klass
70
+ retry_key = retry_key_for_job(job)
71
+ Resque.remove_delayed(klass, *job['args'])
72
+ Resque.redis.del("failure-#{retry_key}")
73
+ Resque.redis.del(retry_key)
74
+ else
75
+ raise 'cannot cancel, job not found'
76
+ end
73
77
  end
74
78
 
75
79
  private
76
80
  def get_class(job)
77
- Resque::Job.new(nil, nil).constantize(job['class'])
81
+ begin
82
+ Resque::Job.new(nil, nil).constantize(job['class'])
83
+ rescue
84
+ nil
85
+ end
78
86
  end
79
87
  end
80
88
 
@@ -1,3 +1,3 @@
1
1
  module ResqueRetry
2
- VERSION = '1.7.2'
2
+ VERSION = '1.7.6'
3
3
  end
@@ -42,10 +42,25 @@ module Resque
42
42
  )
43
43
 
44
44
  cleanup_retry_failure_log!
45
- super
46
- elsif retry_delay > 0
45
+ return super
46
+ end
47
+
48
+ # some plugins define retry_delay and have it take no arguments, so rather than break those,
49
+ # we'll just check here to see whether it takes the additional exception class argument or not
50
+ # we also allow all job args to be passed to a custom `retry_delay` method
51
+ retry_delay_arity = klass.method(:retry_delay).arity
52
+
53
+ calculated_retry_delay = if [-2, 2].include?(retry_delay_arity)
54
+ klass.retry_delay(exception.class, *args)
55
+ elsif [-1, 1].include?(retry_delay_arity)
56
+ klass.retry_delay(exception.class)
57
+ else
58
+ klass.retry_delay
59
+ end
60
+
61
+ if calculated_retry_delay > 0
47
62
  log_message(
48
- "retry_delay: #{retry_delay} > 0 - saving details in Redis",
63
+ "retry_delay: #{calculated_retry_delay} > 0 - saving details in Redis",
49
64
  args,
50
65
  exception
51
66
  )
@@ -63,12 +78,12 @@ module Resque
63
78
 
64
79
  Resque.redis.setex(
65
80
  failure_key,
66
- 2 * retry_delay,
81
+ 2 * calculated_retry_delay,
67
82
  data
68
83
  )
69
84
  else
70
85
  log_message(
71
- "retry_delay: #{retry_delay} <= 0 - ignoring",
86
+ "retry_delay: #{calculated_retry_delay} <= 0 - ignoring",
72
87
  args,
73
88
  exception
74
89
  )
@@ -117,10 +132,6 @@ module Resque
117
132
  Resque::Job.new(nil, nil).constantize(payload['class'])
118
133
  end
119
134
 
120
- def retry_delay
121
- klass.retry_delay
122
- end
123
-
124
135
  def retry_key
125
136
  klass.redis_retry_key(*payload['args'])
126
137
  end
@@ -132,7 +143,17 @@ module Resque
132
143
  end
133
144
 
134
145
  def retrying?
135
- Resque.redis.exists(retry_key)
146
+ redis_key_exists?(retry_key)
147
+ end
148
+
149
+ private
150
+
151
+ def redis_key_exists?(key)
152
+ if Resque.redis.respond_to?(:exists?)
153
+ Resque.redis.exists?(key)
154
+ else
155
+ ![false, 0].include?(Resque.redis.exists(key) || false)
156
+ end
136
157
  end
137
158
  end
138
159
  end
@@ -80,10 +80,11 @@ module Resque
80
80
 
81
81
  # Selects the delay from the backoff strategy
82
82
  #
83
+ # @param _ [Exception] unused exception argument for signature parity
83
84
  # @return [Number] seconds to delay until the next retry.
84
85
  #
85
86
  # @api private
86
- def retry_delay
87
+ def retry_delay(_ = nil)
87
88
  delay = backoff_strategy[retry_attempt] || backoff_strategy.last
88
89
  # if the values are the same don't bother generating a random number, if
89
90
  # the delta is zero, some platforms will raise an error
@@ -107,11 +107,27 @@ module Resque
107
107
  # A retry limit of 0 will *never* retry.
108
108
  # A retry limit of -1 or below will retry forever.
109
109
  #
110
+ # The default value is: `1` or in the case of where `@retry_exceptions` is
111
+ # specified, and it contains one or more `Array` values, the maximum
112
+ # length will be used (e.g. `@retry_exceptions = { NetworkError => 30, SystemCallError => [120, 240] }`
113
+ # would return `2` because `SystemCallError` _should_ be attempted at
114
+ # least twice to respect the specified configuration).
115
+ #
110
116
  # @return [Fixnum]
111
117
  #
112
118
  # @api public
113
119
  def retry_limit
114
- @retry_limit ||= 1
120
+ @retry_limit ||= begin
121
+ default_retry_limit = 1
122
+ if instance_variable_defined?(:@retry_exceptions) && @retry_exceptions.is_a?(Hash)
123
+ @retry_exceptions.values.each do |value|
124
+ if value.is_a?(Array) && value.length > default_retry_limit
125
+ default_retry_limit = value.length
126
+ end
127
+ end
128
+ end
129
+ default_retry_limit
130
+ end
115
131
  end
116
132
 
117
133
  # Number of retry attempts used to try and perform the job
@@ -128,4 +128,11 @@ class ExponentialBackoffTest < Minitest::Test
128
128
  assert_equal 4, Resque.info[:failed], 'failed jobs'
129
129
  assert_equal 0, Resque.info[:pending], 'pending jobs'
130
130
  end
131
+
132
+ def test_backoff_with_expiration
133
+ Resque.redis.expects(:expire)
134
+
135
+ Resque.enqueue(ExponentialBackoffWithExpiryJob)
136
+ perform_next_job(@worker)
137
+ end
131
138
  end
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require_relative './test_helper'
2
2
 
3
3
  # Mock failure backend for testing MultipleWithRetrySuppression
4
4
  class MockFailureBackend < Resque::Failure::Base
@@ -25,8 +25,8 @@ class MultipleFailureTest < Minitest::Test
25
25
  Resque::Failure.backend = Resque::Failure::MultipleWithRetrySuppression
26
26
  end
27
27
 
28
- def failure_key_for(klass)
29
- 'failure-' + klass.redis_retry_key([])
28
+ def failure_key_for(klass, *args)
29
+ Resque::Failure::MultipleWithRetrySuppression.failure_key(klass.redis_retry_key(args))
30
30
  end
31
31
 
32
32
  def test_failure_is_passed_on_when_job_class_not_found
@@ -53,6 +53,22 @@ class MultipleFailureTest < Minitest::Test
53
53
  assert Resque.redis.exists(key)
54
54
  end
55
55
 
56
+ def test_retry_delay_is_calculated_with_custom_calculation
57
+ delay = 5
58
+ Resque.enqueue(DynamicDelayedJobOnExceptionAndArgs, delay.to_s)
59
+ perform_next_job(@worker)
60
+
61
+ key = failure_key_for(DynamicDelayedJobOnExceptionAndArgs, delay.to_s)
62
+ ttl = Resque.redis.ttl(key)
63
+ assert Resque.redis.exists(key)
64
+ assert MockFailureBackend.errors.size == 0
65
+
66
+ # expiration on failure_key is set to 2x the delay
67
+ # to ensure the customized delay is properly calculated using
68
+ # dynamic retry_delay method on the job
69
+ assert ttl > delay && delay <= (delay * 2)
70
+ end
71
+
56
72
  def test_retry_key_splatting_args
57
73
  # were expecting this to be called three times:
58
74
  # - once when we queue the job to try again
@@ -141,6 +157,20 @@ class MultipleFailureTest < Minitest::Test
141
157
  end
142
158
  end
143
159
 
160
+ def test_redis_exists_returns_integer
161
+ Resque.enqueue(RetryDefaultsJob)
162
+ original = Redis.exists_returns_integer
163
+ Redis.exists_returns_integer = true
164
+
165
+ 3.times do
166
+ perform_next_job(@worker)
167
+ end
168
+
169
+ Redis.exists_returns_integer = original
170
+
171
+ assert_equal 1, MockFailureBackend.errors.size
172
+ end
173
+
144
174
  def teardown
145
175
  Resque::Failure.backend = @old_failure_backend
146
176
  end
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class RetryExeptionDelayTest < Minitest::Test
3
+ class RetryExceptionDelayTest < Minitest::Test
4
4
  def setup
5
5
  Resque.redis.flushall
6
6
  @worker = Resque::Worker.new(:testing)
@@ -43,4 +43,12 @@ class RetryExeptionDelayTest < Minitest::Test
43
43
  assert_in_delta (start_time + 10), delayed[1], 1.00, '2nd retry delay timestamp'
44
44
  assert_in_delta (start_time + 15), delayed[2], 1.00, '3rd retry delay timestamp'
45
45
  end
46
+
47
+ def test_retry_delay_per_exception_multiple_delay_no_retry_limit_specified
48
+ # For this job-type there are 3 `Exception` types defined: `Exception`,
49
+ # `StandardError` and `Timeout::Error` -- their array-lengths are are 1, 3
50
+ # and 5 (respectively). We expect the `retry_limit` to default to the
51
+ # maximum length of the `Array` of delays or 1 (in this case: 5)
52
+ assert_equal 5, PerExceptionClassRetryCountArrayNoRetryLimitSpecifiedJob.retry_limit
53
+ end
46
54
  end
data/test/test_helper.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  dir = File.dirname(File.expand_path(__FILE__))
2
2
  $LOAD_PATH.unshift dir + '/../lib'
3
+
3
4
  $TESTING = true
5
+ $VERBOSE = ENV['VERBOSE'] != 'true' ? nil : true
4
6
 
5
7
  # Run code coverage in MRI 1.9 only.
6
8
  if RUBY_VERSION >= '1.9' && RUBY_ENGINE == 'ruby'
@@ -20,25 +22,29 @@ require 'mocha/setup'
20
22
  require 'resque-retry'
21
23
  require dir + '/test_jobs'
22
24
 
23
- # make sure we can run redis-server
24
- if !system('which redis-server')
25
- puts '', "** `redis-server` was not found in your PATH"
26
- abort ''
27
- end
25
+ if ENV['CI'] != 'true'
26
+ # make sure we can run redis-server
27
+ if !system('which redis-server')
28
+ puts '', "** `redis-server` was not found in your PATH"
29
+ abort ''
30
+ end
28
31
 
29
- # make sure we can shutdown the server using cli.
30
- if !system('which redis-cli')
31
- puts '', "** `redis-cli` was not found in your PATH"
32
- abort ''
33
- end
32
+ # make sure we can shutdown the server using cli.
33
+ if !system('which redis-cli')
34
+ puts '', "** `redis-cli` was not found in your PATH"
35
+ abort ''
36
+ end
34
37
 
35
- # This code is run after all the tests have finished running to ensure that the
36
- # Redis server is shutdowa
37
- Minitest.after_run { `redis-cli -p 9736 shutdown nosave` }
38
+ # This code is run after all the tests have finished running to ensure that the
39
+ # Redis server is shutdowa
40
+ Minitest.after_run { `redis-cli -p 9736 shutdown nosave` }
38
41
 
39
- puts "Starting redis for testing at localhost:9736..."
40
- `redis-server #{dir}/redis-test.conf`
41
- Resque.redis = '127.0.0.1:9736'
42
+ puts "Starting redis for testing at localhost:9736..."
43
+ `redis-server #{dir}/redis-test.conf`
44
+ Resque.redis = '127.0.0.1:9736'
45
+ else
46
+ Resque.redis = '127.0.0.1:6379'
47
+ end
42
48
 
43
49
  # Test helpers
44
50
  class Minitest::Test
@@ -65,11 +71,11 @@ class Minitest::Test
65
71
 
66
72
  def delayed_jobs
67
73
  # The double-checks here are so that we won't blow up if the config stops using redis-namespace
68
- timestamps = Resque.redis.zrange("resque:delayed_queue_schedule", 0, -1) +
74
+ timestamps = Resque.redis.zrange("resque:delayed_queue_schedule", 0, -1) +
69
75
  Resque.redis.zrange("delayed_queue_schedule", 0, -1)
70
76
 
71
77
  delayed_jobs_as_json = timestamps.map do |timestamp|
72
- Resque.redis.lrange("resque:delayed:#{timestamp}", 0, -1) +
78
+ Resque.redis.lrange("resque:delayed:#{timestamp}", 0, -1) +
73
79
  Resque.redis.lrange("delayed:#{timestamp}", 0, -1)
74
80
  end.flatten
75
81
 
data/test/test_jobs.rb CHANGED
@@ -254,6 +254,12 @@ class ExponentialBackoffWithRetryDelayMultiplicandMinAndMaxJob < RetryDefaultsJo
254
254
  @retry_delay_multiplicand_max = 3.0
255
255
  end
256
256
 
257
+ class ExponentialBackoffWithExpiryJob < RetryDefaultsJob
258
+ extend Resque::Plugins::ExponentialBackoff
259
+ @queue = :testing
260
+ @expire_retry_key_after = 60 * 60
261
+ end
262
+
257
263
  class InvalidRetryDelayMaxConfigurationJob
258
264
  @queue = :testing
259
265
  @retry_delay_multiplicand_max = 0.9
@@ -576,6 +582,17 @@ class PerExceptionClassRetryCountArrayJob
576
582
  end
577
583
  end
578
584
 
585
+ class PerExceptionClassRetryCountArrayNoRetryLimitSpecifiedJob
586
+ extend Resque::Plugins::Retry
587
+
588
+ @queue = :testing
589
+ @retry_exceptions = { Exception => 11, RuntimeError => [5, 10, 15], Timeout::Error => [2, 4, 6, 8, 10] }
590
+
591
+ def self.perform
592
+ raise RuntimeError, 'I always fail with a RuntimeError'
593
+ end
594
+ end
595
+
579
596
  # We can't design a job to fail during connect, see perform_next_job_fail_on_reconnect
580
597
  class FailsDuringConnectJob < RetryDefaultsJob
581
598
  @queue = :testing
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-retry
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luke Antins
8
8
  - Ryan Carver
9
9
  - Jonathan W. Zaleski
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-11-21 00:00:00.000000000 Z
13
+ date: 2021-08-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: resque
@@ -161,8 +161,8 @@ executables: []
161
161
  extensions: []
162
162
  extra_rdoc_files: []
163
163
  files:
164
+ - ".github/workflows/ci.yml"
164
165
  - ".gitignore"
165
- - ".travis.yml"
166
166
  - Gemfile
167
167
  - HISTORY.md
168
168
  - LICENSE
@@ -208,7 +208,7 @@ homepage: http://github.com/lantins/resque-retry
208
208
  licenses:
209
209
  - MIT
210
210
  metadata: {}
211
- post_install_message:
211
+ post_install_message:
212
212
  rdoc_options: []
213
213
  require_paths:
214
214
  - lib
@@ -223,8 +223,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
223
  - !ruby/object:Gem::Version
224
224
  version: '0'
225
225
  requirements: []
226
- rubygems_version: 3.0.6
227
- signing_key:
226
+ rubygems_version: 3.2.22
227
+ signing_key:
228
228
  specification_version: 4
229
229
  summary: A resque plugin; provides retry, delay and exponential backoff support for
230
230
  resque jobs.
data/.travis.yml DELETED
@@ -1,14 +0,0 @@
1
- language: ruby
2
-
3
- sudo: false
4
-
5
- matrix:
6
- allow_failures:
7
- - rvm: jruby-9.1.9.0
8
-
9
- rvm:
10
- - 2.3.8
11
- - 2.4.6
12
- - 2.5.5
13
- - 2.6.3
14
- - jruby-9.1.9.0