sidekiq-rescue 0.3.0 → 0.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01671c36411fb052587442c08db979d740c563e6d74311ed1c4d17293b266745
4
- data.tar.gz: 90de1649e943e5a422dc6d601d7b7425ee74958418ed9f89ef635c27aac1b0b7
3
+ metadata.gz: dd1cc37dddad565e32a86226f22111be381fffb62f80253e36dbd563598531be
4
+ data.tar.gz: 050f58d316e722f243b7eb49a5396cb56aa0ba368d827099aa4ca2dafafe43a0
5
5
  SHA512:
6
- metadata.gz: c60d5673e1c25f7634820a760e4e4f0463ab618187147f96a412527db866944f7a37b44327d50053066c83b2e2694c09570befba78902053969b27a116968df7
7
- data.tar.gz: 291b24d3f090b425702bd9ec2f644f8bb92e374deb7cb20d9553574b30b3502d7091544de81a2769238fbb3f4ea3851b9939299a2630dd54d2b416285db3c587
6
+ metadata.gz: 34d0325a6547e9ff4087114fb20c424383a62a144bafe72ede1ed53badecdba38affa32f6b695a5e68ab02c100c96629dfce30bc766f656f5dc319659e8e9489
7
+ data.tar.gz: a6d66d595d8e75e18cadb0f4e7360e49604c36a86c2587b249cb3e98fb4d90b30564ecf1e88d574c97ec34e5673e7a6df12b4d36d45eede6f6529cab9c1423c5
data/CHANGELOG.md CHANGED
@@ -1,9 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2024-06-03
4
+ - Add support for jitter configuration [#4](https://github.com/moofkit/sidekiq-rescue/pull/4)
5
+ - Changes the strategy for retry delay. Now it's calculated using the formula `delay + delay * jitter * rand`
6
+
7
+ ## [0.3.1] - 2024-05-30
8
+
9
+ - Fix bug with inheritance of DSL options
10
+
3
11
  ## [0.3.0] - 2024-05-30
4
12
 
5
13
  - Fix issue with RSpec matcher when job is not rescueable
6
- - Add support for multiple invocations of the DSL #3
14
+ - Add support for multiple invocations of the DSL
7
15
  - Update documentation with new features
8
16
 
9
17
  ## [0.2.1] - 2024-02-27
@@ -26,7 +34,10 @@
26
34
  - Add documentation
27
35
  - Add CI
28
36
 
29
- [Unreleased]: https://github.com/moofkit/sidekiq-rescue/compare/v0.2.1...HEAD
37
+ [Unreleased]: https://github.com/moofkit/sidekiq-rescue/compare/v0.4.0...HEAD
38
+ [0.4.0]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.4.0
39
+ [0.3.1]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.3.1
40
+ [0.3.0]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.3.0
30
41
  [0.2.1]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.2.1
31
42
  [0.2.0]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.2.0
32
43
  [0.1.0]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.1.0
data/README.md CHANGED
@@ -81,7 +81,7 @@ class MyJob
81
81
  include Sidekiq::Job
82
82
  include Sidekiq::Rescue::Dsl
83
83
 
84
- sidekiq_rescue ExpectedError, delay: 60, limit: 5
84
+ sidekiq_rescue ExpectedError, delay: 60, limit: 5, jitter: 0.15
85
85
 
86
86
  def perform(*)
87
87
  # ...
@@ -89,18 +89,20 @@ class MyJob
89
89
  end
90
90
  ```
91
91
 
92
- The `delay` is not the exact time between retries, but a minimum delay. The actual delay calculates based on retries counter and `delay` value. The formula is `delay + retries * rand(10)` seconds. Randomization is used to avoid retry storms.
92
+ The `delay` is not the exact time between retries, but a minimum delay. The actual delay calculates based on jitter and `delay` value. The formula is `delay + delay * jitter * rand` seconds. Randomization is used to avoid retry storms. The `jitter` represents the upper bound of possible wait time (expressed as a percentage) and defaults to 0.15 (15%).
93
93
 
94
94
  The default values are:
95
95
  - `delay`: 60 seconds
96
96
  - `limit`: 5 retries
97
+ - `jitter`: 0.15
97
98
 
98
- Delay and limit can be configured globally:
99
+ Delay, limit and jitter can be configured globally:
99
100
 
100
101
  ```ruby
101
102
  Sidekiq::Rescue.configure do |config|
102
103
  config.delay = 65
103
104
  config.limit = 10
105
+ config.jitter = 0.2
104
106
  end
105
107
  ```
106
108
 
@@ -7,12 +7,14 @@ module Sidekiq
7
7
  class Config
8
8
  DEFAULTS = {
9
9
  delay: 60,
10
- limit: 10
10
+ limit: 10,
11
+ jitter: 0.15
11
12
  }.freeze
12
13
 
13
14
  def initialize
14
15
  @delay = DEFAULTS[:delay]
15
16
  @limit = DEFAULTS[:limit]
17
+ @jitter = DEFAULTS[:jitter]
16
18
  @logger = Sidekiq.logger
17
19
  end
18
20
 
@@ -45,6 +47,22 @@ module Sidekiq
45
47
  @limit = limit
46
48
  end
47
49
 
50
+ # The jitter for the delay.
51
+ # @return [Integer, Float]
52
+ attr_reader :jitter
53
+
54
+ # @param jitter [Integer, Float] The jitter for the delay.
55
+ # @return [void]
56
+ # @raise [ArgumentError] if jitter is not an Integer or Float
57
+ def jitter=(jitter)
58
+ case jitter
59
+ when Integer, Float
60
+ @jitter = jitter
61
+ else
62
+ raise ArgumentError, "jitter must be Integer or Float"
63
+ end
64
+ end
65
+
48
66
  # The logger instance.
49
67
  # @return [Logger]
50
68
  # @note The default logger is Sidekiq's logger.
@@ -22,17 +22,25 @@ module Sidekiq
22
22
  # @raise [ArgumentError] if error is not an array of StandardError
23
23
  # @raise [ArgumentError] if delay is not an Integer or Float
24
24
  # @raise [ArgumentError] if limit is not an Integer
25
+ # @raise [ArgumentError] if jitter is not an Integer or Float
25
26
  # @example
26
27
  # sidekiq_rescue NetworkError, delay: 60, limit: 10
27
- def sidekiq_rescue(*errors, delay: Sidekiq::Rescue.config.delay, limit: Sidekiq::Rescue.config.limit)
28
+ def sidekiq_rescue(*errors, delay: Sidekiq::Rescue.config.delay, limit: Sidekiq::Rescue.config.limit,
29
+ jitter: Sidekiq::Rescue.config.jitter)
28
30
  unpacked_errors = validate_and_unpack_error_argument(errors)
29
31
  validate_delay_argument(delay)
30
32
  validate_limit_argument(limit)
31
- assign_sidekiq_rescue_options(unpacked_errors, delay, limit)
33
+ validate_jitter_argument(jitter)
34
+ assign_sidekiq_rescue_options(errors: unpacked_errors, delay: delay, limit: limit, jitter: jitter)
32
35
  end
33
36
 
34
- def sidekiq_rescue_options_for(error)
35
- sidekiq_rescue_options&.find { |k, _v| k.include?(error) }&.last
37
+ # Find the error group and options for the given exception.
38
+ # @param exception [StandardError] The exception to find the error group for.
39
+ # @return [Array<StandardError>, Hash] The error group and options.
40
+ def sidekiq_rescue_error_group_with_options_by(exception)
41
+ sidekiq_rescue_options.reverse_each.find do |error_group, _options|
42
+ Array(error_group).any? { |error_klass| exception.is_a?(error_klass) }
43
+ end
36
44
  end
37
45
 
38
46
  private
@@ -46,7 +54,6 @@ module Sidekiq
46
54
  end
47
55
 
48
56
  def validate_delay_argument(delay)
49
- return if delay.nil?
50
57
  return if delay.is_a?(Integer) || delay.is_a?(Float)
51
58
 
52
59
  if delay.is_a?(Proc)
@@ -63,9 +70,17 @@ module Sidekiq
63
70
  raise ArgumentError, "limit must be integer" if limit && !limit.is_a?(Integer)
64
71
  end
65
72
 
66
- def assign_sidekiq_rescue_options(errors, delay, limit)
73
+ def validate_jitter_argument(jitter)
74
+ return if jitter.is_a?(Integer) || jitter.is_a?(Float)
75
+
76
+ raise ArgumentError,
77
+ "jitter must be integer or float"
78
+ end
79
+
80
+ def assign_sidekiq_rescue_options(errors:, delay:, limit:, jitter:)
67
81
  self.sidekiq_rescue_options ||= {}
68
- self.sidekiq_rescue_options.merge!(errors => { delay: delay, limit: limit })
82
+ self.sidekiq_rescue_options = self.sidekiq_rescue_options.merge(errors => { delay: delay, limit: limit,
83
+ jitter: jitter })
69
84
  end
70
85
  end
71
86
  end
@@ -35,7 +35,7 @@ module Sidekiq
35
35
 
36
36
  return false unless matched
37
37
 
38
- options = actual.sidekiq_rescue_options_for(expected)
38
+ _error_group, options = actual.sidekiq_rescue_error_group_with_options_by(expected.new)
39
39
 
40
40
  (@delay.nil? || options.fetch(:delay) == @delay) &&
41
41
  (@limit.nil? || options.fetch(:limit) == @limit)
@@ -23,22 +23,20 @@ module Sidekiq
23
23
  def sidekiq_rescue(job_payload, job_class)
24
24
  yield
25
25
  rescue StandardError => e
26
- error_group, options = job_class.sidekiq_rescue_options.reverse_each.find do |error_group, _options|
27
- Array(error_group).any? { |error| e.is_a?(error) }
28
- end
26
+ error_group, options = job_class.sidekiq_rescue_error_group_with_options_by(e)
29
27
  raise e unless error_group
30
28
 
31
29
  rescue_error(e, error_group, options, job_payload)
32
30
  end
33
31
 
34
32
  def rescue_error(error, error_group, options, job_payload)
35
- delay, limit = options.fetch_values(:delay, :limit)
33
+ delay, limit, jitter = options.fetch_values(:delay, :limit, :jitter)
36
34
  rescue_counter = increment_rescue_counter_for(error_group, job_payload)
37
35
  raise error if rescue_counter > limit
38
36
 
39
- reschedule_at = calculate_reschedule_time(delay, rescue_counter)
40
- log_reschedule_info(rescue_counter, error, reschedule_at)
41
- reschedule_job(job_payload: job_payload, reschedule_at: reschedule_at, rescue_counter: rescue_counter,
37
+ calculated_delay = calculate_delay(delay, rescue_counter, jitter)
38
+ log_reschedule_info(rescue_counter, error, calculated_delay)
39
+ reschedule_job(job_payload: job_payload, delay: calculated_delay, rescue_counter: rescue_counter,
42
40
  error_group: error_group)
43
41
  end
44
42
 
@@ -48,23 +46,27 @@ module Sidekiq
48
46
  rescue_counter
49
47
  end
50
48
 
51
- def calculate_reschedule_time(delay, rescue_counter)
52
- # NOTE: we use the retry counter to increase the jitter
53
- # so that the jobs don't retry at the same time
54
- # inspired by sidekiq https://github.com/sidekiq/sidekiq/blob/73c150d0430a8394cadb5cd49218895b113613a0/lib/sidekiq/job_retry.rb#L188
55
- jitter = rand(10) * rescue_counter
49
+ def calculate_delay(delay, rescue_counter, jitter)
56
50
  delay = delay.call(rescue_counter) if delay.is_a?(Proc)
57
- Time.now.to_f + delay + jitter
51
+ jitter_delay = calculate_delay_jitter(jitter, delay)
52
+ delay + jitter_delay
53
+ end
54
+
55
+ def calculate_delay_jitter(jitter, delay)
56
+ return 0.0 if jitter.zero?
57
+
58
+ jitter * Kernel.rand * delay
58
59
  end
59
60
 
60
- def log_reschedule_info(rescue_counter, error, reschedule_at)
61
+ def log_reschedule_info(rescue_counter, error, delay)
61
62
  Sidekiq::Rescue.logger.info("[sidekiq_rescue] Job failed #{rescue_counter} times with error: " \
62
- "#{error.message}; rescheduling at #{reschedule_at}")
63
+ "#{error.message}; rescheduling in #{delay} seconds")
63
64
  end
64
65
 
65
- def reschedule_job(job_payload:, reschedule_at:, rescue_counter:, error_group:)
66
- payload = job_payload.merge("at" => reschedule_at,
67
- "sidekiq_rescue_exceptions_counter" => { error_group.to_s => rescue_counter })
66
+ def reschedule_job(job_payload:, delay:, rescue_counter:, error_group:)
67
+ payload = job_payload.dup
68
+ payload["at"] = Time.now.to_f + delay if delay.positive?
69
+ payload["sidekiq_rescue_exceptions_counter"] = { error_group.to_s => rescue_counter }
68
70
  Sidekiq::Client.push(payload)
69
71
  end
70
72
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sidekiq
4
4
  module Rescue
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-rescue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitrii Ivliev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-30 00:00:00.000000000 Z
11
+ date: 2024-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -50,7 +50,7 @@ metadata:
50
50
  homepage_uri: https://github.com/moofkit/sidekiq-rescue
51
51
  source_code_uri: https://github.com/moofkit/sidekiq-rescue
52
52
  changelog_uri: https://github.com/moofkit/sidekiq-rescue/blob/master/CHANGELOG.md
53
- documentation_uri: https://rubydoc.info/gems/sidekiq-rescue/0.3.0
53
+ documentation_uri: https://rubydoc.info/gems/sidekiq-rescue/0.4.0
54
54
  rubygems_mfa_required: 'true'
55
55
  post_install_message:
56
56
  rdoc_options: []