sidekiq 4.2.10 → 7.3.2
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 +5 -5
- data/Changes.md +859 -7
- data/LICENSE.txt +9 -0
- data/README.md +49 -50
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiq +22 -3
- data/bin/sidekiqload +212 -119
- data/bin/sidekiqmon +11 -0
- data/lib/generators/sidekiq/job_generator.rb +59 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
- data/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +680 -315
- data/lib/sidekiq/capsule.rb +132 -0
- data/lib/sidekiq/cli.rb +268 -248
- data/lib/sidekiq/client.rb +136 -101
- data/lib/sidekiq/component.rb +68 -0
- data/lib/sidekiq/config.rb +293 -0
- data/lib/sidekiq/deploy.rb +64 -0
- data/lib/sidekiq/embedded.rb +63 -0
- data/lib/sidekiq/fetch.rb +49 -42
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +231 -0
- data/lib/sidekiq/job.rb +385 -0
- data/lib/sidekiq/job_logger.rb +62 -0
- data/lib/sidekiq/job_retry.rb +305 -0
- data/lib/sidekiq/job_util.rb +109 -0
- data/lib/sidekiq/launcher.rb +208 -108
- data/lib/sidekiq/logger.rb +131 -0
- data/lib/sidekiq/manager.rb +43 -47
- data/lib/sidekiq/metrics/query.rb +158 -0
- data/lib/sidekiq/metrics/shared.rb +97 -0
- data/lib/sidekiq/metrics/tracking.rb +148 -0
- data/lib/sidekiq/middleware/chain.rb +113 -56
- data/lib/sidekiq/middleware/current_attributes.rb +113 -0
- data/lib/sidekiq/middleware/i18n.rb +7 -7
- data/lib/sidekiq/middleware/modules.rb +23 -0
- data/lib/sidekiq/monitor.rb +147 -0
- data/lib/sidekiq/paginator.rb +28 -16
- data/lib/sidekiq/processor.rb +188 -98
- data/lib/sidekiq/rails.rb +46 -97
- data/lib/sidekiq/redis_client_adapter.rb +114 -0
- data/lib/sidekiq/redis_connection.rb +71 -73
- data/lib/sidekiq/ring_buffer.rb +31 -0
- data/lib/sidekiq/scheduled.rb +140 -51
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +26 -0
- data/lib/sidekiq/testing/inline.rb +6 -5
- data/lib/sidekiq/testing.rb +95 -85
- data/lib/sidekiq/transaction_aware_client.rb +51 -0
- data/lib/sidekiq/version.rb +3 -1
- data/lib/sidekiq/web/action.rb +22 -16
- data/lib/sidekiq/web/application.rb +230 -86
- data/lib/sidekiq/web/csrf_protection.rb +183 -0
- data/lib/sidekiq/web/helpers.rb +241 -104
- data/lib/sidekiq/web/router.rb +23 -19
- data/lib/sidekiq/web.rb +118 -110
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +96 -185
- data/sidekiq.gemspec +26 -27
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +157 -61
- data/web/assets/javascripts/base-charts.js +106 -0
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +192 -0
- data/web/assets/javascripts/dashboard.js +37 -280
- data/web/assets/javascripts/metrics.js +298 -0
- data/web/assets/stylesheets/application-dark.css +147 -0
- data/web/assets/stylesheets/application-rtl.css +163 -0
- data/web/assets/stylesheets/application.css +173 -198
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +87 -0
- data/web/locales/cs.yml +62 -62
- data/web/locales/da.yml +60 -53
- data/web/locales/de.yml +65 -53
- data/web/locales/el.yml +43 -24
- data/web/locales/en.yml +86 -64
- data/web/locales/es.yml +70 -53
- data/web/locales/fa.yml +65 -64
- data/web/locales/fr.yml +83 -62
- data/web/locales/gd.yml +99 -0
- data/web/locales/he.yml +80 -0
- data/web/locales/hi.yml +59 -59
- data/web/locales/it.yml +53 -53
- data/web/locales/ja.yml +75 -62
- data/web/locales/ko.yml +52 -52
- data/web/locales/lt.yml +83 -0
- data/web/locales/nb.yml +61 -61
- data/web/locales/nl.yml +52 -52
- data/web/locales/pl.yml +45 -45
- data/web/locales/pt-br.yml +83 -55
- data/web/locales/pt.yml +51 -51
- data/web/locales/ru.yml +68 -63
- data/web/locales/sv.yml +53 -53
- data/web/locales/ta.yml +60 -60
- data/web/locales/tr.yml +101 -0
- data/web/locales/uk.yml +62 -61
- data/web/locales/ur.yml +80 -0
- data/web/locales/vi.yml +83 -0
- data/web/locales/zh-cn.yml +43 -16
- data/web/locales/zh-tw.yml +42 -8
- data/web/views/_footer.erb +21 -3
- data/web/views/_job_info.erb +21 -4
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_nav.erb +5 -19
- data/web/views/_paging.erb +3 -1
- data/web/views/_poll_link.erb +3 -6
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +85 -31
- data/web/views/dashboard.erb +50 -20
- data/web/views/dead.erb +3 -3
- data/web/views/filtering.erb +7 -0
- data/web/views/layout.erb +17 -6
- data/web/views/metrics.erb +91 -0
- data/web/views/metrics_for_job.erb +59 -0
- data/web/views/morgue.erb +14 -15
- data/web/views/queue.erb +34 -24
- data/web/views/queues.erb +20 -4
- data/web/views/retries.erb +19 -16
- data/web/views/retry.erb +3 -3
- data/web/views/scheduled.erb +19 -17
- metadata +91 -198
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -9
- data/.gitignore +0 -12
- data/.travis.yml +0 -18
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/COMM-LICENSE +0 -95
- data/Ent-Changes.md +0 -173
- data/Gemfile +0 -29
- data/LICENSE +0 -9
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-Changes.md +0 -628
- data/Rakefile +0 -12
- data/bin/sidekiqctl +0 -99
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
- data/lib/generators/sidekiq/worker_generator.rb +0 -49
- data/lib/sidekiq/core_ext.rb +0 -119
- data/lib/sidekiq/exception_handler.rb +0 -31
- data/lib/sidekiq/extensions/action_mailer.rb +0 -57
- data/lib/sidekiq/extensions/active_record.rb +0 -40
- data/lib/sidekiq/extensions/class_methods.rb +0 -40
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -25
- data/lib/sidekiq/logging.rb +0 -106
- data/lib/sidekiq/middleware/server/active_record.rb +0 -13
- data/lib/sidekiq/middleware/server/logging.rb +0 -31
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
- data/lib/sidekiq/util.rb +0 -63
- data/lib/sidekiq/worker.rb +0 -121
data/lib/sidekiq/api.rb
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
# encoding: utf-8
|
|
2
1
|
# frozen_string_literal: true
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
require "sidekiq"
|
|
4
|
+
|
|
5
|
+
require "zlib"
|
|
6
|
+
require "set"
|
|
7
|
+
|
|
8
|
+
require "sidekiq/metrics/query"
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Sidekiq's Data API provides a Ruby object model on top
|
|
12
|
+
# of Sidekiq's runtime data in Redis. This API should never
|
|
13
|
+
# be used within application code for business logic.
|
|
14
|
+
#
|
|
15
|
+
# The Sidekiq server process never uses this API: all data
|
|
16
|
+
# manipulation is done directly for performance reasons to
|
|
17
|
+
# ensure we are using Redis as efficiently as possible at
|
|
18
|
+
# every callsite.
|
|
19
|
+
#
|
|
4
20
|
|
|
5
21
|
module Sidekiq
|
|
22
|
+
# Retrieve runtime statistics from Redis regarding
|
|
23
|
+
# this Sidekiq cluster.
|
|
24
|
+
#
|
|
25
|
+
# stat = Sidekiq::Stats.new
|
|
26
|
+
# stat.processed
|
|
6
27
|
class Stats
|
|
7
28
|
def initialize
|
|
8
|
-
|
|
29
|
+
fetch_stats_fast!
|
|
9
30
|
end
|
|
10
31
|
|
|
11
32
|
def processed
|
|
@@ -45,59 +66,96 @@ module Sidekiq
|
|
|
45
66
|
end
|
|
46
67
|
|
|
47
68
|
def queues
|
|
48
|
-
Sidekiq
|
|
69
|
+
Sidekiq.redis do |conn|
|
|
70
|
+
queues = conn.sscan("queues").to_a
|
|
71
|
+
|
|
72
|
+
lengths = conn.pipelined { |pipeline|
|
|
73
|
+
queues.each do |queue|
|
|
74
|
+
pipeline.llen("queue:#{queue}")
|
|
75
|
+
end
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
array_of_arrays = queues.zip(lengths).sort_by { |_, size| -size }
|
|
79
|
+
array_of_arrays.to_h
|
|
80
|
+
end
|
|
49
81
|
end
|
|
50
82
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
83
|
+
# O(1) redis calls
|
|
84
|
+
# @api private
|
|
85
|
+
def fetch_stats_fast!
|
|
86
|
+
pipe1_res = Sidekiq.redis { |conn|
|
|
87
|
+
conn.pipelined do |pipeline|
|
|
88
|
+
pipeline.get("stat:processed")
|
|
89
|
+
pipeline.get("stat:failed")
|
|
90
|
+
pipeline.zcard("schedule")
|
|
91
|
+
pipeline.zcard("retry")
|
|
92
|
+
pipeline.zcard("dead")
|
|
93
|
+
pipeline.scard("processes")
|
|
94
|
+
pipeline.lindex("queue:default", -1)
|
|
63
95
|
end
|
|
64
|
-
|
|
96
|
+
}
|
|
65
97
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
98
|
+
default_queue_latency = if (entry = pipe1_res[6])
|
|
99
|
+
job = begin
|
|
100
|
+
Sidekiq.load_json(entry)
|
|
101
|
+
rescue
|
|
102
|
+
{}
|
|
70
103
|
end
|
|
104
|
+
now = Time.now.to_f
|
|
105
|
+
thence = job["enqueued_at"] || now
|
|
106
|
+
now - thence
|
|
107
|
+
else
|
|
108
|
+
0
|
|
71
109
|
end
|
|
72
110
|
|
|
73
|
-
s = pipe1_res[7].size
|
|
74
|
-
workers_size = pipe2_res[0...s].map(&:to_i).inject(0, &:+)
|
|
75
|
-
enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+)
|
|
76
|
-
|
|
77
|
-
default_queue_latency = if (entry = pipe1_res[6].first)
|
|
78
|
-
job = Sidekiq.load_json(entry)
|
|
79
|
-
now = Time.now.to_f
|
|
80
|
-
thence = job['enqueued_at'.freeze] || now
|
|
81
|
-
now - thence
|
|
82
|
-
else
|
|
83
|
-
0
|
|
84
|
-
end
|
|
85
111
|
@stats = {
|
|
86
|
-
processed:
|
|
87
|
-
failed:
|
|
88
|
-
scheduled_size:
|
|
89
|
-
retry_size:
|
|
90
|
-
dead_size:
|
|
91
|
-
processes_size:
|
|
92
|
-
|
|
93
|
-
default_queue_latency: default_queue_latency
|
|
94
|
-
|
|
95
|
-
|
|
112
|
+
processed: pipe1_res[0].to_i,
|
|
113
|
+
failed: pipe1_res[1].to_i,
|
|
114
|
+
scheduled_size: pipe1_res[2],
|
|
115
|
+
retry_size: pipe1_res[3],
|
|
116
|
+
dead_size: pipe1_res[4],
|
|
117
|
+
processes_size: pipe1_res[5],
|
|
118
|
+
|
|
119
|
+
default_queue_latency: default_queue_latency
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# O(number of processes + number of queues) redis calls
|
|
124
|
+
# @api private
|
|
125
|
+
def fetch_stats_slow!
|
|
126
|
+
processes = Sidekiq.redis { |conn|
|
|
127
|
+
conn.sscan("processes").to_a
|
|
96
128
|
}
|
|
129
|
+
|
|
130
|
+
queues = Sidekiq.redis { |conn|
|
|
131
|
+
conn.sscan("queues").to_a
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pipe2_res = Sidekiq.redis { |conn|
|
|
135
|
+
conn.pipelined do |pipeline|
|
|
136
|
+
processes.each { |key| pipeline.hget(key, "busy") }
|
|
137
|
+
queues.each { |queue| pipeline.llen("queue:#{queue}") }
|
|
138
|
+
end
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
s = processes.size
|
|
142
|
+
workers_size = pipe2_res[0...s].sum(&:to_i)
|
|
143
|
+
enqueued = pipe2_res[s..].sum(&:to_i)
|
|
144
|
+
|
|
145
|
+
@stats[:workers_size] = workers_size
|
|
146
|
+
@stats[:enqueued] = enqueued
|
|
147
|
+
@stats
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @api private
|
|
151
|
+
def fetch_stats!
|
|
152
|
+
fetch_stats_fast!
|
|
153
|
+
fetch_stats_slow!
|
|
97
154
|
end
|
|
98
155
|
|
|
156
|
+
# @api private
|
|
99
157
|
def reset(*stats)
|
|
100
|
-
all
|
|
158
|
+
all = %w[failed processed]
|
|
101
159
|
stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s)
|
|
102
160
|
|
|
103
161
|
mset_args = []
|
|
@@ -113,61 +171,35 @@ module Sidekiq
|
|
|
113
171
|
private
|
|
114
172
|
|
|
115
173
|
def stat(s)
|
|
116
|
-
@stats[s]
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
class Queues
|
|
120
|
-
def lengths
|
|
121
|
-
Sidekiq.redis do |conn|
|
|
122
|
-
queues = conn.smembers('queues'.freeze)
|
|
123
|
-
|
|
124
|
-
lengths = conn.pipelined do
|
|
125
|
-
queues.each do |queue|
|
|
126
|
-
conn.llen("queue:#{queue}")
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
i = 0
|
|
131
|
-
array_of_arrays = queues.inject({}) do |memo, queue|
|
|
132
|
-
memo[queue] = lengths[i]
|
|
133
|
-
i += 1
|
|
134
|
-
memo
|
|
135
|
-
end.sort_by { |_, size| size }
|
|
136
|
-
|
|
137
|
-
Hash[array_of_arrays.reverse]
|
|
138
|
-
end
|
|
139
|
-
end
|
|
174
|
+
fetch_stats_slow! if @stats[s].nil?
|
|
175
|
+
@stats[s] || raise(ArgumentError, "Unknown stat #{s}")
|
|
140
176
|
end
|
|
141
177
|
|
|
142
178
|
class History
|
|
143
|
-
def initialize(days_previous, start_date = nil)
|
|
179
|
+
def initialize(days_previous, start_date = nil, pool: nil)
|
|
180
|
+
# we only store five years of data in Redis
|
|
181
|
+
raise ArgumentError if days_previous < 1 || days_previous > (5 * 365)
|
|
144
182
|
@days_previous = days_previous
|
|
145
183
|
@start_date = start_date || Time.now.utc.to_date
|
|
146
184
|
end
|
|
147
185
|
|
|
148
186
|
def processed
|
|
149
|
-
date_stat_hash("processed")
|
|
187
|
+
@processed ||= date_stat_hash("processed")
|
|
150
188
|
end
|
|
151
189
|
|
|
152
190
|
def failed
|
|
153
|
-
date_stat_hash("failed")
|
|
191
|
+
@failed ||= date_stat_hash("failed")
|
|
154
192
|
end
|
|
155
193
|
|
|
156
194
|
private
|
|
157
195
|
|
|
158
196
|
def date_stat_hash(stat)
|
|
159
|
-
i = 0
|
|
160
197
|
stat_hash = {}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
datestr = date.strftime("%Y-%m-%d".freeze)
|
|
167
|
-
keys << "stat:#{stat}:#{datestr}"
|
|
168
|
-
dates << datestr
|
|
169
|
-
i += 1
|
|
170
|
-
end
|
|
198
|
+
dates = @start_date.downto(@start_date - @days_previous + 1).map { |date|
|
|
199
|
+
date.strftime("%Y-%m-%d")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
keys = dates.map { |datestr| "stat:#{stat}:#{datestr}" }
|
|
171
203
|
|
|
172
204
|
Sidekiq.redis do |conn|
|
|
173
205
|
conn.mget(keys).each_with_index do |value, idx|
|
|
@@ -181,9 +213,10 @@ module Sidekiq
|
|
|
181
213
|
end
|
|
182
214
|
|
|
183
215
|
##
|
|
184
|
-
#
|
|
216
|
+
# Represents a queue within Sidekiq.
|
|
185
217
|
# Allows enumeration of all jobs within the queue
|
|
186
|
-
# and deletion of jobs.
|
|
218
|
+
# and deletion of jobs. NB: this queue data is real-time
|
|
219
|
+
# and is changing within Redis moment by moment.
|
|
187
220
|
#
|
|
188
221
|
# queue = Sidekiq::Queue.new("mailer")
|
|
189
222
|
# queue.each do |job|
|
|
@@ -191,29 +224,34 @@ module Sidekiq
|
|
|
191
224
|
# job.args # => [1, 2, 3]
|
|
192
225
|
# job.delete if job.jid == 'abcdef1234567890'
|
|
193
226
|
# end
|
|
194
|
-
#
|
|
195
227
|
class Queue
|
|
196
228
|
include Enumerable
|
|
197
229
|
|
|
198
230
|
##
|
|
199
|
-
#
|
|
231
|
+
# Fetch all known queues within Redis.
|
|
200
232
|
#
|
|
233
|
+
# @return [Array<Sidekiq::Queue>]
|
|
201
234
|
def self.all
|
|
202
|
-
Sidekiq.redis { |c| c.
|
|
235
|
+
Sidekiq.redis { |c| c.sscan("queues").to_a }.sort.map { |q| Sidekiq::Queue.new(q) }
|
|
203
236
|
end
|
|
204
237
|
|
|
205
238
|
attr_reader :name
|
|
206
239
|
|
|
207
|
-
|
|
208
|
-
|
|
240
|
+
# @param name [String] the name of the queue
|
|
241
|
+
def initialize(name = "default")
|
|
242
|
+
@name = name.to_s
|
|
209
243
|
@rname = "queue:#{name}"
|
|
210
244
|
end
|
|
211
245
|
|
|
246
|
+
# The current size of the queue within Redis.
|
|
247
|
+
# This value is real-time and can change between calls.
|
|
248
|
+
#
|
|
249
|
+
# @return [Integer] the size
|
|
212
250
|
def size
|
|
213
251
|
Sidekiq.redis { |con| con.llen(@rname) }
|
|
214
252
|
end
|
|
215
253
|
|
|
216
|
-
#
|
|
254
|
+
# @return [Boolean] if the queue is currently paused
|
|
217
255
|
def paused?
|
|
218
256
|
false
|
|
219
257
|
end
|
|
@@ -222,15 +260,15 @@ module Sidekiq
|
|
|
222
260
|
# Calculates this queue's latency, the difference in seconds since the oldest
|
|
223
261
|
# job in the queue was enqueued.
|
|
224
262
|
#
|
|
225
|
-
# @return Float
|
|
263
|
+
# @return [Float] in seconds
|
|
226
264
|
def latency
|
|
227
|
-
entry = Sidekiq.redis
|
|
228
|
-
conn.
|
|
229
|
-
|
|
265
|
+
entry = Sidekiq.redis { |conn|
|
|
266
|
+
conn.lindex(@rname, -1)
|
|
267
|
+
}
|
|
230
268
|
return 0 unless entry
|
|
231
269
|
job = Sidekiq.load_json(entry)
|
|
232
270
|
now = Time.now.to_f
|
|
233
|
-
thence = job[
|
|
271
|
+
thence = job["enqueued_at"] || now
|
|
234
272
|
now - thence
|
|
235
273
|
end
|
|
236
274
|
|
|
@@ -240,16 +278,16 @@ module Sidekiq
|
|
|
240
278
|
page = 0
|
|
241
279
|
page_size = 50
|
|
242
280
|
|
|
243
|
-
|
|
281
|
+
loop do
|
|
244
282
|
range_start = page * page_size - deleted_size
|
|
245
|
-
range_end
|
|
246
|
-
entries = Sidekiq.redis
|
|
283
|
+
range_end = range_start + page_size - 1
|
|
284
|
+
entries = Sidekiq.redis { |conn|
|
|
247
285
|
conn.lrange @rname, range_start, range_end
|
|
248
|
-
|
|
286
|
+
}
|
|
249
287
|
break if entries.empty?
|
|
250
288
|
page += 1
|
|
251
289
|
entries.each do |entry|
|
|
252
|
-
yield
|
|
290
|
+
yield JobRecord.new(entry, @name)
|
|
253
291
|
end
|
|
254
292
|
deleted_size = initial_size - size
|
|
255
293
|
end
|
|
@@ -258,150 +296,228 @@ module Sidekiq
|
|
|
258
296
|
##
|
|
259
297
|
# Find the job with the given JID within this queue.
|
|
260
298
|
#
|
|
261
|
-
# This is a slow, inefficient operation. Do not use under
|
|
262
|
-
# normal conditions.
|
|
299
|
+
# This is a *slow, inefficient* operation. Do not use under
|
|
300
|
+
# normal conditions.
|
|
301
|
+
#
|
|
302
|
+
# @param jid [String] the job_id to look for
|
|
303
|
+
# @return [Sidekiq::JobRecord]
|
|
304
|
+
# @return [nil] if not found
|
|
263
305
|
def find_job(jid)
|
|
264
306
|
detect { |j| j.jid == jid }
|
|
265
307
|
end
|
|
266
308
|
|
|
309
|
+
# delete all jobs within this queue
|
|
310
|
+
# @return [Boolean] true
|
|
267
311
|
def clear
|
|
268
312
|
Sidekiq.redis do |conn|
|
|
269
|
-
conn.multi do
|
|
270
|
-
|
|
271
|
-
|
|
313
|
+
conn.multi do |transaction|
|
|
314
|
+
transaction.unlink(@rname)
|
|
315
|
+
transaction.srem("queues", [name])
|
|
272
316
|
end
|
|
273
317
|
end
|
|
318
|
+
true
|
|
274
319
|
end
|
|
275
320
|
alias_method :💣, :clear
|
|
321
|
+
|
|
322
|
+
# :nodoc:
|
|
323
|
+
# @api private
|
|
324
|
+
def as_json(options = nil)
|
|
325
|
+
{name: name} # 5336
|
|
326
|
+
end
|
|
276
327
|
end
|
|
277
328
|
|
|
278
329
|
##
|
|
279
|
-
#
|
|
280
|
-
# sorted set.
|
|
330
|
+
# Represents a pending job within a Sidekiq queue.
|
|
281
331
|
#
|
|
282
332
|
# The job should be considered immutable but may be
|
|
283
|
-
# removed from the queue via
|
|
284
|
-
|
|
285
|
-
|
|
333
|
+
# removed from the queue via JobRecord#delete.
|
|
334
|
+
class JobRecord
|
|
335
|
+
# the parsed Hash of job data
|
|
336
|
+
# @!attribute [r] Item
|
|
286
337
|
attr_reader :item
|
|
338
|
+
# the underlying String in Redis
|
|
339
|
+
# @!attribute [r] Value
|
|
287
340
|
attr_reader :value
|
|
288
|
-
|
|
289
|
-
|
|
341
|
+
# the queue associated with this job
|
|
342
|
+
# @!attribute [r] Queue
|
|
343
|
+
attr_reader :queue
|
|
344
|
+
|
|
345
|
+
# :nodoc:
|
|
346
|
+
# @api private
|
|
347
|
+
def initialize(item, queue_name = nil)
|
|
348
|
+
@args = nil
|
|
290
349
|
@value = item
|
|
291
|
-
@item = item.is_a?(Hash) ? item :
|
|
292
|
-
@queue = queue_name || @item[
|
|
350
|
+
@item = item.is_a?(Hash) ? item : parse(item)
|
|
351
|
+
@queue = queue_name || @item["queue"]
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# :nodoc:
|
|
355
|
+
# @api private
|
|
356
|
+
def parse(item)
|
|
357
|
+
Sidekiq.load_json(item)
|
|
358
|
+
rescue JSON::ParserError
|
|
359
|
+
# If the job payload in Redis is invalid JSON, we'll load
|
|
360
|
+
# the item as an empty hash and store the invalid JSON as
|
|
361
|
+
# the job 'args' for display in the Web UI.
|
|
362
|
+
@invalid = true
|
|
363
|
+
@args = [item]
|
|
364
|
+
{}
|
|
293
365
|
end
|
|
294
366
|
|
|
367
|
+
# This is the job class which Sidekiq will execute. If using ActiveJob,
|
|
368
|
+
# this class will be the ActiveJob adapter class rather than a specific job.
|
|
295
369
|
def klass
|
|
296
|
-
|
|
370
|
+
self["class"]
|
|
297
371
|
end
|
|
298
372
|
|
|
299
373
|
def display_class
|
|
300
374
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
|
301
|
-
@klass ||=
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
else
|
|
315
|
-
klass
|
|
316
|
-
end
|
|
375
|
+
@klass ||= self["display_class"] || begin
|
|
376
|
+
if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
|
377
|
+
job_class = @item["wrapped"] || args[0]
|
|
378
|
+
if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
|
|
379
|
+
# MailerClass#mailer_method
|
|
380
|
+
args[0]["arguments"][0..1].join("#")
|
|
381
|
+
else
|
|
382
|
+
job_class
|
|
383
|
+
end
|
|
384
|
+
else
|
|
385
|
+
klass
|
|
386
|
+
end
|
|
387
|
+
end
|
|
317
388
|
end
|
|
318
389
|
|
|
319
390
|
def display_args
|
|
320
391
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
|
321
|
-
@
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
392
|
+
@display_args ||= if klass == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
|
393
|
+
job_args = self["wrapped"] ? deserialize_argument(args[0]["arguments"]) : []
|
|
394
|
+
if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
|
|
395
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
|
396
|
+
job_args.drop(3)
|
|
397
|
+
elsif (self["wrapped"] || args[0]) == "ActionMailer::MailDeliveryJob"
|
|
398
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
|
399
|
+
job_args.drop(3).first.values_at("params", "args")
|
|
400
|
+
else
|
|
401
|
+
job_args
|
|
402
|
+
end
|
|
403
|
+
else
|
|
404
|
+
if self["encrypt"]
|
|
405
|
+
# no point in showing 150+ bytes of random garbage
|
|
406
|
+
args[-1] = "[encrypted data]"
|
|
407
|
+
end
|
|
408
|
+
args
|
|
409
|
+
end
|
|
337
410
|
end
|
|
338
411
|
|
|
339
412
|
def args
|
|
340
|
-
@item[
|
|
413
|
+
@args || @item["args"]
|
|
341
414
|
end
|
|
342
415
|
|
|
343
416
|
def jid
|
|
344
|
-
|
|
417
|
+
self["jid"]
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def bid
|
|
421
|
+
self["bid"]
|
|
345
422
|
end
|
|
346
423
|
|
|
347
424
|
def enqueued_at
|
|
348
|
-
|
|
425
|
+
self["enqueued_at"] ? Time.at(self["enqueued_at"]).utc : nil
|
|
349
426
|
end
|
|
350
427
|
|
|
351
428
|
def created_at
|
|
352
|
-
Time.at(
|
|
429
|
+
Time.at(self["created_at"] || self["enqueued_at"] || 0).utc
|
|
353
430
|
end
|
|
354
431
|
|
|
355
|
-
def
|
|
356
|
-
|
|
432
|
+
def tags
|
|
433
|
+
self["tags"] || []
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def error_backtrace
|
|
437
|
+
# Cache nil values
|
|
438
|
+
if defined?(@error_backtrace)
|
|
439
|
+
@error_backtrace
|
|
440
|
+
else
|
|
441
|
+
value = self["error_backtrace"]
|
|
442
|
+
@error_backtrace = value && uncompress_backtrace(value)
|
|
443
|
+
end
|
|
357
444
|
end
|
|
358
445
|
|
|
359
446
|
def latency
|
|
360
447
|
now = Time.now.to_f
|
|
361
|
-
now - (@item[
|
|
448
|
+
now - (@item["enqueued_at"] || @item["created_at"] || now)
|
|
362
449
|
end
|
|
363
450
|
|
|
364
|
-
|
|
365
|
-
# Remove this job from the queue.
|
|
451
|
+
# Remove this job from the queue
|
|
366
452
|
def delete
|
|
367
|
-
count = Sidekiq.redis
|
|
453
|
+
count = Sidekiq.redis { |conn|
|
|
368
454
|
conn.lrem("queue:#{@queue}", 1, @value)
|
|
369
|
-
|
|
455
|
+
}
|
|
370
456
|
count != 0
|
|
371
457
|
end
|
|
372
458
|
|
|
459
|
+
# Access arbitrary attributes within the job hash
|
|
373
460
|
def [](name)
|
|
374
|
-
|
|
461
|
+
# nil will happen if the JSON fails to parse.
|
|
462
|
+
# We don't guarantee Sidekiq will work with bad job JSON but we should
|
|
463
|
+
# make a best effort to minimize the damage.
|
|
464
|
+
@item ? @item[name] : nil
|
|
375
465
|
end
|
|
376
466
|
|
|
377
467
|
private
|
|
378
468
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
469
|
+
ACTIVE_JOB_PREFIX = "_aj_"
|
|
470
|
+
GLOBALID_KEY = "_aj_globalid"
|
|
471
|
+
|
|
472
|
+
def deserialize_argument(argument)
|
|
473
|
+
case argument
|
|
474
|
+
when Array
|
|
475
|
+
argument.map { |arg| deserialize_argument(arg) }
|
|
476
|
+
when Hash
|
|
477
|
+
if serialized_global_id?(argument)
|
|
478
|
+
argument[GLOBALID_KEY]
|
|
479
|
+
else
|
|
480
|
+
argument.transform_values { |v| deserialize_argument(v) }
|
|
481
|
+
.reject { |k, _| k.start_with?(ACTIVE_JOB_PREFIX) }
|
|
482
|
+
end
|
|
483
|
+
else
|
|
484
|
+
argument
|
|
387
485
|
end
|
|
388
486
|
end
|
|
487
|
+
|
|
488
|
+
def serialized_global_id?(hash)
|
|
489
|
+
hash.size == 1 && hash.include?(GLOBALID_KEY)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def uncompress_backtrace(backtrace)
|
|
493
|
+
strict_base64_decoded = backtrace.unpack1("m")
|
|
494
|
+
uncompressed = Zlib::Inflate.inflate(strict_base64_decoded)
|
|
495
|
+
Sidekiq.load_json(uncompressed)
|
|
496
|
+
end
|
|
389
497
|
end
|
|
390
498
|
|
|
391
|
-
|
|
499
|
+
# Represents a job within a Redis sorted set where the score
|
|
500
|
+
# represents a timestamp associated with the job. This timestamp
|
|
501
|
+
# could be the scheduled time for it to run (e.g. scheduled set),
|
|
502
|
+
# or the expiration date after which the entry should be deleted (e.g. dead set).
|
|
503
|
+
class SortedEntry < JobRecord
|
|
392
504
|
attr_reader :score
|
|
393
505
|
attr_reader :parent
|
|
394
506
|
|
|
507
|
+
# :nodoc:
|
|
508
|
+
# @api private
|
|
395
509
|
def initialize(parent, score, item)
|
|
396
510
|
super(item)
|
|
397
|
-
@score = score
|
|
511
|
+
@score = Float(score)
|
|
398
512
|
@parent = parent
|
|
399
513
|
end
|
|
400
514
|
|
|
515
|
+
# The timestamp associated with this entry
|
|
401
516
|
def at
|
|
402
517
|
Time.at(score).utc
|
|
403
518
|
end
|
|
404
519
|
|
|
520
|
+
# remove this entry from the sorted set
|
|
405
521
|
def delete
|
|
406
522
|
if @value
|
|
407
523
|
@parent.delete_by_value(@parent.name, @value)
|
|
@@ -410,11 +526,17 @@ module Sidekiq
|
|
|
410
526
|
end
|
|
411
527
|
end
|
|
412
528
|
|
|
529
|
+
# Change the scheduled time for this job.
|
|
530
|
+
#
|
|
531
|
+
# @param at [Time] the new timestamp for this job
|
|
413
532
|
def reschedule(at)
|
|
414
|
-
|
|
415
|
-
|
|
533
|
+
Sidekiq.redis do |conn|
|
|
534
|
+
conn.zincrby(@parent.name, at.to_f - @score, Sidekiq.dump_json(@item))
|
|
535
|
+
end
|
|
416
536
|
end
|
|
417
537
|
|
|
538
|
+
# Enqueue this job from the scheduled or dead set so it will
|
|
539
|
+
# be executed at some point in the near future.
|
|
418
540
|
def add_to_queue
|
|
419
541
|
remove_job do |message|
|
|
420
542
|
msg = Sidekiq.load_json(message)
|
|
@@ -422,98 +544,127 @@ module Sidekiq
|
|
|
422
544
|
end
|
|
423
545
|
end
|
|
424
546
|
|
|
547
|
+
# enqueue this job from the retry set so it will be executed
|
|
548
|
+
# at some point in the near future.
|
|
425
549
|
def retry
|
|
426
550
|
remove_job do |message|
|
|
427
551
|
msg = Sidekiq.load_json(message)
|
|
428
|
-
msg[
|
|
552
|
+
msg["retry_count"] -= 1 if msg["retry_count"]
|
|
429
553
|
Sidekiq::Client.push(msg)
|
|
430
554
|
end
|
|
431
555
|
end
|
|
432
556
|
|
|
433
|
-
|
|
434
|
-
# Place job in the dead set
|
|
557
|
+
# Move this job from its current set into the Dead set.
|
|
435
558
|
def kill
|
|
436
559
|
remove_job do |message|
|
|
437
|
-
|
|
438
|
-
Sidekiq.redis do |conn|
|
|
439
|
-
conn.multi do
|
|
440
|
-
conn.zadd('dead', now, message)
|
|
441
|
-
conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout)
|
|
442
|
-
conn.zremrangebyrank('dead', 0, - DeadSet.max_jobs)
|
|
443
|
-
end
|
|
444
|
-
end
|
|
560
|
+
DeadSet.new.kill(message)
|
|
445
561
|
end
|
|
446
562
|
end
|
|
447
563
|
|
|
448
564
|
def error?
|
|
449
|
-
!!item[
|
|
565
|
+
!!item["error_class"]
|
|
450
566
|
end
|
|
451
567
|
|
|
452
568
|
private
|
|
453
569
|
|
|
454
570
|
def remove_job
|
|
455
571
|
Sidekiq.redis do |conn|
|
|
456
|
-
results = conn.multi
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
572
|
+
results = conn.multi { |transaction|
|
|
573
|
+
transaction.zrange(parent.name, score, score, "BYSCORE")
|
|
574
|
+
transaction.zremrangebyscore(parent.name, score, score)
|
|
575
|
+
}.first
|
|
460
576
|
|
|
461
577
|
if results.size == 1
|
|
462
578
|
yield results.first
|
|
463
579
|
else
|
|
464
580
|
# multiple jobs with the same score
|
|
465
581
|
# find the one with the right JID and push it
|
|
466
|
-
|
|
582
|
+
matched, nonmatched = results.partition { |message|
|
|
467
583
|
if message.index(jid)
|
|
468
584
|
msg = Sidekiq.load_json(message)
|
|
469
|
-
msg[
|
|
585
|
+
msg["jid"] == jid
|
|
470
586
|
else
|
|
471
587
|
false
|
|
472
588
|
end
|
|
473
|
-
|
|
589
|
+
}
|
|
474
590
|
|
|
475
|
-
msg =
|
|
591
|
+
msg = matched.first
|
|
476
592
|
yield msg if msg
|
|
477
593
|
|
|
478
594
|
# push the rest back onto the sorted set
|
|
479
|
-
conn.multi do
|
|
480
|
-
|
|
481
|
-
|
|
595
|
+
conn.multi do |transaction|
|
|
596
|
+
nonmatched.each do |message|
|
|
597
|
+
transaction.zadd(parent.name, score.to_f.to_s, message)
|
|
482
598
|
end
|
|
483
599
|
end
|
|
484
600
|
end
|
|
485
601
|
end
|
|
486
602
|
end
|
|
487
|
-
|
|
488
603
|
end
|
|
489
604
|
|
|
605
|
+
# Base class for all sorted sets within Sidekiq.
|
|
490
606
|
class SortedSet
|
|
491
607
|
include Enumerable
|
|
492
608
|
|
|
609
|
+
# Redis key of the set
|
|
610
|
+
# @!attribute [r] Name
|
|
493
611
|
attr_reader :name
|
|
494
612
|
|
|
613
|
+
# :nodoc:
|
|
614
|
+
# @api private
|
|
495
615
|
def initialize(name)
|
|
496
616
|
@name = name
|
|
497
617
|
@_size = size
|
|
498
618
|
end
|
|
499
619
|
|
|
620
|
+
# real-time size of the set, will change
|
|
500
621
|
def size
|
|
501
622
|
Sidekiq.redis { |c| c.zcard(name) }
|
|
502
623
|
end
|
|
503
624
|
|
|
625
|
+
# Scan through each element of the sorted set, yielding each to the supplied block.
|
|
626
|
+
# Please see Redis's <a href="https://redis.io/commands/scan/">SCAN documentation</a> for implementation details.
|
|
627
|
+
#
|
|
628
|
+
# @param match [String] a snippet or regexp to filter matches.
|
|
629
|
+
# @param count [Integer] number of elements to retrieve at a time, default 100
|
|
630
|
+
# @yieldparam [Sidekiq::SortedEntry] each entry
|
|
631
|
+
def scan(match, count = 100)
|
|
632
|
+
return to_enum(:scan, match, count) unless block_given?
|
|
633
|
+
|
|
634
|
+
match = "*#{match}*" unless match.include?("*")
|
|
635
|
+
Sidekiq.redis do |conn|
|
|
636
|
+
conn.zscan(name, match: match, count: count) do |entry, score|
|
|
637
|
+
yield SortedEntry.new(self, score, entry)
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# @return [Boolean] always true
|
|
504
643
|
def clear
|
|
505
644
|
Sidekiq.redis do |conn|
|
|
506
|
-
conn.
|
|
645
|
+
conn.unlink(name)
|
|
507
646
|
end
|
|
647
|
+
true
|
|
508
648
|
end
|
|
509
649
|
alias_method :💣, :clear
|
|
650
|
+
|
|
651
|
+
# :nodoc:
|
|
652
|
+
# @api private
|
|
653
|
+
def as_json(options = nil)
|
|
654
|
+
{name: name} # 5336
|
|
655
|
+
end
|
|
510
656
|
end
|
|
511
657
|
|
|
658
|
+
# Base class for all sorted sets which contain jobs, e.g. scheduled, retry and dead.
|
|
659
|
+
# Sidekiq Pro and Enterprise add additional sorted sets which do not contain job data,
|
|
660
|
+
# e.g. Batches.
|
|
512
661
|
class JobSet < SortedSet
|
|
513
|
-
|
|
514
|
-
|
|
662
|
+
# Add a job with the associated timestamp to this set.
|
|
663
|
+
# @param timestamp [Time] the score for the job
|
|
664
|
+
# @param job [Hash] the job data
|
|
665
|
+
def schedule(timestamp, job)
|
|
515
666
|
Sidekiq.redis do |conn|
|
|
516
|
-
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(
|
|
667
|
+
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(job))
|
|
517
668
|
end
|
|
518
669
|
end
|
|
519
670
|
|
|
@@ -523,46 +674,66 @@ module Sidekiq
|
|
|
523
674
|
page = -1
|
|
524
675
|
page_size = 50
|
|
525
676
|
|
|
526
|
-
|
|
677
|
+
loop do
|
|
527
678
|
range_start = page * page_size + offset_size
|
|
528
|
-
range_end
|
|
529
|
-
elements = Sidekiq.redis
|
|
530
|
-
conn.zrange name, range_start, range_end,
|
|
531
|
-
|
|
679
|
+
range_end = range_start + page_size - 1
|
|
680
|
+
elements = Sidekiq.redis { |conn|
|
|
681
|
+
conn.zrange name, range_start, range_end, "withscores"
|
|
682
|
+
}
|
|
532
683
|
break if elements.empty?
|
|
533
684
|
page -= 1
|
|
534
|
-
elements.
|
|
685
|
+
elements.reverse_each do |element, score|
|
|
535
686
|
yield SortedEntry.new(self, score, element)
|
|
536
687
|
end
|
|
537
688
|
offset_size = initial_size - @_size
|
|
538
689
|
end
|
|
539
690
|
end
|
|
540
691
|
|
|
692
|
+
##
|
|
693
|
+
# Fetch jobs that match a given time or Range. Job ID is an
|
|
694
|
+
# optional second argument.
|
|
695
|
+
#
|
|
696
|
+
# @param score [Time,Range] a specific timestamp or range
|
|
697
|
+
# @param jid [String, optional] find a specific JID within the score
|
|
698
|
+
# @return [Array<SortedEntry>] any results found, can be empty
|
|
541
699
|
def fetch(score, jid = nil)
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
elements.inject([]) do |result, element|
|
|
547
|
-
entry = SortedEntry.new(self, score, element)
|
|
548
|
-
if jid
|
|
549
|
-
result << entry if entry.jid == jid
|
|
700
|
+
begin_score, end_score =
|
|
701
|
+
if score.is_a?(Range)
|
|
702
|
+
[score.first, score.last]
|
|
550
703
|
else
|
|
551
|
-
|
|
704
|
+
[score, score]
|
|
552
705
|
end
|
|
553
|
-
|
|
706
|
+
|
|
707
|
+
elements = Sidekiq.redis { |conn|
|
|
708
|
+
conn.zrange(name, begin_score, end_score, "BYSCORE", "withscores")
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
elements.each_with_object([]) do |element, result|
|
|
712
|
+
data, job_score = element
|
|
713
|
+
entry = SortedEntry.new(self, job_score, data)
|
|
714
|
+
result << entry if jid.nil? || entry.jid == jid
|
|
554
715
|
end
|
|
555
716
|
end
|
|
556
717
|
|
|
557
718
|
##
|
|
558
719
|
# Find the job with the given JID within this sorted set.
|
|
720
|
+
# *This is a slow O(n) operation*. Do not use for app logic.
|
|
559
721
|
#
|
|
560
|
-
#
|
|
561
|
-
#
|
|
722
|
+
# @param jid [String] the job identifier
|
|
723
|
+
# @return [SortedEntry] the record or nil
|
|
562
724
|
def find_job(jid)
|
|
563
|
-
|
|
725
|
+
Sidekiq.redis do |conn|
|
|
726
|
+
conn.zscan(name, match: "*#{jid}*", count: 100) do |entry, score|
|
|
727
|
+
job = Sidekiq.load_json(entry)
|
|
728
|
+
matched = job["jid"] == jid
|
|
729
|
+
return SortedEntry.new(self, score, entry) if matched
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
nil
|
|
564
733
|
end
|
|
565
734
|
|
|
735
|
+
# :nodoc:
|
|
736
|
+
# @api private
|
|
566
737
|
def delete_by_value(name, value)
|
|
567
738
|
Sidekiq.redis do |conn|
|
|
568
739
|
ret = conn.zrem(name, value)
|
|
@@ -571,17 +742,20 @@ module Sidekiq
|
|
|
571
742
|
end
|
|
572
743
|
end
|
|
573
744
|
|
|
745
|
+
# :nodoc:
|
|
746
|
+
# @api private
|
|
574
747
|
def delete_by_jid(score, jid)
|
|
575
748
|
Sidekiq.redis do |conn|
|
|
576
|
-
elements = conn.
|
|
749
|
+
elements = conn.zrange(name, score, score, "BYSCORE")
|
|
577
750
|
elements.each do |element|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
751
|
+
if element.index(jid)
|
|
752
|
+
message = Sidekiq.load_json(element)
|
|
753
|
+
if message["jid"] == jid
|
|
754
|
+
ret = conn.zrem(name, element)
|
|
755
|
+
@_size -= 1 if ret
|
|
756
|
+
break ret
|
|
757
|
+
end
|
|
583
758
|
end
|
|
584
|
-
false
|
|
585
759
|
end
|
|
586
760
|
end
|
|
587
761
|
end
|
|
@@ -590,136 +764,214 @@ module Sidekiq
|
|
|
590
764
|
end
|
|
591
765
|
|
|
592
766
|
##
|
|
593
|
-
#
|
|
767
|
+
# The set of scheduled jobs within Sidekiq.
|
|
594
768
|
# Based on this, you can search/filter for jobs. Here's an
|
|
595
|
-
# example where I'm selecting
|
|
596
|
-
# and deleting them from the
|
|
769
|
+
# example where I'm selecting jobs based on some complex logic
|
|
770
|
+
# and deleting them from the scheduled set.
|
|
771
|
+
#
|
|
772
|
+
# See the API wiki page for usage notes and examples.
|
|
597
773
|
#
|
|
598
|
-
# r = Sidekiq::ScheduledSet.new
|
|
599
|
-
# r.select do |scheduled|
|
|
600
|
-
# scheduled.klass == 'Sidekiq::Extensions::DelayedClass' &&
|
|
601
|
-
# scheduled.args[0] == 'User' &&
|
|
602
|
-
# scheduled.args[1] == 'setup_new_subscriber'
|
|
603
|
-
# end.map(&:delete)
|
|
604
774
|
class ScheduledSet < JobSet
|
|
605
775
|
def initialize
|
|
606
|
-
super
|
|
776
|
+
super("schedule")
|
|
607
777
|
end
|
|
608
778
|
end
|
|
609
779
|
|
|
610
780
|
##
|
|
611
|
-
#
|
|
781
|
+
# The set of retries within Sidekiq.
|
|
612
782
|
# Based on this, you can search/filter for jobs. Here's an
|
|
613
783
|
# example where I'm selecting all jobs of a certain type
|
|
614
784
|
# and deleting them from the retry queue.
|
|
615
785
|
#
|
|
616
|
-
#
|
|
617
|
-
#
|
|
618
|
-
# retri.klass == 'Sidekiq::Extensions::DelayedClass' &&
|
|
619
|
-
# retri.args[0] == 'User' &&
|
|
620
|
-
# retri.args[1] == 'setup_new_subscriber'
|
|
621
|
-
# end.map(&:delete)
|
|
786
|
+
# See the API wiki page for usage notes and examples.
|
|
787
|
+
#
|
|
622
788
|
class RetrySet < JobSet
|
|
623
789
|
def initialize
|
|
624
|
-
super
|
|
790
|
+
super("retry")
|
|
625
791
|
end
|
|
626
792
|
|
|
793
|
+
# Enqueues all jobs pending within the retry set.
|
|
627
794
|
def retry_all
|
|
628
|
-
while size > 0
|
|
629
|
-
|
|
630
|
-
|
|
795
|
+
each(&:retry) while size > 0
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
# Kills all jobs pending within the retry set.
|
|
799
|
+
def kill_all
|
|
800
|
+
each(&:kill) while size > 0
|
|
631
801
|
end
|
|
632
802
|
end
|
|
633
803
|
|
|
634
804
|
##
|
|
635
|
-
#
|
|
805
|
+
# The set of dead jobs within Sidekiq. Dead jobs have failed all of
|
|
806
|
+
# their retries and are helding in this set pending some sort of manual
|
|
807
|
+
# fix. They will be removed after 6 months (dead_timeout) if not.
|
|
636
808
|
#
|
|
637
809
|
class DeadSet < JobSet
|
|
638
810
|
def initialize
|
|
639
|
-
super
|
|
811
|
+
super("dead")
|
|
640
812
|
end
|
|
641
813
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
814
|
+
# Add the given job to the Dead set.
|
|
815
|
+
# @param message [String] the job data as JSON
|
|
816
|
+
# @option opts [Boolean] :notify_failure (true) Whether death handlers should be called
|
|
817
|
+
# @option opts [Exception] :ex (RuntimeError) An exception to pass to the death handlers
|
|
818
|
+
def kill(message, opts = {})
|
|
819
|
+
now = Time.now.to_f
|
|
820
|
+
Sidekiq.redis do |conn|
|
|
821
|
+
conn.multi do |transaction|
|
|
822
|
+
transaction.zadd(name, now.to_s, message)
|
|
823
|
+
transaction.zremrangebyscore(name, "-inf", now - Sidekiq::Config::DEFAULTS[:dead_timeout_in_seconds])
|
|
824
|
+
transaction.zremrangebyrank(name, 0, - Sidekiq::Config::DEFAULTS[:dead_max_jobs])
|
|
825
|
+
end
|
|
645
826
|
end
|
|
646
|
-
end
|
|
647
827
|
|
|
648
|
-
|
|
649
|
-
|
|
828
|
+
if opts[:notify_failure] != false
|
|
829
|
+
job = Sidekiq.load_json(message)
|
|
830
|
+
if opts[:ex]
|
|
831
|
+
ex = opts[:ex]
|
|
832
|
+
else
|
|
833
|
+
ex = RuntimeError.new("Job killed by API")
|
|
834
|
+
ex.set_backtrace(caller)
|
|
835
|
+
end
|
|
836
|
+
Sidekiq.default_configuration.death_handlers.each do |handle|
|
|
837
|
+
handle.call(job, ex)
|
|
838
|
+
end
|
|
839
|
+
end
|
|
840
|
+
true
|
|
650
841
|
end
|
|
651
842
|
|
|
652
|
-
|
|
653
|
-
|
|
843
|
+
# Enqueue all dead jobs
|
|
844
|
+
def retry_all
|
|
845
|
+
each(&:retry) while size > 0
|
|
654
846
|
end
|
|
655
847
|
end
|
|
656
848
|
|
|
657
849
|
##
|
|
658
850
|
# Enumerates the set of Sidekiq processes which are actively working
|
|
659
|
-
# right now. Each process
|
|
851
|
+
# right now. Each process sends a heartbeat to Redis every 5 seconds
|
|
660
852
|
# so this set should be relatively accurate, barring network partitions.
|
|
661
853
|
#
|
|
662
|
-
#
|
|
854
|
+
# @yieldparam [Sidekiq::Process]
|
|
663
855
|
#
|
|
664
856
|
class ProcessSet
|
|
665
857
|
include Enumerable
|
|
666
858
|
|
|
667
|
-
def
|
|
668
|
-
|
|
859
|
+
def self.[](identity)
|
|
860
|
+
exists, (info, busy, beat, quiet, rss, rtt_us) = Sidekiq.redis { |conn|
|
|
861
|
+
conn.multi { |transaction|
|
|
862
|
+
transaction.sismember("processes", identity)
|
|
863
|
+
transaction.hmget(identity, "info", "busy", "beat", "quiet", "rss", "rtt_us")
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return nil if exists == 0 || info.nil?
|
|
868
|
+
|
|
869
|
+
hash = Sidekiq.load_json(info)
|
|
870
|
+
Process.new(hash.merge("busy" => busy.to_i,
|
|
871
|
+
"beat" => beat.to_f,
|
|
872
|
+
"quiet" => quiet,
|
|
873
|
+
"rss" => rss.to_i,
|
|
874
|
+
"rtt_us" => rtt_us.to_i))
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
# :nodoc:
|
|
878
|
+
# @api private
|
|
879
|
+
def initialize(clean_plz = true)
|
|
880
|
+
cleanup if clean_plz
|
|
669
881
|
end
|
|
670
882
|
|
|
671
883
|
# Cleans up dead processes recorded in Redis.
|
|
672
884
|
# Returns the number of processes cleaned.
|
|
673
|
-
|
|
885
|
+
# :nodoc:
|
|
886
|
+
# @api private
|
|
887
|
+
def cleanup
|
|
888
|
+
# dont run cleanup more than once per minute
|
|
889
|
+
return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", "NX", "EX", "60") }
|
|
890
|
+
|
|
674
891
|
count = 0
|
|
675
892
|
Sidekiq.redis do |conn|
|
|
676
|
-
procs = conn.
|
|
677
|
-
heartbeats = conn.pipelined
|
|
893
|
+
procs = conn.sscan("processes").to_a
|
|
894
|
+
heartbeats = conn.pipelined { |pipeline|
|
|
678
895
|
procs.each do |key|
|
|
679
|
-
|
|
896
|
+
pipeline.hget(key, "info")
|
|
680
897
|
end
|
|
681
|
-
|
|
898
|
+
}
|
|
682
899
|
|
|
683
900
|
# the hash named key has an expiry of 60 seconds.
|
|
684
901
|
# if it's not found, that means the process has not reported
|
|
685
902
|
# in to Redis and probably died.
|
|
686
|
-
to_prune =
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
count = conn.srem('processes', to_prune) unless to_prune.empty?
|
|
903
|
+
to_prune = procs.select.with_index { |proc, i|
|
|
904
|
+
heartbeats[i].nil?
|
|
905
|
+
}
|
|
906
|
+
count = conn.srem("processes", to_prune) unless to_prune.empty?
|
|
691
907
|
end
|
|
692
908
|
count
|
|
693
909
|
end
|
|
694
910
|
|
|
695
911
|
def each
|
|
696
|
-
|
|
912
|
+
result = Sidekiq.redis { |conn|
|
|
913
|
+
procs = conn.sscan("processes").to_a.sort
|
|
697
914
|
|
|
698
|
-
Sidekiq.redis do |conn|
|
|
699
915
|
# We're making a tradeoff here between consuming more memory instead of
|
|
700
916
|
# making more roundtrips to Redis, but if you have hundreds or thousands of workers,
|
|
701
917
|
# you'll be happier this way
|
|
702
|
-
|
|
918
|
+
conn.pipelined do |pipeline|
|
|
703
919
|
procs.each do |key|
|
|
704
|
-
|
|
920
|
+
pipeline.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
|
|
705
921
|
end
|
|
706
922
|
end
|
|
923
|
+
}
|
|
707
924
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
925
|
+
result.each do |info, busy, beat, quiet, rss, rtt_us|
|
|
926
|
+
# If a process is stopped between when we query Redis for `procs` and
|
|
927
|
+
# when we query for `result`, we will have an item in `result` that is
|
|
928
|
+
# composed of `nil` values.
|
|
929
|
+
next if info.nil?
|
|
930
|
+
|
|
931
|
+
hash = Sidekiq.load_json(info)
|
|
932
|
+
yield Process.new(hash.merge("busy" => busy.to_i,
|
|
933
|
+
"beat" => beat.to_f,
|
|
934
|
+
"quiet" => quiet,
|
|
935
|
+
"rss" => rss.to_i,
|
|
936
|
+
"rtt_us" => rtt_us.to_i))
|
|
712
937
|
end
|
|
713
|
-
|
|
714
|
-
nil
|
|
715
938
|
end
|
|
716
939
|
|
|
717
940
|
# This method is not guaranteed accurate since it does not prune the set
|
|
718
941
|
# based on current heartbeat. #each does that and ensures the set only
|
|
719
942
|
# contains Sidekiq processes which have sent a heartbeat within the last
|
|
720
943
|
# 60 seconds.
|
|
944
|
+
# @return [Integer] current number of registered Sidekiq processes
|
|
721
945
|
def size
|
|
722
|
-
Sidekiq.redis { |conn| conn.scard(
|
|
946
|
+
Sidekiq.redis { |conn| conn.scard("processes") }
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
# Total number of threads available to execute jobs.
|
|
950
|
+
# For Sidekiq Enterprise customers this number (in production) must be
|
|
951
|
+
# less than or equal to your licensed concurrency.
|
|
952
|
+
# @return [Integer] the sum of process concurrency
|
|
953
|
+
def total_concurrency
|
|
954
|
+
sum { |x| x["concurrency"].to_i }
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
# @return [Integer] total amount of RSS memory consumed by Sidekiq processes
|
|
958
|
+
def total_rss_in_kb
|
|
959
|
+
sum { |x| x["rss"].to_i }
|
|
960
|
+
end
|
|
961
|
+
alias_method :total_rss, :total_rss_in_kb
|
|
962
|
+
|
|
963
|
+
# Returns the identity of the current cluster leader or "" if no leader.
|
|
964
|
+
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
|
965
|
+
# or Sidekiq Pro.
|
|
966
|
+
# @return [String] Identity of cluster leader
|
|
967
|
+
# @return [String] empty string if no leader
|
|
968
|
+
def leader
|
|
969
|
+
@leader ||= begin
|
|
970
|
+
x = Sidekiq.redis { |c| c.get("dear-leader") }
|
|
971
|
+
# need a non-falsy value so we can memoize
|
|
972
|
+
x ||= ""
|
|
973
|
+
x
|
|
974
|
+
end
|
|
723
975
|
end
|
|
724
976
|
end
|
|
725
977
|
|
|
@@ -737,38 +989,78 @@ module Sidekiq
|
|
|
737
989
|
# 'busy' => 10,
|
|
738
990
|
# 'beat' => <last heartbeat>,
|
|
739
991
|
# 'identity' => <unique string identifying the process>,
|
|
992
|
+
# 'embedded' => true,
|
|
740
993
|
# }
|
|
741
994
|
class Process
|
|
995
|
+
# :nodoc:
|
|
996
|
+
# @api private
|
|
742
997
|
def initialize(hash)
|
|
743
998
|
@attribs = hash
|
|
744
999
|
end
|
|
745
1000
|
|
|
746
1001
|
def tag
|
|
747
|
-
self[
|
|
1002
|
+
self["tag"]
|
|
748
1003
|
end
|
|
749
1004
|
|
|
750
1005
|
def labels
|
|
751
|
-
|
|
1006
|
+
self["labels"].to_a
|
|
752
1007
|
end
|
|
753
1008
|
|
|
754
1009
|
def [](key)
|
|
755
1010
|
@attribs[key]
|
|
756
1011
|
end
|
|
757
1012
|
|
|
1013
|
+
def identity
|
|
1014
|
+
self["identity"]
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
def queues
|
|
1018
|
+
self["queues"]
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
def weights
|
|
1022
|
+
self["weights"]
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
def version
|
|
1026
|
+
self["version"]
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
def embedded?
|
|
1030
|
+
self["embedded"]
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
# Signal this process to stop processing new jobs.
|
|
1034
|
+
# It will continue to execute jobs it has already fetched.
|
|
1035
|
+
# This method is *asynchronous* and it can take 5-10
|
|
1036
|
+
# seconds for the process to quiet.
|
|
758
1037
|
def quiet!
|
|
759
|
-
|
|
1038
|
+
raise "Can't quiet an embedded process" if embedded?
|
|
1039
|
+
|
|
1040
|
+
signal("TSTP")
|
|
760
1041
|
end
|
|
761
1042
|
|
|
1043
|
+
# Signal this process to shutdown.
|
|
1044
|
+
# It will shutdown within its configured :timeout value, default 25 seconds.
|
|
1045
|
+
# This method is *asynchronous* and it can take 5-10
|
|
1046
|
+
# seconds for the process to start shutting down.
|
|
762
1047
|
def stop!
|
|
763
|
-
|
|
1048
|
+
raise "Can't stop an embedded process" if embedded?
|
|
1049
|
+
|
|
1050
|
+
signal("TERM")
|
|
764
1051
|
end
|
|
765
1052
|
|
|
1053
|
+
# Signal this process to log backtraces for all threads.
|
|
1054
|
+
# Useful if you have a frozen or deadlocked process which is
|
|
1055
|
+
# still sending a heartbeat.
|
|
1056
|
+
# This method is *asynchronous* and it can take 5-10 seconds.
|
|
766
1057
|
def dump_threads
|
|
767
|
-
signal(
|
|
1058
|
+
signal("TTIN")
|
|
768
1059
|
end
|
|
769
1060
|
|
|
1061
|
+
# @return [Boolean] true if this process is quiet or shutting down
|
|
770
1062
|
def stopping?
|
|
771
|
-
self[
|
|
1063
|
+
self["quiet"] == "true"
|
|
772
1064
|
end
|
|
773
1065
|
|
|
774
1066
|
private
|
|
@@ -776,21 +1068,17 @@ module Sidekiq
|
|
|
776
1068
|
def signal(sig)
|
|
777
1069
|
key = "#{identity}-signals"
|
|
778
1070
|
Sidekiq.redis do |c|
|
|
779
|
-
c.multi do
|
|
780
|
-
|
|
781
|
-
|
|
1071
|
+
c.multi do |transaction|
|
|
1072
|
+
transaction.lpush(key, sig)
|
|
1073
|
+
transaction.expire(key, 60)
|
|
782
1074
|
end
|
|
783
1075
|
end
|
|
784
1076
|
end
|
|
785
|
-
|
|
786
|
-
def identity
|
|
787
|
-
self['identity']
|
|
788
|
-
end
|
|
789
1077
|
end
|
|
790
1078
|
|
|
791
1079
|
##
|
|
792
|
-
#
|
|
793
|
-
#
|
|
1080
|
+
# The WorkSet stores the work being done by this Sidekiq cluster.
|
|
1081
|
+
# It tracks the process and thread working on each job.
|
|
794
1082
|
#
|
|
795
1083
|
# WARNING WARNING WARNING
|
|
796
1084
|
#
|
|
@@ -798,33 +1086,40 @@ module Sidekiq
|
|
|
798
1086
|
# If you call #size => 5 and then expect #each to be
|
|
799
1087
|
# called 5 times, you're going to have a bad time.
|
|
800
1088
|
#
|
|
801
|
-
#
|
|
802
|
-
#
|
|
803
|
-
#
|
|
1089
|
+
# works = Sidekiq::WorkSet.new
|
|
1090
|
+
# works.size => 2
|
|
1091
|
+
# works.each do |process_id, thread_id, work|
|
|
804
1092
|
# # process_id is a unique identifier per Sidekiq process
|
|
805
1093
|
# # thread_id is a unique identifier per thread
|
|
806
1094
|
# # work is a Hash which looks like:
|
|
807
|
-
# # { 'queue' => name, 'run_at' => timestamp, 'payload' =>
|
|
1095
|
+
# # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash }
|
|
808
1096
|
# # run_at is an epoch Integer.
|
|
809
1097
|
# end
|
|
810
1098
|
#
|
|
811
|
-
class
|
|
1099
|
+
class WorkSet
|
|
812
1100
|
include Enumerable
|
|
813
1101
|
|
|
814
|
-
def each
|
|
1102
|
+
def each(&block)
|
|
1103
|
+
results = []
|
|
1104
|
+
procs = nil
|
|
1105
|
+
all_works = nil
|
|
1106
|
+
|
|
815
1107
|
Sidekiq.redis do |conn|
|
|
816
|
-
procs = conn.
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
conn.hgetall("#{key}:workers")
|
|
821
|
-
end
|
|
822
|
-
next unless valid
|
|
823
|
-
workers.each_pair do |tid, json|
|
|
824
|
-
yield key, tid, Sidekiq.load_json(json)
|
|
1108
|
+
procs = conn.sscan("processes").to_a.sort
|
|
1109
|
+
all_works = conn.pipelined do |pipeline|
|
|
1110
|
+
procs.each do |key|
|
|
1111
|
+
pipeline.hgetall("#{key}:work")
|
|
825
1112
|
end
|
|
826
1113
|
end
|
|
827
1114
|
end
|
|
1115
|
+
|
|
1116
|
+
procs.zip(all_works).each do |key, workers|
|
|
1117
|
+
workers.each_pair do |tid, json|
|
|
1118
|
+
results << [key, tid, Sidekiq::Work.new(key, tid, Sidekiq.load_json(json))] unless json.empty?
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
results.sort_by { |(_, _, hsh)| hsh.raw("run_at") }.each(&block)
|
|
828
1123
|
end
|
|
829
1124
|
|
|
830
1125
|
# Note that #size is only as accurate as Sidekiq's heartbeat,
|
|
@@ -835,18 +1130,88 @@ module Sidekiq
|
|
|
835
1130
|
# which can easily get out of sync with crashy processes.
|
|
836
1131
|
def size
|
|
837
1132
|
Sidekiq.redis do |conn|
|
|
838
|
-
procs = conn.
|
|
1133
|
+
procs = conn.sscan("processes").to_a
|
|
839
1134
|
if procs.empty?
|
|
840
1135
|
0
|
|
841
1136
|
else
|
|
842
|
-
conn.pipelined
|
|
1137
|
+
conn.pipelined { |pipeline|
|
|
843
1138
|
procs.each do |key|
|
|
844
|
-
|
|
1139
|
+
pipeline.hget(key, "busy")
|
|
845
1140
|
end
|
|
846
|
-
|
|
1141
|
+
}.sum(&:to_i)
|
|
847
1142
|
end
|
|
848
1143
|
end
|
|
849
1144
|
end
|
|
1145
|
+
|
|
1146
|
+
##
|
|
1147
|
+
# Find the work which represents a job with the given JID.
|
|
1148
|
+
# *This is a slow O(n) operation*. Do not use for app logic.
|
|
1149
|
+
#
|
|
1150
|
+
# @param jid [String] the job identifier
|
|
1151
|
+
# @return [Sidekiq::Work] the work or nil
|
|
1152
|
+
def find_work_by_jid(jid)
|
|
1153
|
+
each do |_process_id, _thread_id, work|
|
|
1154
|
+
job = work.job
|
|
1155
|
+
return work if job.jid == jid
|
|
1156
|
+
end
|
|
1157
|
+
nil
|
|
1158
|
+
end
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
# Sidekiq::Work represents a job which is currently executing.
|
|
1162
|
+
class Work
|
|
1163
|
+
attr_reader :process_id
|
|
1164
|
+
attr_reader :thread_id
|
|
1165
|
+
|
|
1166
|
+
def initialize(pid, tid, hsh)
|
|
1167
|
+
@process_id = pid
|
|
1168
|
+
@thread_id = tid
|
|
1169
|
+
@hsh = hsh
|
|
1170
|
+
@job = nil
|
|
1171
|
+
end
|
|
1172
|
+
|
|
1173
|
+
def queue
|
|
1174
|
+
@hsh["queue"]
|
|
1175
|
+
end
|
|
1176
|
+
|
|
1177
|
+
def run_at
|
|
1178
|
+
Time.at(@hsh["run_at"])
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
def job
|
|
1182
|
+
@job ||= Sidekiq::JobRecord.new(@hsh["payload"])
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
def payload
|
|
1186
|
+
@hsh["payload"]
|
|
1187
|
+
end
|
|
1188
|
+
|
|
1189
|
+
# deprecated
|
|
1190
|
+
def [](key)
|
|
1191
|
+
kwargs = {uplevel: 1}
|
|
1192
|
+
kwargs[:category] = :deprecated if RUBY_VERSION > "3.0" # TODO
|
|
1193
|
+
warn("Direct access to `Sidekiq::Work` attributes is deprecated, please use `#payload`, `#queue`, `#run_at` or `#job` instead", **kwargs)
|
|
1194
|
+
|
|
1195
|
+
@hsh[key]
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
# :nodoc:
|
|
1199
|
+
# @api private
|
|
1200
|
+
def raw(name)
|
|
1201
|
+
@hsh[name]
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
def method_missing(*all)
|
|
1205
|
+
@hsh.send(*all)
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
def respond_to_missing?(name, *args)
|
|
1209
|
+
@hsh.respond_to?(name)
|
|
1210
|
+
end
|
|
850
1211
|
end
|
|
851
1212
|
|
|
1213
|
+
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
|
1214
|
+
# Is "worker" a process, a type of job, a thread? Undefined!
|
|
1215
|
+
# WorkSet better describes the data.
|
|
1216
|
+
Workers = WorkSet
|
|
852
1217
|
end
|