sidekiq-unique-jobs 6.0.24 → 7.0.4

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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +707 -25
  3. data/README.md +516 -105
  4. data/lib/sidekiq_unique_jobs.rb +48 -7
  5. data/lib/sidekiq_unique_jobs/batch_delete.rb +123 -0
  6. data/lib/sidekiq_unique_jobs/changelog.rb +78 -0
  7. data/lib/sidekiq_unique_jobs/cli.rb +34 -31
  8. data/lib/sidekiq_unique_jobs/config.rb +263 -0
  9. data/lib/sidekiq_unique_jobs/connection.rb +6 -5
  10. data/lib/sidekiq_unique_jobs/constants.rb +46 -24
  11. data/lib/sidekiq_unique_jobs/core_ext.rb +80 -0
  12. data/lib/sidekiq_unique_jobs/digests.rb +71 -100
  13. data/lib/sidekiq_unique_jobs/exceptions.rb +78 -12
  14. data/lib/sidekiq_unique_jobs/job.rb +41 -12
  15. data/lib/sidekiq_unique_jobs/json.rb +40 -0
  16. data/lib/sidekiq_unique_jobs/key.rb +93 -0
  17. data/lib/sidekiq_unique_jobs/lock.rb +325 -0
  18. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +66 -50
  19. data/lib/sidekiq_unique_jobs/lock/client_validator.rb +28 -0
  20. data/lib/sidekiq_unique_jobs/lock/server_validator.rb +27 -0
  21. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +7 -10
  22. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +6 -6
  23. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +1 -1
  24. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +4 -21
  25. data/lib/sidekiq_unique_jobs/lock/validator.rb +96 -0
  26. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +13 -9
  27. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +3 -3
  28. data/lib/sidekiq_unique_jobs/lock_args.rb +123 -0
  29. data/lib/sidekiq_unique_jobs/lock_config.rb +122 -0
  30. data/lib/sidekiq_unique_jobs/lock_digest.rb +79 -0
  31. data/lib/sidekiq_unique_jobs/lock_info.rb +68 -0
  32. data/lib/sidekiq_unique_jobs/lock_timeout.rb +62 -0
  33. data/lib/sidekiq_unique_jobs/lock_ttl.rb +77 -0
  34. data/lib/sidekiq_unique_jobs/locksmith.rb +261 -101
  35. data/lib/sidekiq_unique_jobs/logging.rb +149 -23
  36. data/lib/sidekiq_unique_jobs/logging/middleware_context.rb +44 -0
  37. data/lib/sidekiq_unique_jobs/lua/delete.lua +51 -0
  38. data/lib/sidekiq_unique_jobs/lua/delete_by_digest.lua +42 -0
  39. data/lib/sidekiq_unique_jobs/lua/delete_job_by_digest.lua +38 -0
  40. data/lib/sidekiq_unique_jobs/lua/find_digest_in_queues.lua +26 -0
  41. data/lib/sidekiq_unique_jobs/lua/lock.lua +93 -0
  42. data/lib/sidekiq_unique_jobs/lua/locked.lua +35 -0
  43. data/lib/sidekiq_unique_jobs/lua/queue.lua +87 -0
  44. data/lib/sidekiq_unique_jobs/lua/reap_orphans.lua +94 -0
  45. data/lib/sidekiq_unique_jobs/lua/shared/_common.lua +40 -0
  46. data/lib/sidekiq_unique_jobs/lua/shared/_current_time.lua +8 -0
  47. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_queue.lua +22 -0
  48. data/lib/sidekiq_unique_jobs/lua/shared/_delete_from_sorted_set.lua +18 -0
  49. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_process_set.lua +53 -0
  50. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_queues.lua +43 -0
  51. data/lib/sidekiq_unique_jobs/lua/shared/_find_digest_in_sorted_set.lua +24 -0
  52. data/lib/sidekiq_unique_jobs/lua/shared/_hgetall.lua +13 -0
  53. data/lib/sidekiq_unique_jobs/lua/shared/_upgrades.lua +3 -0
  54. data/lib/sidekiq_unique_jobs/lua/unlock.lua +95 -0
  55. data/lib/sidekiq_unique_jobs/lua/update_version.lua +40 -0
  56. data/lib/sidekiq_unique_jobs/lua/upgrade.lua +68 -0
  57. data/lib/sidekiq_unique_jobs/middleware.rb +29 -31
  58. data/lib/sidekiq_unique_jobs/middleware/client.rb +42 -0
  59. data/lib/sidekiq_unique_jobs/middleware/server.rb +27 -0
  60. data/lib/sidekiq_unique_jobs/normalizer.rb +4 -4
  61. data/lib/sidekiq_unique_jobs/on_conflict.rb +23 -10
  62. data/lib/sidekiq_unique_jobs/on_conflict/log.rb +9 -5
  63. data/lib/sidekiq_unique_jobs/on_conflict/null_strategy.rb +1 -1
  64. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +1 -1
  65. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +61 -15
  66. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +54 -14
  67. data/lib/sidekiq_unique_jobs/on_conflict/reschedule.rb +12 -5
  68. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +25 -6
  69. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +41 -27
  70. data/lib/sidekiq_unique_jobs/orphans/lua_reaper.rb +29 -0
  71. data/lib/sidekiq_unique_jobs/orphans/manager.rb +212 -0
  72. data/lib/sidekiq_unique_jobs/orphans/null_reaper.rb +24 -0
  73. data/lib/sidekiq_unique_jobs/orphans/observer.rb +42 -0
  74. data/lib/sidekiq_unique_jobs/orphans/reaper.rb +114 -0
  75. data/lib/sidekiq_unique_jobs/orphans/ruby_reaper.rb +201 -0
  76. data/lib/sidekiq_unique_jobs/redis.rb +11 -0
  77. data/lib/sidekiq_unique_jobs/redis/entity.rb +106 -0
  78. data/lib/sidekiq_unique_jobs/redis/hash.rb +56 -0
  79. data/lib/sidekiq_unique_jobs/redis/list.rb +32 -0
  80. data/lib/sidekiq_unique_jobs/redis/set.rb +32 -0
  81. data/lib/sidekiq_unique_jobs/redis/sorted_set.rb +86 -0
  82. data/lib/sidekiq_unique_jobs/redis/string.rb +49 -0
  83. data/lib/sidekiq_unique_jobs/rspec/matchers.rb +26 -0
  84. data/lib/sidekiq_unique_jobs/rspec/matchers/have_valid_sidekiq_options.rb +51 -0
  85. data/lib/sidekiq_unique_jobs/script.rb +15 -0
  86. data/lib/sidekiq_unique_jobs/script/caller.rb +125 -0
  87. data/lib/sidekiq_unique_jobs/server.rb +48 -0
  88. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +92 -65
  89. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +185 -34
  90. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +11 -5
  91. data/lib/sidekiq_unique_jobs/testing.rb +62 -21
  92. data/lib/sidekiq_unique_jobs/timer_task.rb +78 -0
  93. data/lib/sidekiq_unique_jobs/timing.rb +58 -0
  94. data/lib/sidekiq_unique_jobs/unlockable.rb +20 -4
  95. data/lib/sidekiq_unique_jobs/update_version.rb +25 -0
  96. data/lib/sidekiq_unique_jobs/upgrade_locks.rb +155 -0
  97. data/lib/sidekiq_unique_jobs/version.rb +3 -1
  98. data/lib/sidekiq_unique_jobs/version_check.rb +23 -4
  99. data/lib/sidekiq_unique_jobs/web.rb +50 -27
  100. data/lib/sidekiq_unique_jobs/web/helpers.rb +125 -10
  101. data/lib/sidekiq_unique_jobs/web/views/changelogs.erb +54 -0
  102. data/lib/sidekiq_unique_jobs/web/views/lock.erb +108 -0
  103. data/lib/sidekiq_unique_jobs/web/views/locks.erb +52 -0
  104. data/lib/tasks/changelog.rake +5 -5
  105. metadata +117 -177
  106. data/lib/sidekiq_unique_jobs/client/middleware.rb +0 -56
  107. data/lib/sidekiq_unique_jobs/scripts.rb +0 -118
  108. data/lib/sidekiq_unique_jobs/server/middleware.rb +0 -46
  109. data/lib/sidekiq_unique_jobs/timeout.rb +0 -8
  110. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +0 -63
  111. data/lib/sidekiq_unique_jobs/unique_args.rb +0 -150
  112. data/lib/sidekiq_unique_jobs/util.rb +0 -103
  113. data/lib/sidekiq_unique_jobs/web/views/unique_digest.erb +0 -28
  114. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +0 -46
  115. data/redis/acquire_lock.lua +0 -21
  116. data/redis/convert_legacy_lock.lua +0 -13
  117. data/redis/delete.lua +0 -14
  118. data/redis/delete_by_digest.lua +0 -23
  119. data/redis/delete_job_by_digest.lua +0 -60
  120. data/redis/lock.lua +0 -62
  121. data/redis/release_stale_locks.lua +0 -90
  122. data/redis/unlock.lua +0 -35
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ module Orphans
5
+ #
6
+ # Manages the orphan reaper
7
+ #
8
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
9
+ #
10
+ module Manager
11
+ module_function
12
+
13
+ DRIFT_FACTOR = 0.02
14
+ REAPERS = [:ruby, :lua].freeze
15
+
16
+ include SidekiqUniqueJobs::Connection
17
+ include SidekiqUniqueJobs::Logging
18
+
19
+ #
20
+ # Starts a separate thread that periodically reaps orphans
21
+ #
22
+ #
23
+ # @return [SidekiqUniqueJobs::TimerTask] the task that was started
24
+ #
25
+ def start(test_task = nil) # rubocop:disable
26
+ return if disabled?
27
+ return if registered?
28
+
29
+ self.task = test_task || default_task
30
+
31
+ with_logging_context do
32
+ register_reaper_process
33
+ log_info("Starting Reaper")
34
+ task.add_observer(Observer.new)
35
+ task.execute
36
+ task
37
+ end
38
+ end
39
+
40
+ #
41
+ # Stops the thread that reaps orphans
42
+ #
43
+ #
44
+ # @return [Boolean]
45
+ #
46
+ def stop
47
+ return if disabled?
48
+ return if unregistered?
49
+
50
+ with_logging_context do
51
+ log_info("Stopping Reaper")
52
+ unregister_reaper_process
53
+ task.shutdown
54
+ end
55
+ end
56
+
57
+ #
58
+ # The task that runs the reaper
59
+ #
60
+ #
61
+ # @return [<type>] <description>
62
+ #
63
+ def task
64
+ @task ||= default_task
65
+ end
66
+
67
+ def default_task
68
+ SidekiqUniqueJobs::TimerTask.new(timer_task_options) do
69
+ with_logging_context do
70
+ redis do |conn|
71
+ refresh_reaper_mutex
72
+ Orphans::Reaper.call(conn)
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def task=(task)
79
+ @task = task
80
+ end
81
+
82
+ #
83
+ # Arguments passed on to the timer task
84
+ #
85
+ #
86
+ # @return [Hash]
87
+ #
88
+ def timer_task_options
89
+ { run_now: true,
90
+ execution_interval: reaper_interval,
91
+ timeout_interval: reaper_timeout }
92
+ end
93
+
94
+ #
95
+ # @see SidekiqUniqueJobs::Config#reaper
96
+ #
97
+ def reaper
98
+ SidekiqUniqueJobs.config.reaper
99
+ end
100
+
101
+ #
102
+ # @see SidekiqUniqueJobs::Config#reaper_interval
103
+ #
104
+ def reaper_interval
105
+ SidekiqUniqueJobs.config.reaper_interval
106
+ end
107
+
108
+ #
109
+ # @see SidekiqUniqueJobs::Config#reaper_timeout
110
+ #
111
+ def reaper_timeout
112
+ SidekiqUniqueJobs.config.reaper_timeout
113
+ end
114
+
115
+ #
116
+ # A context to use for all log entries
117
+ #
118
+ #
119
+ # @return [Hash] when logger responds to `:with_context`
120
+ # @return [String] when logger does not responds to `:with_context`
121
+ #
122
+ def logging_context
123
+ if logger_context_hash?
124
+ { "uniquejobs" => "reaper" }
125
+ else
126
+ "uniquejobs=orphan-reaper"
127
+ end
128
+ end
129
+
130
+ #
131
+ # Checks if a reaper is registered
132
+ #
133
+ #
134
+ # @return [true, false]
135
+ #
136
+ def registered?
137
+ redis do |conn|
138
+ conn.get(UNIQUE_REAPER).to_i + drift_reaper_interval > current_timestamp
139
+ end
140
+ end
141
+
142
+ #
143
+ # Checks if that reapers are not registerd
144
+ #
145
+ # @see registered?
146
+ #
147
+ # @return [true, false]
148
+ #
149
+ def unregistered?
150
+ !registered?
151
+ end
152
+
153
+ #
154
+ # Checks if reaping is disabled
155
+ #
156
+ # @see enabled?
157
+ #
158
+ # @return [true, false]
159
+ #
160
+ def disabled?
161
+ !enabled?
162
+ end
163
+
164
+ #
165
+ # Checks if reaping is enabled
166
+ #
167
+ # @return [true, false]
168
+ #
169
+ def enabled?
170
+ REAPERS.include?(reaper)
171
+ end
172
+
173
+ #
174
+ # Writes a mutex key to redis
175
+ #
176
+ #
177
+ # @return [void]
178
+ #
179
+ def register_reaper_process
180
+ redis { |conn| conn.set(UNIQUE_REAPER, current_timestamp, nx: true, ex: drift_reaper_interval) }
181
+ end
182
+
183
+ #
184
+ # Updates mutex key
185
+ #
186
+ #
187
+ # @return [void]
188
+ #
189
+ def refresh_reaper_mutex
190
+ redis { |conn| conn.set(UNIQUE_REAPER, current_timestamp, ex: drift_reaper_interval) }
191
+ end
192
+
193
+ #
194
+ # Removes mutex key from redis
195
+ #
196
+ #
197
+ # @return [void]
198
+ #
199
+ def unregister_reaper_process
200
+ redis { |conn| conn.del(UNIQUE_REAPER) }
201
+ end
202
+
203
+ def drift_reaper_interval
204
+ reaper_interval + (reaper_interval * DRIFT_FACTOR).to_i
205
+ end
206
+
207
+ def current_timestamp
208
+ Time.now.to_i
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ module Orphans
5
+ #
6
+ # Class DeleteOrphans provides deletion of orphaned digests
7
+ #
8
+ # @note this is a much slower version of the lua script but does not crash redis
9
+ #
10
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
11
+ #
12
+ class NullReaper < Reaper
13
+ #
14
+ # Delete orphaned digests
15
+ #
16
+ #
17
+ # @return [Integer] the number of reaped locks
18
+ #
19
+ def call
20
+ # NO OP
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Class DeleteOrphans provides deletion of orphaned digests
6
+ #
7
+ # @note this is a much slower version of the lua script but does not crash redis
8
+ #
9
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
10
+ #
11
+ module Orphans
12
+ #
13
+ # Observes the Orphan::Manager and provides information about each execution
14
+ #
15
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
16
+ #
17
+ class Observer
18
+ include SidekiqUniqueJobs::Logging
19
+
20
+ #
21
+ # Runs every time the {Manager} executes the TimerTask
22
+ # used for logging information about the reaping
23
+ #
24
+ # @param [Time] time the time of the execution
25
+ # @param [Object] result the result of the execution
26
+ # @param [Exception] ex any error raised from the TimerTask
27
+ #
28
+ # @return [<type>] <description>
29
+ #
30
+ def update(time, result, ex)
31
+ if result
32
+ log_info("(#{time}) Execution successfully returned #{result}")
33
+ elsif ex.is_a?(Concurrent::TimeoutError)
34
+ log_warn("(#{time}) Execution timed out")
35
+ else
36
+ log_info("(#{time}) Cleanup failed with error #{ex.message}")
37
+ log_error(ex)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ module Orphans
5
+ #
6
+ # Class DeleteOrphans provides deletion of orphaned digests
7
+ #
8
+ # @note this is a much slower version of the lua script but does not crash redis
9
+ #
10
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
11
+ #
12
+ class Reaper
13
+ include SidekiqUniqueJobs::Connection
14
+ include SidekiqUniqueJobs::Script::Caller
15
+ include SidekiqUniqueJobs::Logging
16
+ include SidekiqUniqueJobs::JSON
17
+
18
+ require_relative "lua_reaper"
19
+ require_relative "ruby_reaper"
20
+ require_relative "null_reaper"
21
+
22
+ #
23
+ # @return [Hash<Symbol, SidekiqUniqueJobs::Orphans::Reaper] the current implementation of reapers
24
+ REAPERS = {
25
+ lua: SidekiqUniqueJobs::Orphans::LuaReaper,
26
+ ruby: SidekiqUniqueJobs::Orphans::RubyReaper,
27
+ none: SidekiqUniqueJobs::Orphans::NullReaper,
28
+ nil => SidekiqUniqueJobs::Orphans::NullReaper,
29
+ false => SidekiqUniqueJobs::Orphans::NullReaper,
30
+ }.freeze
31
+
32
+ #
33
+ # Execute deletion of orphaned digests
34
+ #
35
+ # @param [Redis] conn nil a connection to redis
36
+ #
37
+ # @return [void]
38
+ #
39
+ def self.call(conn = nil)
40
+ return new(conn).call if conn
41
+
42
+ redis { |rcon| new(rcon).call }
43
+ end
44
+
45
+ #
46
+ # @!attribute [r] conn
47
+ # @return [Redis] a redis connection
48
+ attr_reader :conn
49
+
50
+ #
51
+ # Initialize a new instance of DeleteOrphans
52
+ #
53
+ # @param [Redis] conn a connection to redis
54
+ #
55
+ def initialize(conn)
56
+ @conn = conn
57
+ end
58
+
59
+ #
60
+ # Convenient access to the global configuration
61
+ #
62
+ #
63
+ # @return [SidekiqUniqueJobs::Config]
64
+ #
65
+ def config
66
+ SidekiqUniqueJobs.config
67
+ end
68
+
69
+ #
70
+ # The reaper that was configured
71
+ #
72
+ #
73
+ # @return [Symbol]
74
+ #
75
+ def reaper
76
+ config.reaper
77
+ end
78
+
79
+ #
80
+ # The configured timeout for the reaper
81
+ #
82
+ #
83
+ # @return [Integer] timeout in seconds
84
+ #
85
+ def reaper_timeout
86
+ config.reaper_timeout
87
+ end
88
+
89
+ #
90
+ # The number of locks to reap at a time
91
+ #
92
+ #
93
+ # @return [Integer]
94
+ #
95
+ def reaper_count
96
+ config.reaper_count
97
+ end
98
+
99
+ #
100
+ # Delete orphaned digests
101
+ #
102
+ #
103
+ # @return [Integer] the number of reaped locks
104
+ #
105
+ def call
106
+ if (implementation = REAPERS[reaper])
107
+ implementation.new(conn).call
108
+ else
109
+ log_fatal(":#{reaper} is invalid for `SidekiqUnqiueJobs.config.reaper`")
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ module Orphans
5
+ #
6
+ # Class DeleteOrphans provides deletion of orphaned digests
7
+ #
8
+ # @note this is a much slower version of the lua script but does not crash redis
9
+ #
10
+ # @author Mikael Henriksson <mikael@mhenrixon.com>
11
+ #
12
+ class RubyReaper < Reaper
13
+ #
14
+ # @!attribute [r] digests
15
+ # @return [SidekiqUniqueJobs::Digests] digest collection
16
+ attr_reader :digests
17
+ #
18
+ # @!attribute [r] scheduled
19
+ # @return [Redis::SortedSet] the Sidekiq ScheduleSet
20
+ attr_reader :scheduled
21
+ #
22
+ # @!attribute [r] retried
23
+ # @return [Redis::SortedSet] the Sidekiq RetrySet
24
+ attr_reader :retried
25
+
26
+ #
27
+ # Initialize a new instance of DeleteOrphans
28
+ #
29
+ # @param [Redis] conn a connection to redis
30
+ #
31
+ def initialize(conn)
32
+ super(conn)
33
+ @digests = SidekiqUniqueJobs::Digests.new
34
+ @scheduled = Redis::SortedSet.new(SCHEDULE)
35
+ @retried = Redis::SortedSet.new(RETRY)
36
+ end
37
+
38
+ #
39
+ # Delete orphaned digests
40
+ #
41
+ #
42
+ # @return [Integer] the number of reaped locks
43
+ #
44
+ def call
45
+ BatchDelete.call(orphans, conn)
46
+ end
47
+
48
+ #
49
+ # Find orphaned digests
50
+ #
51
+ #
52
+ # @return [Array<String>] an array of orphaned digests
53
+ #
54
+ def orphans
55
+ conn.zrevrange(digests.key, 0, -1).each_with_object([]) do |digest, memo|
56
+ next if belongs_to_job?(digest)
57
+
58
+ memo << digest
59
+ break if memo.size >= reaper_count
60
+ end
61
+ end
62
+
63
+ #
64
+ # Checks if the digest has a matching job.
65
+ # 1. It checks the scheduled set
66
+ # 2. It checks the retry set
67
+ # 3. It goes through all queues
68
+ #
69
+ #
70
+ # @param [String] digest the digest to search for
71
+ #
72
+ # @return [true] when either of the checks return true
73
+ # @return [false] when no job was found for this digest
74
+ #
75
+ def belongs_to_job?(digest)
76
+ scheduled?(digest) || retried?(digest) || enqueued?(digest) || active?(digest)
77
+ end
78
+
79
+ #
80
+ # Checks if the digest exists in the Sidekiq::ScheduledSet
81
+ #
82
+ # @param [String] digest the current digest
83
+ #
84
+ # @return [true] when digest exists in scheduled set
85
+ #
86
+ def scheduled?(digest)
87
+ in_sorted_set?(SCHEDULE, digest)
88
+ end
89
+
90
+ #
91
+ # Checks if the digest exists in the Sidekiq::RetrySet
92
+ #
93
+ # @param [String] digest the current digest
94
+ #
95
+ # @return [true] when digest exists in retry set
96
+ #
97
+ def retried?(digest)
98
+ in_sorted_set?(RETRY, digest)
99
+ end
100
+
101
+ #
102
+ # Checks if the digest exists in a Sidekiq::Queue
103
+ #
104
+ # @param [String] digest the current digest
105
+ #
106
+ # @return [true] when digest exists in any queue
107
+ #
108
+ def enqueued?(digest)
109
+ Sidekiq.redis do |conn|
110
+ queues(conn) do |queue|
111
+ entries(conn, queue) do |entry|
112
+ return true if entry.include?(digest)
113
+ end
114
+ end
115
+
116
+ false
117
+ end
118
+ end
119
+
120
+ def active?(digest) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
121
+ Sidekiq.redis do |conn|
122
+ procs = conn.sscan_each("processes").to_a
123
+ return false if procs.empty?
124
+
125
+ procs.sort.each do |key|
126
+ valid, workers = conn.pipelined do
127
+ conn.exists(key)
128
+ conn.hgetall("#{key}:workers")
129
+ end
130
+
131
+ next unless valid
132
+ next unless workers.any?
133
+
134
+ workers.each_pair do |_tid, job|
135
+ next unless (item = safe_load_json(job))
136
+
137
+ payload = safe_load_json(item[PAYLOAD])
138
+
139
+ return true if payload[LOCK_DIGEST] == digest
140
+ return true if considered_active?(payload[CREATED_AT])
141
+ end
142
+ end
143
+
144
+ false
145
+ end
146
+ end
147
+
148
+ def considered_active?(time_f)
149
+ (Time.now - reaper_timeout).to_f < time_f
150
+ end
151
+
152
+ #
153
+ # Loops through all the redis queues and yields them one by one
154
+ #
155
+ # @param [Redis] conn the connection to use for fetching queues
156
+ #
157
+ # @return [void]
158
+ #
159
+ # @yield queues one at a time
160
+ #
161
+ def queues(conn, &block)
162
+ conn.sscan_each("queues", &block)
163
+ end
164
+
165
+ def entries(conn, queue, &block) # rubocop:disable Metrics/MethodLength
166
+ queue_key = "queue:#{queue}"
167
+ initial_size = conn.llen(queue_key)
168
+ deleted_size = 0
169
+ page = 0
170
+ page_size = 50
171
+
172
+ loop do
173
+ range_start = page * page_size - deleted_size
174
+ range_end = range_start + page_size - 1
175
+ entries = conn.lrange(queue_key, range_start, range_end)
176
+ page += 1
177
+
178
+ break if entries.empty?
179
+
180
+ entries.each(&block)
181
+
182
+ deleted_size = initial_size - conn.llen(queue_key)
183
+ end
184
+ end
185
+
186
+ #
187
+ # Checks a sorted set for the existance of this digest
188
+ #
189
+ #
190
+ # @param [String] key the key for the sorted set
191
+ # @param [String] digest the digest to scan for
192
+ #
193
+ # @return [true] when found
194
+ # @return [false] when missing
195
+ #
196
+ def in_sorted_set?(key, digest)
197
+ conn.zscan_each(key, match: "*#{digest}*", count: 1).to_a.any?
198
+ end
199
+ end
200
+ end
201
+ end