sidekiq-rescue 0.2.1 → 0.3.1

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: d1a8899e388b21924f60017c1eb3b724bc129bec25c96f4765a37ab1f1d8e7fb
4
- data.tar.gz: 41c934da18716cf917af164e92ce1bccdcab1a5bbce08df1c4cbf6550d5cfe70
3
+ metadata.gz: f373ccbd6c038182766f55cd0d2091eb3052c2a008827b223aa9293728d41268
4
+ data.tar.gz: d6b8c96d73cb0690446be1b8330eda3f805a63f386d1778d5a1682d70e379037
5
5
  SHA512:
6
- metadata.gz: a644feb9b9c4d3e1eef97d24738b1157c7efc370102d3db19db3df23f2133382878a264818b5a9a99059e1f64e9b60cbe8e4ea8b297757a760e00fb1df71865b
7
- data.tar.gz: 678d83f42a1ffff8dd71def36911fcdc42b7cf2186e053e47b02c9d4237ea51efc2bd10ed003c7909ec830006b3e725b5e64253faca3c0d4555d97a8536be114
6
+ metadata.gz: 967dd0e178742e6dd1df06f0e5382d2a07d889a18007db15c7202f2725954c29916a866b182b1823e35fe24ad5252df791201e26045c456272ac74b36f1c82c8
7
+ data.tar.gz: 6ce5d97588e78c3cf1bd49e462a9af742bd7afa2fc25a5b6e798adc67a182e0d2236b8c24471d729de757a7cbb25d8544d7baac3e69d5fb01dec164fb72c34a8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 2024-05-30
4
+
5
+ - Fix bug with inheritance of DSL options
6
+
7
+ ## [0.3.0] - 2024-05-30
8
+
9
+ - Fix issue with RSpec matcher when job is not rescueable
10
+ - Add support for multiple invocations of the DSL #3
11
+ - Update documentation with new features
12
+
3
13
  ## [0.2.1] - 2024-02-27
4
14
 
5
15
  - Fix readme with correct middleware name
@@ -20,7 +30,9 @@
20
30
  - Add documentation
21
31
  - Add CI
22
32
 
23
- [Unreleased]: https://github.com/moofkit/sidekiq-rescue/compare/v0.2.1...HEAD
33
+ [Unreleased]: https://github.com/moofkit/sidekiq-rescue/compare/v0.3.1...HEAD
34
+ [0.3.1]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.3.1
35
+ [0.3.0]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.3.0
24
36
  [0.2.1]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.2.1
25
37
  [0.2.0]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.2.0
26
38
  [0.1.0]: https://github.com/moofkit/sidekiq-rescue/releases/tag/v0.1.0
data/README.md CHANGED
@@ -3,6 +3,31 @@
3
3
  [![Build Status](https://github.com/moofkit/sidekiq-rescue/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/moofkit/sidekiq-rescue/actions/workflows/main.yml)
4
4
 
5
5
  [Sidekiq](https://github.com/sidekiq/sidekiq) plugin to rescue jobs from expected errors and retry them later.
6
+ Catch expected errors and retry the job with a delay and a limit. It's useful when you want to retry jobs that failed due to expected errors and not spam your exception tracker with these errors. If the exception will getting raised beyond the limit, it will be re-raised and will be handled by Sidekiq standard retry mechanism.
7
+
8
+ Handlers are searched from bottom to top, and up the inheritance chain. The first handler that `exception.is_a?(klass)` holds true will be used.
9
+
10
+ ## Example
11
+
12
+ ```ruby
13
+ class MyJob
14
+ include Sidekiq::Job
15
+ include Sidekiq::Rescue::Dsl
16
+
17
+ sidekiq_rescue CustomAppException # defaults to 60 seconds delay and 10 retries
18
+ sidekiq_rescue AnotherCustomAppException, delay: ->(counter) { counter * 2 }
19
+ sidekiq_rescue CustomInfrastructureException, delay: 5.minutes
20
+ sidekiq_rescue ActiveRecord::Deadlocked, delay: 5.seconds, limit: 3
21
+ sidekiq_rescue Net::OpenTimeout, Timeout::Error, limit: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
22
+
23
+ def perform(*args)
24
+ # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
25
+ # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
26
+ # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
27
+ end
28
+ end
29
+ ```
30
+
6
31
 
7
32
  ## Installation
8
33
 
@@ -224,7 +249,7 @@ end
224
249
  ## Motivation
225
250
 
226
251
  Sidekiq provides a retry mechanism for jobs that failed due to unexpected errors. However, it does not provide a way to retry jobs that failed due to expected errors. This gem aims to fill this gap.
227
- In addition, it provides a way to configure the number of retries and the delay between retries independently from the Sidekiq standard retry mechanism.
252
+ In addition, it provides a way to configure the number of retries and the delay between retries independently from the Sidekiq standard retry mechanism. Mostly inspired by [ActiveJob](https://edgeapi.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html#method-i-retry_on)
228
253
 
229
254
  ## Supported Ruby versions
230
255
 
@@ -24,16 +24,20 @@ module Sidekiq
24
24
  # @raise [ArgumentError] if limit is not an Integer
25
25
  # @example
26
26
  # sidekiq_rescue NetworkError, delay: 60, limit: 10
27
- def sidekiq_rescue(*error, delay: nil, limit: nil)
28
- error = validate_and_unpack_error_argument(error)
27
+ def sidekiq_rescue(*errors, delay: Sidekiq::Rescue.config.delay, limit: Sidekiq::Rescue.config.limit)
28
+ unpacked_errors = validate_and_unpack_error_argument(errors)
29
29
  validate_delay_argument(delay)
30
30
  validate_limit_argument(limit)
31
+ assign_sidekiq_rescue_options(unpacked_errors, delay, limit)
32
+ end
31
33
 
32
- self.sidekiq_rescue_options = {
33
- error: error,
34
- delay: delay || Sidekiq::Rescue.config.delay,
35
- limit: limit || Sidekiq::Rescue.config.limit
36
- }
34
+ # Find the error group and options for the given exception.
35
+ # @param exception [StandardError] The exception to find the error group for.
36
+ # @return [Array<StandardError>, Hash] The error group and options.
37
+ def sidekiq_rescue_error_group_with_options_by(exception)
38
+ sidekiq_rescue_options.reverse_each.find do |error_group, _options|
39
+ Array(error_group).any? { |error_klass| exception.is_a?(error_klass) }
40
+ end
37
41
  end
38
42
 
39
43
  private
@@ -63,6 +67,11 @@ module Sidekiq
63
67
  def validate_limit_argument(limit)
64
68
  raise ArgumentError, "limit must be integer" if limit && !limit.is_a?(Integer)
65
69
  end
70
+
71
+ def assign_sidekiq_rescue_options(errors, delay, limit)
72
+ self.sidekiq_rescue_options ||= {}
73
+ self.sidekiq_rescue_options = self.sidekiq_rescue_options.merge(errors => { delay: delay, limit: limit })
74
+ end
66
75
  end
67
76
  end
68
77
  # Alias for Dsl; TODO: remove in 1.0.0
@@ -28,11 +28,17 @@ module Sidekiq
28
28
  end
29
29
 
30
30
  match do |actual|
31
- actual.is_a?(Class) &&
32
- actual.include?(Sidekiq::Rescue::Dsl) &&
33
- actual.sidekiq_rescue_options[:error].include?(expected) &&
34
- (@delay.nil? || actual.sidekiq_rescue_options[:delay] == @delay) &&
35
- (@limit.nil? || actual.sidekiq_rescue_options[:limit] == @limit)
31
+ matched = actual.is_a?(Class) &&
32
+ actual.include?(Sidekiq::Rescue::Dsl) &&
33
+ actual.respond_to?(:sidekiq_rescue_options) &&
34
+ actual&.sidekiq_rescue_options&.keys&.flatten&.include?(expected)
35
+
36
+ return false unless matched
37
+
38
+ _error_group, options = actual.sidekiq_rescue_error_group_with_options_by(expected.new)
39
+
40
+ (@delay.nil? || options.fetch(:delay) == @delay) &&
41
+ (@limit.nil? || options.fetch(:limit) == @limit)
36
42
  end
37
43
 
38
44
  match_when_negated do |actual|
@@ -41,7 +47,8 @@ module Sidekiq
41
47
 
42
48
  actual.is_a?(Class) &&
43
49
  actual.include?(Sidekiq::Rescue::Dsl) &&
44
- !actual.sidekiq_rescue_options[:error].include?(expected)
50
+ actual.respond_to?(:sidekiq_rescue_options) &&
51
+ !Array(actual&.sidekiq_rescue_options&.[](:error)).include?(expected)
45
52
  end
46
53
  end
47
54
  end
@@ -11,9 +11,8 @@ module Sidekiq
11
11
 
12
12
  def call(job_instance, job_payload, _queue, &block)
13
13
  job_class = job_instance.class
14
- options = job_class.sidekiq_rescue_options if job_class.respond_to?(:sidekiq_rescue_options)
15
- if options
16
- sidekiq_rescue(job_payload, **options, &block)
14
+ if job_class.respond_to?(:sidekiq_rescue_options) && !job_class.sidekiq_rescue_options.nil?
15
+ sidekiq_rescue(job_payload, job_class, &block)
17
16
  else
18
17
  yield
19
18
  end
@@ -21,19 +20,28 @@ module Sidekiq
21
20
 
22
21
  private
23
22
 
24
- def sidekiq_rescue(job_payload, delay:, limit:, error:, **)
23
+ def sidekiq_rescue(job_payload, job_class)
25
24
  yield
26
- rescue *error => e
27
- rescue_counter = increment_rescue_counter(job_payload)
28
- raise e if rescue_counter > limit
25
+ rescue StandardError => e
26
+ error_group, options = job_class.sidekiq_rescue_error_group_with_options_by(e)
27
+ raise e unless error_group
28
+
29
+ rescue_error(e, error_group, options, job_payload)
30
+ end
31
+
32
+ def rescue_error(error, error_group, options, job_payload)
33
+ delay, limit = options.fetch_values(:delay, :limit)
34
+ rescue_counter = increment_rescue_counter_for(error_group, job_payload)
35
+ raise error if rescue_counter > limit
29
36
 
30
37
  reschedule_at = calculate_reschedule_time(delay, rescue_counter)
31
- log_reschedule_info(rescue_counter, e, reschedule_at)
32
- reschedule_job(job_payload, reschedule_at, rescue_counter)
38
+ log_reschedule_info(rescue_counter, error, reschedule_at)
39
+ reschedule_job(job_payload: job_payload, reschedule_at: reschedule_at, rescue_counter: rescue_counter,
40
+ error_group: error_group)
33
41
  end
34
42
 
35
- def increment_rescue_counter(job_payload)
36
- rescue_counter = job_payload["sidekiq_rescue_counter"].to_i
43
+ def increment_rescue_counter_for(error_group, job_payload)
44
+ rescue_counter = job_payload.dig("sidekiq_rescue_exceptions_counter", error_group.to_s) || 0
37
45
  rescue_counter += 1
38
46
  rescue_counter
39
47
  end
@@ -52,8 +60,10 @@ module Sidekiq
52
60
  "#{error.message}; rescheduling at #{reschedule_at}")
53
61
  end
54
62
 
55
- def reschedule_job(job_payload, reschedule_at, rescue_counter)
56
- Sidekiq::Client.push(job_payload.merge("at" => reschedule_at, "sidekiq_rescue_counter" => rescue_counter))
63
+ def reschedule_job(job_payload:, reschedule_at:, rescue_counter:, error_group:)
64
+ payload = job_payload.merge("at" => reschedule_at,
65
+ "sidekiq_rescue_exceptions_counter" => { error_group.to_s => rescue_counter })
66
+ Sidekiq::Client.push(payload)
57
67
  end
58
68
  end
59
69
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sidekiq
4
4
  module Rescue
5
- VERSION = "0.2.1"
5
+ VERSION = "0.3.1"
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.2.1
4
+ version: 0.3.1
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-02-27 00:00:00.000000000 Z
11
+ date: 2024-05-30 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.2.1
53
+ documentation_uri: https://rubydoc.info/gems/sidekiq-rescue/0.3.1
54
54
  rubygems_mfa_required: 'true'
55
55
  post_install_message:
56
56
  rdoc_options: []
@@ -67,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  requirements: []
70
- rubygems_version: 3.4.10
70
+ rubygems_version: 3.5.9
71
71
  signing_key:
72
72
  specification_version: 4
73
73
  summary: Rescue Sidekiq jobs on expected error and reschedule them