sidekiq-unique-jobs 7.0.2 → 7.1.12

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +331 -69
  3. data/README.md +546 -426
  4. data/lib/sidekiq_unique_jobs/changelog.rb +2 -2
  5. data/lib/sidekiq_unique_jobs/config.rb +55 -4
  6. data/lib/sidekiq_unique_jobs/constants.rb +44 -45
  7. data/lib/sidekiq_unique_jobs/deprecation.rb +65 -0
  8. data/lib/sidekiq_unique_jobs/digests.rb +5 -8
  9. data/lib/sidekiq_unique_jobs/exceptions.rb +10 -0
  10. data/lib/sidekiq_unique_jobs/json.rb +7 -0
  11. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +64 -51
  12. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +37 -9
  13. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +23 -5
  14. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +21 -1
  15. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +27 -0
  16. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +15 -8
  17. data/lib/sidekiq_unique_jobs/lock_config.rb +8 -4
  18. data/lib/sidekiq_unique_jobs/lock_ttl.rb +1 -1
  19. data/lib/sidekiq_unique_jobs/locksmith.rb +93 -80
  20. data/lib/sidekiq_unique_jobs/logging.rb +40 -11
  21. data/lib/sidekiq_unique_jobs/lua/lock.lua +3 -3
  22. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +1 -1
  23. data/lib/sidekiq_unique_jobs/lua/unlock.lua +12 -5
  24. data/lib/sidekiq_unique_jobs/middleware/client.rb +8 -10
  25. data/lib/sidekiq_unique_jobs/middleware/server.rb +2 -0
  26. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +7 -3
  27. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +2 -13
  28. data/lib/sidekiq_unique_jobs/orphans/manager.rb +49 -3
  29. data/lib/sidekiq_unique_jobs/orphans/reaper_resurrector.rb +170 -0
  30. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +38 -8
  31. data/lib/sidekiq_unique_jobs/redis/entity.rb +7 -1
  32. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +1 -1
  33. data/lib/sidekiq_unique_jobs/reflectable.rb +26 -0
  34. data/lib/sidekiq_unique_jobs/reflections.rb +79 -0
  35. data/lib/sidekiq_unique_jobs/script/caller.rb +3 -1
  36. data/lib/sidekiq_unique_jobs/server.rb +14 -1
  37. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +35 -13
  38. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +57 -2
  39. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +1 -11
  40. data/lib/sidekiq_unique_jobs/timer_task.rb +78 -0
  41. data/lib/sidekiq_unique_jobs/timing.rb +1 -1
  42. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  43. data/lib/sidekiq_unique_jobs/web/helpers.rb +5 -5
  44. data/lib/sidekiq_unique_jobs/web/views/_paging.erb +4 -4
  45. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +1 -1
  46. data/lib/sidekiq_unique_jobs/web/views/locks.erb +17 -15
  47. data/lib/sidekiq_unique_jobs/web.rb +11 -4
  48. data/lib/sidekiq_unique_jobs.rb +7 -1
  49. data/lib/tasks/changelog.rake +15 -15
  50. metadata +17 -16
@@ -10,25 +10,36 @@ module SidekiqUniqueJobs
10
10
  module Manager
11
11
  module_function
12
12
 
13
+ #
14
+ # @return [Float] the amount to add to the reaper interval
13
15
  DRIFT_FACTOR = 0.02
16
+ #
17
+ # @return [Symbol] allowed reapers (:ruby or :lua)
14
18
  REAPERS = [:ruby, :lua].freeze
15
19
 
20
+ # includes "SidekiqUniqueJobs::Connection"
21
+ # @!parse include SidekiqUniqueJobs::Connection
16
22
  include SidekiqUniqueJobs::Connection
23
+ # includes "SidekiqUniqueJobs::Logging"
24
+ # @!parse include SidekiqUniqueJobs::Logging
17
25
  include SidekiqUniqueJobs::Logging
18
26
 
19
27
  #
20
28
  # Starts a separate thread that periodically reaps orphans
21
29
  #
22
30
  #
23
- # @return [Concurrent::TimerTask] the task that was started
31
+ # @return [SidekiqUniqueJobs::TimerTask] the task that was started
24
32
  #
25
- def start # rubocop:disable
33
+ def start(test_task = nil) # rubocop:disable
26
34
  return if disabled?
27
35
  return if registered?
28
36
 
37
+ self.task = test_task || default_task
38
+
29
39
  with_logging_context do
30
40
  register_reaper_process
31
41
  log_info("Starting Reaper")
42
+
32
43
  task.add_observer(Observer.new)
33
44
  task.execute
34
45
  task
@@ -59,7 +70,17 @@ module SidekiqUniqueJobs
59
70
  # @return [<type>] <description>
60
71
  #
61
72
  def task
62
- @task ||= Concurrent::TimerTask.new(timer_task_options) do
73
+ @task ||= default_task
74
+ end
75
+
76
+ #
77
+ # A properly configured timer task
78
+ #
79
+ #
80
+ # @return [SidekiqUniqueJobs::TimerTask]
81
+ #
82
+ def default_task
83
+ SidekiqUniqueJobs::TimerTask.new(timer_task_options) do
63
84
  with_logging_context do
64
85
  redis do |conn|
65
86
  refresh_reaper_mutex
@@ -69,6 +90,17 @@ module SidekiqUniqueJobs
69
90
  end
70
91
  end
71
92
 
93
+ #
94
+ # Store a task to use for scheduled execution
95
+ #
96
+ # @param [SidekiqUniqueJobs::TimerTask] task the task to use
97
+ #
98
+ # @return [void]
99
+ #
100
+ def task=(task)
101
+ @task = task
102
+ end
103
+
72
104
  #
73
105
  # Arguments passed on to the timer task
74
106
  #
@@ -190,10 +222,24 @@ module SidekiqUniqueJobs
190
222
  redis { |conn| conn.del(UNIQUE_REAPER) }
191
223
  end
192
224
 
225
+ #
226
+ # Reaper interval with a little drift
227
+ # Redis isn't exact enough so to give a little bufffer,
228
+ # we add a tiny value to the reaper interval.
229
+ #
230
+ #
231
+ # @return [Integer] <description>
232
+ #
193
233
  def drift_reaper_interval
194
234
  reaper_interval + (reaper_interval * DRIFT_FACTOR).to_i
195
235
  end
196
236
 
237
+ #
238
+ # Current time (as integer value)
239
+ #
240
+ #
241
+ # @return [Integer]
242
+ #
197
243
  def current_timestamp
198
244
  Time.now.to_i
199
245
  end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ module Orphans
5
+ # Restarts orphan manager if it is considered dead
6
+ module ReaperResurrector
7
+ module_function
8
+
9
+ include SidekiqUniqueJobs::Connection
10
+ include SidekiqUniqueJobs::Logging
11
+
12
+ DRIFT_FACTOR = 0.1
13
+ REAPERS = [:ruby, :lua].freeze
14
+
15
+ #
16
+ # Starts reaper resurrector that watches orphans reaper
17
+ #
18
+ # @return [SidekiqUniqueJobs::TimerTask] the task that was started
19
+ #
20
+ def start
21
+ return if resurrector_disabled?
22
+ return if reaper_disabled?
23
+
24
+ with_logging_context do
25
+ run_task
26
+ end
27
+ end
28
+
29
+ #
30
+ # Runs reaper resurrector task
31
+ #
32
+ # @return [SidekiqUniqueJobs::TimerTask]
33
+ def run_task
34
+ log_info("Starting Reaper Resurrector")
35
+ task.execute
36
+ task
37
+ end
38
+
39
+ #
40
+ # The task that runs the resurrector
41
+ #
42
+ # @return [SidekiqUniqueJobs::TimerTask]
43
+ def task
44
+ SidekiqUniqueJobs::TimerTask.new(timer_task_options) do
45
+ with_logging_context do
46
+ restart_if_dead
47
+ end
48
+ end
49
+ end
50
+
51
+ #
52
+ # Starts new instance of orphan reaper if reaper is considered dead (reaper mutex has not been refreshed lately)
53
+ #
54
+ def restart_if_dead
55
+ return if reaper_registered?
56
+
57
+ log_info("Reaper is considered dead. Starting new reaper instance")
58
+ orphans_manager.start
59
+ end
60
+
61
+ #
62
+ # Returns orphan manager
63
+ #
64
+ # @return [SidekiqUniqueJobs::Orphans::Manager]
65
+ def orphans_manager
66
+ SidekiqUniqueJobs::Orphans::Manager
67
+ end
68
+
69
+ #
70
+ # Checks if resurrector is disabled
71
+ #
72
+ # @see resurrector_enabled?
73
+ #
74
+ # @return [true, false]
75
+ def resurrector_disabled?
76
+ !resurrector_enabled?
77
+ end
78
+
79
+ #
80
+ # Checks if resurrector is enabled
81
+ #
82
+ # @return [true, false]
83
+ def resurrector_enabled?
84
+ SidekiqUniqueJobs.config.reaper_resurrector_enabled
85
+ end
86
+
87
+ #
88
+ # Checks if reaping is disabled
89
+ #
90
+ # @see reaper_enabled?
91
+ #
92
+ # @return [true, false]
93
+ #
94
+ def reaper_disabled?
95
+ !reaper_enabled?
96
+ end
97
+
98
+ #
99
+ # Checks if reaping is enabled
100
+ #
101
+ # @return [true, false]
102
+ #
103
+ def reaper_enabled?
104
+ REAPERS.include?(reaper)
105
+ end
106
+
107
+ #
108
+ # Checks if reaper is registered
109
+ #
110
+ # @return [true, false]
111
+ def reaper_registered?
112
+ redis do |conn|
113
+ conn.get(UNIQUE_REAPER).to_i + drift_reaper_interval > current_timestamp
114
+ end
115
+ end
116
+
117
+ #
118
+ # @see SidekiqUniqueJobs::Config#reaper
119
+ #
120
+ def reaper
121
+ SidekiqUniqueJobs.config.reaper
122
+ end
123
+
124
+ #
125
+ # Arguments passed on to the timer task
126
+ #
127
+ #
128
+ # @return [Hash]
129
+ #
130
+ def timer_task_options
131
+ { run_now: false,
132
+ execution_interval: reaper_resurrector_interval }
133
+ end
134
+
135
+ #
136
+ # A context to use for all log entries
137
+ #
138
+ #
139
+ # @return [Hash] when logger responds to `:with_context`
140
+ # @return [String] when logger does not responds to `:with_context`
141
+ #
142
+ def logging_context
143
+ if logger_context_hash?
144
+ { "uniquejobs" => "reaper-resurrector" }
145
+ else
146
+ "uniquejobs=reaper-resurrector"
147
+ end
148
+ end
149
+
150
+ #
151
+ # @see SidekiqUniqueJobs::Config#reaper_resurrector_interval
152
+ #
153
+ def reaper_resurrector_interval
154
+ SidekiqUniqueJobs.config.reaper_resurrector_interval
155
+ end
156
+
157
+ def reaper_interval
158
+ SidekiqUniqueJobs.config.reaper_interval
159
+ end
160
+
161
+ def drift_reaper_interval
162
+ reaper_interval + (reaper_interval * DRIFT_FACTOR).to_i
163
+ end
164
+
165
+ def current_timestamp
166
+ Time.now.to_i
167
+ end
168
+ end
169
+ end
170
+ end
@@ -9,7 +9,11 @@ module SidekiqUniqueJobs
9
9
  #
10
10
  # @author Mikael Henriksson <mikael@mhenrixon.com>
11
11
  #
12
+ # rubocop:disable Metrics/ClassLength
12
13
  class RubyReaper < Reaper
14
+ #
15
+ # @return [String] the suffix for :RUN locks
16
+ RUN_SUFFIX = ":RUN"
13
17
  #
14
18
  # @!attribute [r] digests
15
19
  # @return [SidekiqUniqueJobs::Digests] digest collection
@@ -51,13 +55,27 @@ module SidekiqUniqueJobs
51
55
  #
52
56
  # @return [Array<String>] an array of orphaned digests
53
57
  #
54
- def orphans
55
- conn.zrevrange(digests.key, 0, -1).each_with_object([]) do |digest, memo|
56
- next if belongs_to_job?(digest)
58
+ def orphans # rubocop:disable Metrics/MethodLength
59
+ page = 0
60
+ per = reaper_count * 2
61
+ orphans = []
62
+ results = conn.zrange(digests.key, page * per, (page + 1) * per)
63
+
64
+ while results.size.positive?
65
+ results.each do |digest|
66
+ next if belongs_to_job?(digest)
67
+
68
+ orphans << digest
69
+ break if orphans.size >= reaper_count
70
+ end
71
+
72
+ break if orphans.size >= reaper_count
57
73
 
58
- memo << digest
59
- break if memo.size >= reaper_count
74
+ page += 1
75
+ results = conn.zrange(digests.key, page * per, (page + 1) * per)
60
76
  end
77
+
78
+ orphans
61
79
  end
62
80
 
63
81
  #
@@ -124,7 +142,12 @@ module SidekiqUniqueJobs
124
142
 
125
143
  procs.sort.each do |key|
126
144
  valid, workers = conn.pipelined do
127
- conn.exists(key)
145
+ # TODO: Remove the if statement in the future
146
+ if conn.respond_to?(:exists?)
147
+ conn.exists?(key)
148
+ else
149
+ conn.exists(key)
150
+ end
128
151
  conn.hgetall("#{key}:workers")
129
152
  end
130
153
 
@@ -136,7 +159,7 @@ module SidekiqUniqueJobs
136
159
 
137
160
  payload = safe_load_json(item[PAYLOAD])
138
161
 
139
- return true if payload[LOCK_DIGEST] == digest
162
+ return true if match?(digest, payload[LOCK_DIGEST])
140
163
  return true if considered_active?(payload[CREATED_AT])
141
164
  end
142
165
  end
@@ -145,6 +168,12 @@ module SidekiqUniqueJobs
145
168
  end
146
169
  end
147
170
 
171
+ def match?(key_one, key_two)
172
+ return false if key_one.nil? || key_two.nil?
173
+
174
+ key_one.delete_suffix(RUN_SUFFIX) == key_two.delete_suffix(RUN_SUFFIX)
175
+ end
176
+
148
177
  def considered_active?(time_f)
149
178
  (Time.now - reaper_timeout).to_f < time_f
150
179
  end
@@ -170,7 +199,7 @@ module SidekiqUniqueJobs
170
199
  page_size = 50
171
200
 
172
201
  loop do
173
- range_start = page * page_size - deleted_size
202
+ range_start = (page * page_size) - deleted_size
174
203
  range_end = range_start + page_size - 1
175
204
  entries = conn.lrange(queue_key, range_start, range_end)
176
205
  page += 1
@@ -197,5 +226,6 @@ module SidekiqUniqueJobs
197
226
  conn.zscan_each(key, match: "*#{digest}*", count: 1).to_a.any?
198
227
  end
199
228
  end
229
+ # rubocop:enable Metrics/ClassLength
200
230
  end
201
231
  end
@@ -47,7 +47,13 @@ module SidekiqUniqueJobs
47
47
  #
48
48
  def exist?
49
49
  redis do |conn|
50
- value = conn.exists(key)
50
+ # TODO: Remove the if statement in the future
51
+ value =
52
+ if conn.respond_to?(:exists?)
53
+ conn.exists?(key)
54
+ else
55
+ conn.exists(key)
56
+ end
51
57
 
52
58
  return value if boolean?(value)
53
59
 
@@ -26,7 +26,7 @@ module SidekiqUniqueJobs
26
26
  #
27
27
  # Adds a value to the sorted set
28
28
  #
29
- # @param [Array<Float, String>, String] the values to add
29
+ # @param [Array<Float, String>, String] values the values to add
30
30
  #
31
31
  # @return [Boolean, Integer] <description>
32
32
  #
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Module Reflectable provides a method to notify subscribers
6
+ #
7
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
8
+ #
9
+ module Reflectable
10
+ #
11
+ # Reflects on specific event
12
+ #
13
+ # @param [Symbol] reflection the reflected event
14
+ # @param [Array] args arguments to provide to reflector
15
+ #
16
+ # @return [void]
17
+ #
18
+ def reflect(reflection, *args)
19
+ SidekiqUniqueJobs.reflections.dispatch(reflection, *args)
20
+ nil
21
+ rescue UniqueJobsError => ex
22
+ SidekiqUniqueJobs.logger.error(ex)
23
+ nil
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Class NotificationCollection provides a collection with known notifications
6
+ #
7
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
8
+ #
9
+ class Reflections
10
+ #
11
+ # @return [Array<Symbol>] list of notifications
12
+ REFLECTIONS = [
13
+ :after_unlock_callback_failed,
14
+ :debug,
15
+ :duplicate,
16
+ :error,
17
+ :execution_failed,
18
+ :lock_failed,
19
+ :locked,
20
+ :reschedule_failed,
21
+ :rescheduled,
22
+ :timeout,
23
+ :unknown_sidekiq_worker,
24
+ :unlock_failed,
25
+ :unlocked,
26
+ ].freeze
27
+
28
+ #
29
+ # @return [Hash<Symbol, Array<Symbol, String>>] a hash with deprecated notifications
30
+ DEPRECATIONS = {}.freeze
31
+
32
+ REFLECTIONS.each do |reflection|
33
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
34
+ def #{reflection}(*args, &block) # def unlock_failed(*args, &block)
35
+ raise NoBlockGiven, "block required" unless block_given? # raise NoBlockGiven, "block required" unless block_given?
36
+ @reflections[:#{reflection}] = block # @notifications[:unlock_failed] = block
37
+ end # end
38
+ RUBY
39
+ end
40
+
41
+ def initialize
42
+ @reflections = {}
43
+ end
44
+
45
+ #
46
+ # Dispatch a reflected event
47
+ #
48
+ # @param [reflection] reflection the reflected event
49
+ # @param [Array] args the arguments to provide to the block
50
+ #
51
+ # @return [void] <description>
52
+ #
53
+ def dispatch(reflection, *args)
54
+ if (block = @reflections[reflection])
55
+ block.call(*args)
56
+
57
+ if DEPRECATIONS.key?(reflection)
58
+ replacement, removal_version = DEPRECATIONS[reflection]
59
+ SidekiqUniqueJobs::Deprecation.warn(
60
+ "#{reflection} is deprecated and will be removed in version #{removal_version}." \
61
+ " Use #{replacement} instead.",
62
+ )
63
+ end
64
+ elsif misconfigured?(reflection)
65
+ raise NoSuchNotificationError, reflection
66
+ end
67
+
68
+ nil
69
+ end
70
+
71
+ def configured?(reflection)
72
+ REFLECTIONS.include?(reflection)
73
+ end
74
+
75
+ def misconfigured?(reflection)
76
+ !configured?(reflection)
77
+ end
78
+ end
79
+ end
@@ -45,7 +45,9 @@ module SidekiqUniqueJobs
45
45
  pool = defined?(redis_pool) ? redis_pool : nil
46
46
 
47
47
  redis(pool) do |new_conn|
48
- do_call(file_name, new_conn, keys, argv)
48
+ result = do_call(file_name, new_conn, keys, argv)
49
+ yield result if block_given?
50
+ result
49
51
  end
50
52
  end
51
53
 
@@ -5,7 +5,7 @@ module SidekiqUniqueJobs
5
5
  #
6
6
  # @author Mikael Henriksson <mikael@mhenrixon.com>
7
7
  class Server
8
- DEATH_HANDLER ||= (lambda do |job, _ex|
8
+ DEATH_HANDLER = (lambda do |job, _ex|
9
9
  return unless (digest = job["lock_digest"])
10
10
 
11
11
  SidekiqUniqueJobs::Digests.new.delete_by_digest(digest)
@@ -25,12 +25,25 @@ module SidekiqUniqueJobs
25
25
  config.death_handlers << death_handler
26
26
  end
27
27
 
28
+ #
29
+ # Start the sidekiq unique jobs server process
30
+ #
31
+ #
32
+ # @return [void]
33
+ #
28
34
  def self.start
29
35
  SidekiqUniqueJobs::UpdateVersion.call
30
36
  SidekiqUniqueJobs::UpgradeLocks.call
31
37
  SidekiqUniqueJobs::Orphans::Manager.start
38
+ SidekiqUniqueJobs::Orphans::ReaperResurrector.start
32
39
  end
33
40
 
41
+ #
42
+ # Stop the sidekiq unique jobs server process
43
+ #
44
+ #
45
+ # @return [void]
46
+ #
34
47
  def self.stop
35
48
  SidekiqUniqueJobs::Orphans::Manager.stop
36
49
  end
@@ -68,24 +68,46 @@ module Sidekiq
68
68
  prepend UniqueExtension
69
69
  end
70
70
 
71
- # See Sidekiq::Api
72
- class Job
73
- #
74
- # Provides extensions for unlocking jobs that are removed and deleted
75
- #
76
- # @author Mikael Henriksson <mikael@mhenrixon.com>
77
- #
78
- module UniqueExtension
71
+ if Sidekiq.const_defined?("JobRecord")
72
+ # See Sidekiq::Api
73
+ class JobRecord
79
74
  #
80
- # Wraps the original method to ensure locks for the job are deleted
75
+ # Provides extensions for unlocking jobs that are removed and deleted
81
76
  #
82
- def delete
83
- SidekiqUniqueJobs::Unlockable.delete!(item)
84
- super
77
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
78
+ #
79
+ module UniqueExtension
80
+ #
81
+ # Wraps the original method to ensure locks for the job are deleted
82
+ #
83
+ def delete
84
+ SidekiqUniqueJobs::Unlockable.delete!(item)
85
+ super
86
+ end
85
87
  end
88
+
89
+ prepend UniqueExtension
86
90
  end
91
+ else
92
+ # See Sidekiq::Api
93
+ class Job
94
+ #
95
+ # Provides extensions for unlocking jobs that are removed and deleted
96
+ #
97
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
98
+ #
99
+ module UniqueExtension
100
+ #
101
+ # Wraps the original method to ensure locks for the job are deleted
102
+ #
103
+ def delete
104
+ SidekiqUniqueJobs::Unlockable.delete!(item)
105
+ super
106
+ end
107
+ end
87
108
 
88
- prepend UniqueExtension
109
+ prepend UniqueExtension
110
+ end
89
111
  end
90
112
 
91
113
  # See Sidekiq::Api
@@ -4,7 +4,7 @@
4
4
  # Contains configuration and utility methods that belongs top level
5
5
  #
6
6
  # @author Mikael Henriksson <mikael@mhenrixon.com>
7
- module SidekiqUniqueJobs
7
+ module SidekiqUniqueJobs # rubocop:disable Metrics/ModuleLength
8
8
  include SidekiqUniqueJobs::Connection
9
9
  extend SidekiqUniqueJobs::JSON
10
10
 
@@ -188,7 +188,7 @@ module SidekiqUniqueJobs
188
188
  # @return [String] a string like `5.0.2`
189
189
  #
190
190
  def fetch_redis_version
191
- redis { |conn| conn.info("server")["redis_version"] }
191
+ Sidekiq.redis_info["redis_version"]
192
192
  end
193
193
 
194
194
  #
@@ -236,4 +236,59 @@ module SidekiqUniqueJobs
236
236
  lock_config = validate_worker(options)
237
237
  raise InvalidWorker, lock_config unless lock_config.errors.empty?
238
238
  end
239
+
240
+ # Attempt to constantize a string worker_class argument, always
241
+ # failing back to the original argument when the constant can't be found
242
+ #
243
+ # @return [Sidekiq::Worker]
244
+ def constantize(str)
245
+ return str.class if str.is_a?(Sidekiq::Worker) # sidekiq v6.x
246
+ return str unless str.is_a?(String)
247
+ return Object.const_get(str) unless str.include?("::")
248
+
249
+ names = str.split("::")
250
+ names.shift if names.empty? || names.first.empty?
251
+
252
+ names.inject(Object) do |constant, name|
253
+ # the false flag limits search for name to under the constant namespace
254
+ # which mimics Rails' behaviour
255
+ constant.const_get(name, false)
256
+ end
257
+ end
258
+
259
+ # Attempt to constantize a string worker_class argument, always
260
+ # failing back to the original argument when the constant can't be found
261
+ #
262
+ # @return [Sidekiq::Worker, String]
263
+ def safe_constantize(str)
264
+ constantize(str)
265
+ rescue NameError => ex
266
+ case ex.message
267
+ when /uninitialized constant/
268
+ str
269
+ else
270
+ raise
271
+ end
272
+ end
273
+
274
+ #
275
+ # Collection with notifications
276
+ #
277
+ #
278
+ # @return [Reflections]
279
+ #
280
+ def reflections
281
+ @reflections ||= Reflections.new
282
+ end
283
+
284
+ #
285
+ # Yields notification stack for sidekiq unique jobs to configure notifications
286
+ #
287
+ #
288
+ # @return [void] <description>
289
+ #
290
+ # @yieldparam [Reflections] x used to configure notifications
291
+ def reflect
292
+ yield reflections if block_given?
293
+ end
239
294
  end
@@ -49,17 +49,7 @@ module SidekiqUniqueJobs
49
49
  #
50
50
  # @return [Sidekiq::Worker]
51
51
  def worker_class_constantize(klazz = @worker_class)
52
- return klazz.class if klazz.is_a?(Sidekiq::Worker) # sidekiq v6.x
53
- return klazz unless klazz.is_a?(String)
54
-
55
- Object.const_get(klazz)
56
- rescue NameError => ex
57
- case ex.message
58
- when /uninitialized constant/
59
- klazz
60
- else
61
- raise
62
- end
52
+ SidekiqUniqueJobs.safe_constantize(klazz)
63
53
  end
64
54
 
65
55
  #