sidekiq 4.2.2 → 6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Changes.md +516 -0
- data/LICENSE +2 -2
- data/README.md +23 -36
- data/bin/sidekiq +26 -2
- data/bin/sidekiqload +28 -38
- data/bin/sidekiqmon +8 -0
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +2 -2
- data/lib/generators/sidekiq/worker_generator.rb +21 -13
- data/lib/sidekiq/api.rb +401 -243
- data/lib/sidekiq/cli.rb +228 -212
- data/lib/sidekiq/client.rb +76 -53
- data/lib/sidekiq/delay.rb +41 -0
- data/lib/sidekiq/exception_handler.rb +12 -16
- data/lib/sidekiq/extensions/action_mailer.rb +13 -22
- data/lib/sidekiq/extensions/active_record.rb +13 -10
- data/lib/sidekiq/extensions/class_methods.rb +14 -11
- data/lib/sidekiq/extensions/generic_proxy.rb +12 -4
- data/lib/sidekiq/fetch.rb +39 -31
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +63 -0
- data/lib/sidekiq/job_retry.rb +259 -0
- data/lib/sidekiq/launcher.rb +170 -71
- data/lib/sidekiq/logger.rb +166 -0
- data/lib/sidekiq/manager.rb +17 -20
- data/lib/sidekiq/middleware/chain.rb +20 -8
- data/lib/sidekiq/middleware/current_attributes.rb +52 -0
- data/lib/sidekiq/middleware/i18n.rb +5 -7
- data/lib/sidekiq/monitor.rb +133 -0
- data/lib/sidekiq/paginator.rb +18 -14
- data/lib/sidekiq/processor.rb +169 -78
- data/lib/sidekiq/rails.rb +41 -36
- data/lib/sidekiq/redis_connection.rb +65 -20
- data/lib/sidekiq/scheduled.rb +85 -34
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing/inline.rb +2 -1
- data/lib/sidekiq/testing.rb +52 -26
- data/lib/sidekiq/util.rb +48 -15
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +15 -17
- data/lib/sidekiq/web/application.rb +114 -92
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +151 -83
- data/lib/sidekiq/web/router.rb +27 -19
- data/lib/sidekiq/web.rb +85 -76
- data/lib/sidekiq/worker.rb +233 -43
- data/lib/sidekiq.rb +88 -64
- data/sidekiq.gemspec +24 -22
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +86 -59
- data/web/assets/javascripts/dashboard.js +81 -85
- data/web/assets/stylesheets/application-dark.css +147 -0
- data/web/assets/stylesheets/application-rtl.css +242 -0
- data/web/assets/stylesheets/application.css +319 -141
- 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/de.yml +14 -2
- data/web/locales/en.yml +8 -1
- data/web/locales/es.yml +22 -5
- data/web/locales/fa.yml +80 -0
- data/web/locales/fr.yml +10 -3
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +12 -4
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/locales/ur.yml +80 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +4 -3
- data/web/views/_nav.erb +4 -18
- data/web/views/_paging.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +60 -22
- data/web/views/dashboard.erb +23 -15
- data/web/views/dead.erb +3 -3
- data/web/views/layout.erb +14 -3
- data/web/views/morgue.erb +19 -12
- data/web/views/queue.erb +24 -14
- data/web/views/queues.erb +14 -4
- data/web/views/retries.erb +22 -13
- data/web/views/retry.erb +4 -4
- data/web/views/scheduled.erb +7 -4
- metadata +44 -194
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -4
- data/.gitignore +0 -12
- data/.travis.yml +0 -12
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/COMM-LICENSE +0 -95
- data/Ent-Changes.md +0 -146
- data/Gemfile +0 -29
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-Changes.md +0 -570
- data/Rakefile +0 -9
- data/bin/sidekiqctl +0 -99
- data/code_of_conduct.md +0 -50
- data/lib/sidekiq/core_ext.rb +0 -106
- 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 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -1
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -75
- data/test/test_actors.rb +0 -138
- data/test/test_api.rb +0 -528
- data/test/test_cli.rb +0 -418
- data/test/test_client.rb +0 -266
- data/test/test_exception_handler.rb +0 -56
- data/test/test_extensions.rb +0 -127
- data/test/test_fetch.rb +0 -50
- data/test/test_launcher.rb +0 -95
- data/test/test_logging.rb +0 -35
- data/test/test_manager.rb +0 -50
- data/test/test_middleware.rb +0 -158
- data/test/test_processor.rb +0 -201
- data/test/test_rails.rb +0 -22
- data/test/test_redis_connection.rb +0 -132
- data/test/test_retry.rb +0 -326
- data/test/test_retry_exhausted.rb +0 -149
- data/test/test_scheduled.rb +0 -115
- data/test/test_scheduling.rb +0 -50
- data/test/test_sidekiq.rb +0 -107
- data/test/test_testing.rb +0 -143
- data/test/test_testing_fake.rb +0 -357
- data/test/test_testing_inline.rb +0 -94
- data/test/test_util.rb +0 -13
- data/test/test_web.rb +0 -666
- data/test/test_web_helpers.rb +0 -54
data/lib/sidekiq/api.rb
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require
|
|
2
|
+
|
|
3
|
+
require "sidekiq"
|
|
4
|
+
|
|
5
|
+
require "zlib"
|
|
6
|
+
require "base64"
|
|
4
7
|
|
|
5
8
|
module Sidekiq
|
|
6
9
|
class Stats
|
|
7
10
|
def initialize
|
|
8
|
-
|
|
11
|
+
fetch_stats_fast!
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
def processed
|
|
@@ -48,53 +51,78 @@ module Sidekiq
|
|
|
48
51
|
Sidekiq::Stats::Queues.new.lengths
|
|
49
52
|
end
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
# O(1) redis calls
|
|
55
|
+
def fetch_stats_fast!
|
|
56
|
+
pipe1_res = Sidekiq.redis { |conn|
|
|
53
57
|
conn.pipelined do
|
|
54
|
-
conn.get(
|
|
55
|
-
conn.get(
|
|
56
|
-
conn.zcard(
|
|
57
|
-
conn.zcard(
|
|
58
|
-
conn.zcard(
|
|
59
|
-
conn.scard(
|
|
60
|
-
conn.lrange(
|
|
61
|
-
conn.smembers('processes'.freeze)
|
|
62
|
-
conn.smembers('queues'.freeze)
|
|
58
|
+
conn.get("stat:processed")
|
|
59
|
+
conn.get("stat:failed")
|
|
60
|
+
conn.zcard("schedule")
|
|
61
|
+
conn.zcard("retry")
|
|
62
|
+
conn.zcard("dead")
|
|
63
|
+
conn.scard("processes")
|
|
64
|
+
conn.lrange("queue:default", -1, -1)
|
|
63
65
|
end
|
|
64
|
-
|
|
66
|
+
}
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
default_queue_latency = if (entry = pipe1_res[6].first)
|
|
69
|
+
job = begin
|
|
70
|
+
Sidekiq.load_json(entry)
|
|
71
|
+
rescue
|
|
72
|
+
{}
|
|
70
73
|
end
|
|
74
|
+
now = Time.now.to_f
|
|
75
|
+
thence = job["enqueued_at"] || now
|
|
76
|
+
now - thence
|
|
77
|
+
else
|
|
78
|
+
0
|
|
71
79
|
end
|
|
72
80
|
|
|
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
|
-
Time.now.to_f - Sidekiq.load_json(entry)['enqueued_at'.freeze]
|
|
79
|
-
else
|
|
80
|
-
0
|
|
81
|
-
end
|
|
82
81
|
@stats = {
|
|
83
|
-
processed:
|
|
84
|
-
failed:
|
|
85
|
-
scheduled_size:
|
|
86
|
-
retry_size:
|
|
87
|
-
dead_size:
|
|
88
|
-
processes_size:
|
|
89
|
-
|
|
90
|
-
default_queue_latency: default_queue_latency
|
|
91
|
-
workers_size: workers_size,
|
|
92
|
-
enqueued: enqueued
|
|
82
|
+
processed: pipe1_res[0].to_i,
|
|
83
|
+
failed: pipe1_res[1].to_i,
|
|
84
|
+
scheduled_size: pipe1_res[2],
|
|
85
|
+
retry_size: pipe1_res[3],
|
|
86
|
+
dead_size: pipe1_res[4],
|
|
87
|
+
processes_size: pipe1_res[5],
|
|
88
|
+
|
|
89
|
+
default_queue_latency: default_queue_latency
|
|
93
90
|
}
|
|
94
91
|
end
|
|
95
92
|
|
|
93
|
+
# O(number of processes + number of queues) redis calls
|
|
94
|
+
def fetch_stats_slow!
|
|
95
|
+
processes = Sidekiq.redis { |conn|
|
|
96
|
+
conn.sscan_each("processes").to_a
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
queues = Sidekiq.redis { |conn|
|
|
100
|
+
conn.sscan_each("queues").to_a
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pipe2_res = Sidekiq.redis { |conn|
|
|
104
|
+
conn.pipelined do
|
|
105
|
+
processes.each { |key| conn.hget(key, "busy") }
|
|
106
|
+
queues.each { |queue| conn.llen("queue:#{queue}") }
|
|
107
|
+
end
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
s = processes.size
|
|
111
|
+
workers_size = pipe2_res[0...s].sum(&:to_i)
|
|
112
|
+
enqueued = pipe2_res[s..-1].sum(&:to_i)
|
|
113
|
+
|
|
114
|
+
@stats[:workers_size] = workers_size
|
|
115
|
+
@stats[:enqueued] = enqueued
|
|
116
|
+
@stats
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def fetch_stats!
|
|
120
|
+
fetch_stats_fast!
|
|
121
|
+
fetch_stats_slow!
|
|
122
|
+
end
|
|
123
|
+
|
|
96
124
|
def reset(*stats)
|
|
97
|
-
all
|
|
125
|
+
all = %w[failed processed]
|
|
98
126
|
stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s)
|
|
99
127
|
|
|
100
128
|
mset_args = []
|
|
@@ -110,28 +138,23 @@ module Sidekiq
|
|
|
110
138
|
private
|
|
111
139
|
|
|
112
140
|
def stat(s)
|
|
113
|
-
@stats[s]
|
|
141
|
+
fetch_stats_slow! if @stats[s].nil?
|
|
142
|
+
@stats[s] || raise(ArgumentError, "Unknown stat #{s}")
|
|
114
143
|
end
|
|
115
144
|
|
|
116
145
|
class Queues
|
|
117
146
|
def lengths
|
|
118
147
|
Sidekiq.redis do |conn|
|
|
119
|
-
queues = conn.
|
|
148
|
+
queues = conn.sscan_each("queues").to_a
|
|
120
149
|
|
|
121
|
-
lengths = conn.pipelined
|
|
150
|
+
lengths = conn.pipelined {
|
|
122
151
|
queues.each do |queue|
|
|
123
152
|
conn.llen("queue:#{queue}")
|
|
124
153
|
end
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
i = 0
|
|
128
|
-
array_of_arrays = queues.inject({}) do |memo, queue|
|
|
129
|
-
memo[queue] = lengths[i]
|
|
130
|
-
i += 1
|
|
131
|
-
memo
|
|
132
|
-
end.sort_by { |_, size| size }
|
|
154
|
+
}
|
|
133
155
|
|
|
134
|
-
|
|
156
|
+
array_of_arrays = queues.zip(lengths).sort_by { |_, size| -size }
|
|
157
|
+
array_of_arrays.to_h
|
|
135
158
|
end
|
|
136
159
|
end
|
|
137
160
|
end
|
|
@@ -143,33 +166,32 @@ module Sidekiq
|
|
|
143
166
|
end
|
|
144
167
|
|
|
145
168
|
def processed
|
|
146
|
-
date_stat_hash("processed")
|
|
169
|
+
@processed ||= date_stat_hash("processed")
|
|
147
170
|
end
|
|
148
171
|
|
|
149
172
|
def failed
|
|
150
|
-
date_stat_hash("failed")
|
|
173
|
+
@failed ||= date_stat_hash("failed")
|
|
151
174
|
end
|
|
152
175
|
|
|
153
176
|
private
|
|
154
177
|
|
|
155
178
|
def date_stat_hash(stat)
|
|
156
|
-
i = 0
|
|
157
179
|
stat_hash = {}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
while i < @days_previous
|
|
162
|
-
date = @start_date - i
|
|
163
|
-
datestr = date.strftime("%Y-%m-%d".freeze)
|
|
164
|
-
keys << "stat:#{stat}:#{datestr}"
|
|
165
|
-
dates << datestr
|
|
166
|
-
i += 1
|
|
167
|
-
end
|
|
180
|
+
dates = @start_date.downto(@start_date - @days_previous + 1).map { |date|
|
|
181
|
+
date.strftime("%Y-%m-%d")
|
|
182
|
+
}
|
|
168
183
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
184
|
+
keys = dates.map { |datestr| "stat:#{stat}:#{datestr}" }
|
|
185
|
+
|
|
186
|
+
begin
|
|
187
|
+
Sidekiq.redis do |conn|
|
|
188
|
+
conn.mget(keys).each_with_index do |value, idx|
|
|
189
|
+
stat_hash[dates[idx]] = value ? value.to_i : 0
|
|
190
|
+
end
|
|
172
191
|
end
|
|
192
|
+
rescue Redis::CommandError
|
|
193
|
+
# mget will trigger a CROSSSLOT error when run against a Cluster
|
|
194
|
+
# TODO Someone want to add Cluster support?
|
|
173
195
|
end
|
|
174
196
|
|
|
175
197
|
stat_hash
|
|
@@ -196,13 +218,13 @@ module Sidekiq
|
|
|
196
218
|
# Return all known queues within Redis.
|
|
197
219
|
#
|
|
198
220
|
def self.all
|
|
199
|
-
Sidekiq.redis { |c| c.
|
|
221
|
+
Sidekiq.redis { |c| c.sscan_each("queues").to_a }.sort.map { |q| Sidekiq::Queue.new(q) }
|
|
200
222
|
end
|
|
201
223
|
|
|
202
224
|
attr_reader :name
|
|
203
225
|
|
|
204
|
-
def initialize(name="default")
|
|
205
|
-
@name = name
|
|
226
|
+
def initialize(name = "default")
|
|
227
|
+
@name = name.to_s
|
|
206
228
|
@rname = "queue:#{name}"
|
|
207
229
|
end
|
|
208
230
|
|
|
@@ -221,11 +243,14 @@ module Sidekiq
|
|
|
221
243
|
#
|
|
222
244
|
# @return Float
|
|
223
245
|
def latency
|
|
224
|
-
entry = Sidekiq.redis
|
|
246
|
+
entry = Sidekiq.redis { |conn|
|
|
225
247
|
conn.lrange(@rname, -1, -1)
|
|
226
|
-
|
|
248
|
+
}.first
|
|
227
249
|
return 0 unless entry
|
|
228
|
-
|
|
250
|
+
job = Sidekiq.load_json(entry)
|
|
251
|
+
now = Time.now.to_f
|
|
252
|
+
thence = job["enqueued_at"] || now
|
|
253
|
+
now - thence
|
|
229
254
|
end
|
|
230
255
|
|
|
231
256
|
def each
|
|
@@ -234,16 +259,16 @@ module Sidekiq
|
|
|
234
259
|
page = 0
|
|
235
260
|
page_size = 50
|
|
236
261
|
|
|
237
|
-
|
|
262
|
+
loop do
|
|
238
263
|
range_start = page * page_size - deleted_size
|
|
239
|
-
range_end
|
|
240
|
-
entries = Sidekiq.redis
|
|
264
|
+
range_end = range_start + page_size - 1
|
|
265
|
+
entries = Sidekiq.redis { |conn|
|
|
241
266
|
conn.lrange @rname, range_start, range_end
|
|
242
|
-
|
|
267
|
+
}
|
|
243
268
|
break if entries.empty?
|
|
244
269
|
page += 1
|
|
245
270
|
entries.each do |entry|
|
|
246
|
-
yield
|
|
271
|
+
yield JobRecord.new(entry, @name)
|
|
247
272
|
end
|
|
248
273
|
deleted_size = initial_size - size
|
|
249
274
|
end
|
|
@@ -253,7 +278,7 @@ module Sidekiq
|
|
|
253
278
|
# Find the job with the given JID within this queue.
|
|
254
279
|
#
|
|
255
280
|
# This is a slow, inefficient operation. Do not use under
|
|
256
|
-
# normal conditions.
|
|
281
|
+
# normal conditions.
|
|
257
282
|
def find_job(jid)
|
|
258
283
|
detect { |j| j.jid == jid }
|
|
259
284
|
end
|
|
@@ -261,8 +286,8 @@ module Sidekiq
|
|
|
261
286
|
def clear
|
|
262
287
|
Sidekiq.redis do |conn|
|
|
263
288
|
conn.multi do
|
|
264
|
-
conn.
|
|
265
|
-
conn.srem("queues"
|
|
289
|
+
conn.unlink(@rname)
|
|
290
|
+
conn.srem("queues", name)
|
|
266
291
|
end
|
|
267
292
|
end
|
|
268
293
|
end
|
|
@@ -274,114 +299,166 @@ module Sidekiq
|
|
|
274
299
|
# sorted set.
|
|
275
300
|
#
|
|
276
301
|
# The job should be considered immutable but may be
|
|
277
|
-
# removed from the queue via
|
|
302
|
+
# removed from the queue via JobRecord#delete.
|
|
278
303
|
#
|
|
279
|
-
class
|
|
304
|
+
class JobRecord
|
|
280
305
|
attr_reader :item
|
|
281
306
|
attr_reader :value
|
|
282
307
|
|
|
283
|
-
def initialize(item, queue_name=nil)
|
|
308
|
+
def initialize(item, queue_name = nil)
|
|
309
|
+
@args = nil
|
|
284
310
|
@value = item
|
|
285
|
-
@item = item.is_a?(Hash) ? item :
|
|
286
|
-
@queue = queue_name || @item[
|
|
311
|
+
@item = item.is_a?(Hash) ? item : parse(item)
|
|
312
|
+
@queue = queue_name || @item["queue"]
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def parse(item)
|
|
316
|
+
Sidekiq.load_json(item)
|
|
317
|
+
rescue JSON::ParserError
|
|
318
|
+
# If the job payload in Redis is invalid JSON, we'll load
|
|
319
|
+
# the item as an empty hash and store the invalid JSON as
|
|
320
|
+
# the job 'args' for display in the Web UI.
|
|
321
|
+
@invalid = true
|
|
322
|
+
@args = [item]
|
|
323
|
+
{}
|
|
287
324
|
end
|
|
288
325
|
|
|
289
326
|
def klass
|
|
290
|
-
|
|
327
|
+
self["class"]
|
|
291
328
|
end
|
|
292
329
|
|
|
293
330
|
def display_class
|
|
294
331
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
|
295
|
-
@klass ||=
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
332
|
+
@klass ||= self["display_class"] || begin
|
|
333
|
+
case klass
|
|
334
|
+
when /\ASidekiq::Extensions::Delayed/
|
|
335
|
+
safe_load(args[0], klass) do |target, method, _|
|
|
336
|
+
"#{target}.#{method}"
|
|
337
|
+
end
|
|
338
|
+
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
|
339
|
+
job_class = @item["wrapped"] || args[0]
|
|
340
|
+
if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob"
|
|
341
|
+
# MailerClass#mailer_method
|
|
342
|
+
args[0]["arguments"][0..1].join("#")
|
|
343
|
+
else
|
|
344
|
+
job_class
|
|
345
|
+
end
|
|
346
|
+
else
|
|
347
|
+
klass
|
|
348
|
+
end
|
|
349
|
+
end
|
|
311
350
|
end
|
|
312
351
|
|
|
313
352
|
def display_args
|
|
314
353
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
|
315
|
-
@
|
|
354
|
+
@display_args ||= case klass
|
|
316
355
|
when /\ASidekiq::Extensions::Delayed/
|
|
317
356
|
safe_load(args[0], args) do |_, _, arg|
|
|
318
357
|
arg
|
|
319
358
|
end
|
|
320
359
|
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
|
321
|
-
job_args =
|
|
322
|
-
if
|
|
323
|
-
|
|
324
|
-
|
|
360
|
+
job_args = self["wrapped"] ? args[0]["arguments"] : []
|
|
361
|
+
if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob"
|
|
362
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
|
363
|
+
job_args.drop(3)
|
|
364
|
+
elsif (self["wrapped"] || args[0]) == "ActionMailer::MailDeliveryJob"
|
|
365
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
|
366
|
+
job_args.drop(3).first["args"]
|
|
325
367
|
else
|
|
326
|
-
|
|
368
|
+
job_args
|
|
327
369
|
end
|
|
328
370
|
else
|
|
371
|
+
if self["encrypt"]
|
|
372
|
+
# no point in showing 150+ bytes of random garbage
|
|
373
|
+
args[-1] = "[encrypted data]"
|
|
374
|
+
end
|
|
329
375
|
args
|
|
330
|
-
|
|
376
|
+
end
|
|
331
377
|
end
|
|
332
378
|
|
|
333
379
|
def args
|
|
334
|
-
@item[
|
|
380
|
+
@args || @item["args"]
|
|
335
381
|
end
|
|
336
382
|
|
|
337
383
|
def jid
|
|
338
|
-
|
|
384
|
+
self["jid"]
|
|
339
385
|
end
|
|
340
386
|
|
|
341
387
|
def enqueued_at
|
|
342
|
-
|
|
388
|
+
self["enqueued_at"] ? Time.at(self["enqueued_at"]).utc : nil
|
|
343
389
|
end
|
|
344
390
|
|
|
345
391
|
def created_at
|
|
346
|
-
Time.at(
|
|
392
|
+
Time.at(self["created_at"] || self["enqueued_at"] || 0).utc
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def tags
|
|
396
|
+
self["tags"] || []
|
|
347
397
|
end
|
|
348
398
|
|
|
349
|
-
def
|
|
350
|
-
|
|
399
|
+
def error_backtrace
|
|
400
|
+
# Cache nil values
|
|
401
|
+
if defined?(@error_backtrace)
|
|
402
|
+
@error_backtrace
|
|
403
|
+
else
|
|
404
|
+
value = self["error_backtrace"]
|
|
405
|
+
@error_backtrace = value && uncompress_backtrace(value)
|
|
406
|
+
end
|
|
351
407
|
end
|
|
352
408
|
|
|
409
|
+
attr_reader :queue
|
|
410
|
+
|
|
353
411
|
def latency
|
|
354
|
-
Time.now.to_f
|
|
412
|
+
now = Time.now.to_f
|
|
413
|
+
now - (@item["enqueued_at"] || @item["created_at"] || now)
|
|
355
414
|
end
|
|
356
415
|
|
|
357
416
|
##
|
|
358
417
|
# Remove this job from the queue.
|
|
359
418
|
def delete
|
|
360
|
-
count = Sidekiq.redis
|
|
419
|
+
count = Sidekiq.redis { |conn|
|
|
361
420
|
conn.lrem("queue:#{@queue}", 1, @value)
|
|
362
|
-
|
|
421
|
+
}
|
|
363
422
|
count != 0
|
|
364
423
|
end
|
|
365
424
|
|
|
366
425
|
def [](name)
|
|
367
|
-
|
|
426
|
+
# nil will happen if the JSON fails to parse.
|
|
427
|
+
# We don't guarantee Sidekiq will work with bad job JSON but we should
|
|
428
|
+
# make a best effort to minimize the damage.
|
|
429
|
+
@item ? @item[name] : nil
|
|
368
430
|
end
|
|
369
431
|
|
|
370
432
|
private
|
|
371
433
|
|
|
372
434
|
def safe_load(content, default)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
435
|
+
yield(*YAML.load(content))
|
|
436
|
+
rescue => ex
|
|
437
|
+
# #1761 in dev mode, it's possible to have jobs enqueued which haven't been loaded into
|
|
438
|
+
# memory yet so the YAML can't be loaded.
|
|
439
|
+
Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless Sidekiq.options[:environment] == "development"
|
|
440
|
+
default
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def uncompress_backtrace(backtrace)
|
|
444
|
+
if backtrace.is_a?(Array)
|
|
445
|
+
# Handle old jobs with raw Array backtrace format
|
|
446
|
+
backtrace
|
|
447
|
+
else
|
|
448
|
+
decoded = Base64.decode64(backtrace)
|
|
449
|
+
uncompressed = Zlib::Inflate.inflate(decoded)
|
|
450
|
+
begin
|
|
451
|
+
Sidekiq.load_json(uncompressed)
|
|
452
|
+
rescue
|
|
453
|
+
# Handle old jobs with marshalled backtrace format
|
|
454
|
+
# TODO Remove in 7.x
|
|
455
|
+
Marshal.load(uncompressed)
|
|
456
|
+
end
|
|
380
457
|
end
|
|
381
458
|
end
|
|
382
459
|
end
|
|
383
460
|
|
|
384
|
-
class SortedEntry <
|
|
461
|
+
class SortedEntry < JobRecord
|
|
385
462
|
attr_reader :score
|
|
386
463
|
attr_reader :parent
|
|
387
464
|
|
|
@@ -404,8 +481,9 @@ module Sidekiq
|
|
|
404
481
|
end
|
|
405
482
|
|
|
406
483
|
def reschedule(at)
|
|
407
|
-
|
|
408
|
-
|
|
484
|
+
Sidekiq.redis do |conn|
|
|
485
|
+
conn.zincrby(@parent.name, at.to_f - @score, Sidekiq.dump_json(@item))
|
|
486
|
+
end
|
|
409
487
|
end
|
|
410
488
|
|
|
411
489
|
def add_to_queue
|
|
@@ -416,10 +494,9 @@ module Sidekiq
|
|
|
416
494
|
end
|
|
417
495
|
|
|
418
496
|
def retry
|
|
419
|
-
raise "Retry not available on jobs which have not failed" unless item["failed_at"]
|
|
420
497
|
remove_job do |message|
|
|
421
498
|
msg = Sidekiq.load_json(message)
|
|
422
|
-
msg[
|
|
499
|
+
msg["retry_count"] -= 1 if msg["retry_count"]
|
|
423
500
|
Sidekiq::Client.push(msg)
|
|
424
501
|
end
|
|
425
502
|
end
|
|
@@ -427,56 +504,50 @@ module Sidekiq
|
|
|
427
504
|
##
|
|
428
505
|
# Place job in the dead set
|
|
429
506
|
def kill
|
|
430
|
-
raise 'Kill not available on jobs which have not failed' unless item['failed_at']
|
|
431
507
|
remove_job do |message|
|
|
432
|
-
|
|
433
|
-
now = Time.now.to_f
|
|
434
|
-
Sidekiq.redis do |conn|
|
|
435
|
-
conn.multi do
|
|
436
|
-
conn.zadd('dead', now, message)
|
|
437
|
-
conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout)
|
|
438
|
-
conn.zremrangebyrank('dead', 0, - DeadSet.max_jobs)
|
|
439
|
-
end
|
|
440
|
-
end
|
|
508
|
+
DeadSet.new.kill(message)
|
|
441
509
|
end
|
|
442
510
|
end
|
|
443
511
|
|
|
512
|
+
def error?
|
|
513
|
+
!!item["error_class"]
|
|
514
|
+
end
|
|
515
|
+
|
|
444
516
|
private
|
|
445
517
|
|
|
446
518
|
def remove_job
|
|
447
519
|
Sidekiq.redis do |conn|
|
|
448
|
-
results = conn.multi
|
|
520
|
+
results = conn.multi {
|
|
449
521
|
conn.zrangebyscore(parent.name, score, score)
|
|
450
522
|
conn.zremrangebyscore(parent.name, score, score)
|
|
451
|
-
|
|
523
|
+
}.first
|
|
452
524
|
|
|
453
525
|
if results.size == 1
|
|
454
526
|
yield results.first
|
|
455
527
|
else
|
|
456
528
|
# multiple jobs with the same score
|
|
457
529
|
# find the one with the right JID and push it
|
|
458
|
-
|
|
530
|
+
matched, nonmatched = results.partition { |message|
|
|
459
531
|
if message.index(jid)
|
|
460
532
|
msg = Sidekiq.load_json(message)
|
|
461
|
-
msg[
|
|
533
|
+
msg["jid"] == jid
|
|
462
534
|
else
|
|
463
535
|
false
|
|
464
536
|
end
|
|
465
|
-
|
|
537
|
+
}
|
|
466
538
|
|
|
467
|
-
msg =
|
|
539
|
+
msg = matched.first
|
|
468
540
|
yield msg if msg
|
|
469
541
|
|
|
470
542
|
# push the rest back onto the sorted set
|
|
471
543
|
conn.multi do
|
|
472
|
-
|
|
544
|
+
nonmatched.each do |message|
|
|
473
545
|
conn.zadd(parent.name, score.to_f.to_s, message)
|
|
474
546
|
end
|
|
475
547
|
end
|
|
476
548
|
end
|
|
477
549
|
end
|
|
478
550
|
end
|
|
479
|
-
|
|
480
551
|
end
|
|
481
552
|
|
|
482
553
|
class SortedSet
|
|
@@ -493,16 +564,26 @@ module Sidekiq
|
|
|
493
564
|
Sidekiq.redis { |c| c.zcard(name) }
|
|
494
565
|
end
|
|
495
566
|
|
|
567
|
+
def scan(match, count = 100)
|
|
568
|
+
return to_enum(:scan, match, count) unless block_given?
|
|
569
|
+
|
|
570
|
+
match = "*#{match}*" unless match.include?("*")
|
|
571
|
+
Sidekiq.redis do |conn|
|
|
572
|
+
conn.zscan_each(name, match: match, count: count) do |entry, score|
|
|
573
|
+
yield SortedEntry.new(self, score, entry)
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
496
578
|
def clear
|
|
497
579
|
Sidekiq.redis do |conn|
|
|
498
|
-
conn.
|
|
580
|
+
conn.unlink(name)
|
|
499
581
|
end
|
|
500
582
|
end
|
|
501
583
|
alias_method :💣, :clear
|
|
502
584
|
end
|
|
503
585
|
|
|
504
586
|
class JobSet < SortedSet
|
|
505
|
-
|
|
506
587
|
def schedule(timestamp, message)
|
|
507
588
|
Sidekiq.redis do |conn|
|
|
508
589
|
conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(message))
|
|
@@ -515,44 +596,55 @@ module Sidekiq
|
|
|
515
596
|
page = -1
|
|
516
597
|
page_size = 50
|
|
517
598
|
|
|
518
|
-
|
|
599
|
+
loop do
|
|
519
600
|
range_start = page * page_size + offset_size
|
|
520
|
-
range_end
|
|
521
|
-
elements = Sidekiq.redis
|
|
601
|
+
range_end = range_start + page_size - 1
|
|
602
|
+
elements = Sidekiq.redis { |conn|
|
|
522
603
|
conn.zrange name, range_start, range_end, with_scores: true
|
|
523
|
-
|
|
604
|
+
}
|
|
524
605
|
break if elements.empty?
|
|
525
606
|
page -= 1
|
|
526
|
-
elements.
|
|
607
|
+
elements.reverse_each do |element, score|
|
|
527
608
|
yield SortedEntry.new(self, score, element)
|
|
528
609
|
end
|
|
529
610
|
offset_size = initial_size - @_size
|
|
530
611
|
end
|
|
531
612
|
end
|
|
532
613
|
|
|
614
|
+
##
|
|
615
|
+
# Fetch jobs that match a given time or Range. Job ID is an
|
|
616
|
+
# optional second argument.
|
|
533
617
|
def fetch(score, jid = nil)
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
elements.inject([]) do |result, element|
|
|
539
|
-
entry = SortedEntry.new(self, score, element)
|
|
540
|
-
if jid
|
|
541
|
-
result << entry if entry.jid == jid
|
|
618
|
+
begin_score, end_score =
|
|
619
|
+
if score.is_a?(Range)
|
|
620
|
+
[score.first, score.last]
|
|
542
621
|
else
|
|
543
|
-
|
|
622
|
+
[score, score]
|
|
544
623
|
end
|
|
545
|
-
|
|
624
|
+
|
|
625
|
+
elements = Sidekiq.redis { |conn|
|
|
626
|
+
conn.zrangebyscore(name, begin_score, end_score, with_scores: true)
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
elements.each_with_object([]) do |element, result|
|
|
630
|
+
data, job_score = element
|
|
631
|
+
entry = SortedEntry.new(self, job_score, data)
|
|
632
|
+
result << entry if jid.nil? || entry.jid == jid
|
|
546
633
|
end
|
|
547
634
|
end
|
|
548
635
|
|
|
549
636
|
##
|
|
550
637
|
# Find the job with the given JID within this sorted set.
|
|
551
|
-
#
|
|
552
|
-
# This is a slow, inefficient operation. Do not use under
|
|
553
|
-
# normal conditions. Sidekiq Pro contains a faster version.
|
|
638
|
+
# This is a slower O(n) operation. Do not use for app logic.
|
|
554
639
|
def find_job(jid)
|
|
555
|
-
|
|
640
|
+
Sidekiq.redis do |conn|
|
|
641
|
+
conn.zscan_each(name, match: "*#{jid}*", count: 100) do |entry, score|
|
|
642
|
+
job = JSON.parse(entry)
|
|
643
|
+
matched = job["jid"] == jid
|
|
644
|
+
return SortedEntry.new(self, score, entry) if matched
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
nil
|
|
556
648
|
end
|
|
557
649
|
|
|
558
650
|
def delete_by_value(name, value)
|
|
@@ -567,13 +659,14 @@ module Sidekiq
|
|
|
567
659
|
Sidekiq.redis do |conn|
|
|
568
660
|
elements = conn.zrangebyscore(name, score, score)
|
|
569
661
|
elements.each do |element|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
662
|
+
if element.index(jid)
|
|
663
|
+
message = Sidekiq.load_json(element)
|
|
664
|
+
if message["jid"] == jid
|
|
665
|
+
ret = conn.zrem(name, element)
|
|
666
|
+
@_size -= 1 if ret
|
|
667
|
+
break ret
|
|
668
|
+
end
|
|
575
669
|
end
|
|
576
|
-
false
|
|
577
670
|
end
|
|
578
671
|
end
|
|
579
672
|
end
|
|
@@ -585,17 +678,17 @@ module Sidekiq
|
|
|
585
678
|
# Allows enumeration of scheduled jobs within Sidekiq.
|
|
586
679
|
# Based on this, you can search/filter for jobs. Here's an
|
|
587
680
|
# example where I'm selecting all jobs of a certain type
|
|
588
|
-
# and deleting them from the
|
|
681
|
+
# and deleting them from the schedule queue.
|
|
589
682
|
#
|
|
590
683
|
# r = Sidekiq::ScheduledSet.new
|
|
591
|
-
# r.select do |
|
|
592
|
-
#
|
|
593
|
-
#
|
|
594
|
-
#
|
|
684
|
+
# r.select do |scheduled|
|
|
685
|
+
# scheduled.klass == 'Sidekiq::Extensions::DelayedClass' &&
|
|
686
|
+
# scheduled.args[0] == 'User' &&
|
|
687
|
+
# scheduled.args[1] == 'setup_new_subscriber'
|
|
595
688
|
# end.map(&:delete)
|
|
596
689
|
class ScheduledSet < JobSet
|
|
597
690
|
def initialize
|
|
598
|
-
super
|
|
691
|
+
super "schedule"
|
|
599
692
|
end
|
|
600
693
|
end
|
|
601
694
|
|
|
@@ -613,13 +706,15 @@ module Sidekiq
|
|
|
613
706
|
# end.map(&:delete)
|
|
614
707
|
class RetrySet < JobSet
|
|
615
708
|
def initialize
|
|
616
|
-
super
|
|
709
|
+
super "retry"
|
|
617
710
|
end
|
|
618
711
|
|
|
619
712
|
def retry_all
|
|
620
|
-
while size > 0
|
|
621
|
-
|
|
622
|
-
|
|
713
|
+
each(&:retry) while size > 0
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def kill_all
|
|
717
|
+
each(&:kill) while size > 0
|
|
623
718
|
end
|
|
624
719
|
end
|
|
625
720
|
|
|
@@ -628,13 +723,32 @@ module Sidekiq
|
|
|
628
723
|
#
|
|
629
724
|
class DeadSet < JobSet
|
|
630
725
|
def initialize
|
|
631
|
-
super
|
|
726
|
+
super "dead"
|
|
632
727
|
end
|
|
633
728
|
|
|
634
|
-
def
|
|
635
|
-
|
|
636
|
-
|
|
729
|
+
def kill(message, opts = {})
|
|
730
|
+
now = Time.now.to_f
|
|
731
|
+
Sidekiq.redis do |conn|
|
|
732
|
+
conn.multi do
|
|
733
|
+
conn.zadd(name, now.to_s, message)
|
|
734
|
+
conn.zremrangebyscore(name, "-inf", now - self.class.timeout)
|
|
735
|
+
conn.zremrangebyrank(name, 0, - self.class.max_jobs)
|
|
736
|
+
end
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
if opts[:notify_failure] != false
|
|
740
|
+
job = Sidekiq.load_json(message)
|
|
741
|
+
r = RuntimeError.new("Job killed by API")
|
|
742
|
+
r.set_backtrace(caller)
|
|
743
|
+
Sidekiq.death_handlers.each do |handle|
|
|
744
|
+
handle.call(job, r)
|
|
745
|
+
end
|
|
637
746
|
end
|
|
747
|
+
true
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def retry_all
|
|
751
|
+
each(&:retry) while size > 0
|
|
638
752
|
end
|
|
639
753
|
|
|
640
754
|
def self.max_jobs
|
|
@@ -648,7 +762,7 @@ module Sidekiq
|
|
|
648
762
|
|
|
649
763
|
##
|
|
650
764
|
# Enumerates the set of Sidekiq processes which are actively working
|
|
651
|
-
# right now. Each process
|
|
765
|
+
# right now. Each process sends a heartbeat to Redis every 5 seconds
|
|
652
766
|
# so this set should be relatively accurate, barring network partitions.
|
|
653
767
|
#
|
|
654
768
|
# Yields a Sidekiq::Process.
|
|
@@ -656,54 +770,60 @@ module Sidekiq
|
|
|
656
770
|
class ProcessSet
|
|
657
771
|
include Enumerable
|
|
658
772
|
|
|
659
|
-
def initialize(clean_plz=true)
|
|
660
|
-
|
|
773
|
+
def initialize(clean_plz = true)
|
|
774
|
+
cleanup if clean_plz
|
|
661
775
|
end
|
|
662
776
|
|
|
663
777
|
# Cleans up dead processes recorded in Redis.
|
|
664
778
|
# Returns the number of processes cleaned.
|
|
665
|
-
def
|
|
779
|
+
def cleanup
|
|
666
780
|
count = 0
|
|
667
781
|
Sidekiq.redis do |conn|
|
|
668
|
-
procs = conn.
|
|
669
|
-
heartbeats = conn.pipelined
|
|
782
|
+
procs = conn.sscan_each("processes").to_a.sort
|
|
783
|
+
heartbeats = conn.pipelined {
|
|
670
784
|
procs.each do |key|
|
|
671
|
-
conn.hget(key,
|
|
785
|
+
conn.hget(key, "info")
|
|
672
786
|
end
|
|
673
|
-
|
|
787
|
+
}
|
|
674
788
|
|
|
675
789
|
# the hash named key has an expiry of 60 seconds.
|
|
676
790
|
# if it's not found, that means the process has not reported
|
|
677
791
|
# in to Redis and probably died.
|
|
678
|
-
to_prune =
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
count = conn.srem('processes', to_prune) unless to_prune.empty?
|
|
792
|
+
to_prune = procs.select.with_index { |proc, i|
|
|
793
|
+
heartbeats[i].nil?
|
|
794
|
+
}
|
|
795
|
+
count = conn.srem("processes", to_prune) unless to_prune.empty?
|
|
683
796
|
end
|
|
684
797
|
count
|
|
685
798
|
end
|
|
686
799
|
|
|
687
800
|
def each
|
|
688
|
-
|
|
801
|
+
result = Sidekiq.redis { |conn|
|
|
802
|
+
procs = conn.sscan_each("processes").to_a.sort
|
|
689
803
|
|
|
690
|
-
Sidekiq.redis do |conn|
|
|
691
804
|
# We're making a tradeoff here between consuming more memory instead of
|
|
692
805
|
# making more roundtrips to Redis, but if you have hundreds or thousands of workers,
|
|
693
806
|
# you'll be happier this way
|
|
694
|
-
|
|
807
|
+
conn.pipelined do
|
|
695
808
|
procs.each do |key|
|
|
696
|
-
conn.hmget(key,
|
|
809
|
+
conn.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us")
|
|
697
810
|
end
|
|
698
811
|
end
|
|
812
|
+
}
|
|
699
813
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
814
|
+
result.each do |info, busy, at_s, quiet, rss, rtt|
|
|
815
|
+
# If a process is stopped between when we query Redis for `procs` and
|
|
816
|
+
# when we query for `result`, we will have an item in `result` that is
|
|
817
|
+
# composed of `nil` values.
|
|
818
|
+
next if info.nil?
|
|
705
819
|
|
|
706
|
-
|
|
820
|
+
hash = Sidekiq.load_json(info)
|
|
821
|
+
yield Process.new(hash.merge("busy" => busy.to_i,
|
|
822
|
+
"beat" => at_s.to_f,
|
|
823
|
+
"quiet" => quiet,
|
|
824
|
+
"rss" => rss.to_i,
|
|
825
|
+
"rtt_us" => rtt.to_i))
|
|
826
|
+
end
|
|
707
827
|
end
|
|
708
828
|
|
|
709
829
|
# This method is not guaranteed accurate since it does not prune the set
|
|
@@ -711,7 +831,31 @@ module Sidekiq
|
|
|
711
831
|
# contains Sidekiq processes which have sent a heartbeat within the last
|
|
712
832
|
# 60 seconds.
|
|
713
833
|
def size
|
|
714
|
-
Sidekiq.redis { |conn| conn.scard(
|
|
834
|
+
Sidekiq.redis { |conn| conn.scard("processes") }
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
# Total number of threads available to execute jobs.
|
|
838
|
+
# For Sidekiq Enterprise customers this number (in production) must be
|
|
839
|
+
# less than or equal to your licensed concurrency.
|
|
840
|
+
def total_concurrency
|
|
841
|
+
sum { |x| x["concurrency"].to_i }
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def total_rss_in_kb
|
|
845
|
+
sum { |x| x["rss"].to_i }
|
|
846
|
+
end
|
|
847
|
+
alias_method :total_rss, :total_rss_in_kb
|
|
848
|
+
|
|
849
|
+
# Returns the identity of the current cluster leader or "" if no leader.
|
|
850
|
+
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
|
851
|
+
# or Sidekiq Pro.
|
|
852
|
+
def leader
|
|
853
|
+
@leader ||= begin
|
|
854
|
+
x = Sidekiq.redis { |c| c.get("dear-leader") }
|
|
855
|
+
# need a non-falsy value so we can memoize
|
|
856
|
+
x ||= ""
|
|
857
|
+
x
|
|
858
|
+
end
|
|
715
859
|
end
|
|
716
860
|
end
|
|
717
861
|
|
|
@@ -736,31 +880,39 @@ module Sidekiq
|
|
|
736
880
|
end
|
|
737
881
|
|
|
738
882
|
def tag
|
|
739
|
-
self[
|
|
883
|
+
self["tag"]
|
|
740
884
|
end
|
|
741
885
|
|
|
742
886
|
def labels
|
|
743
|
-
Array(self[
|
|
887
|
+
Array(self["labels"])
|
|
744
888
|
end
|
|
745
889
|
|
|
746
890
|
def [](key)
|
|
747
891
|
@attribs[key]
|
|
748
892
|
end
|
|
749
893
|
|
|
894
|
+
def identity
|
|
895
|
+
self["identity"]
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
def queues
|
|
899
|
+
self["queues"]
|
|
900
|
+
end
|
|
901
|
+
|
|
750
902
|
def quiet!
|
|
751
|
-
signal(
|
|
903
|
+
signal("TSTP")
|
|
752
904
|
end
|
|
753
905
|
|
|
754
906
|
def stop!
|
|
755
|
-
signal(
|
|
907
|
+
signal("TERM")
|
|
756
908
|
end
|
|
757
909
|
|
|
758
910
|
def dump_threads
|
|
759
|
-
signal(
|
|
911
|
+
signal("TTIN")
|
|
760
912
|
end
|
|
761
913
|
|
|
762
914
|
def stopping?
|
|
763
|
-
self[
|
|
915
|
+
self["quiet"] == "true"
|
|
764
916
|
end
|
|
765
917
|
|
|
766
918
|
private
|
|
@@ -774,15 +926,11 @@ module Sidekiq
|
|
|
774
926
|
end
|
|
775
927
|
end
|
|
776
928
|
end
|
|
777
|
-
|
|
778
|
-
def identity
|
|
779
|
-
self['identity']
|
|
780
|
-
end
|
|
781
929
|
end
|
|
782
930
|
|
|
783
931
|
##
|
|
784
|
-
#
|
|
785
|
-
#
|
|
932
|
+
# The WorkSet stores the work being done by this Sidekiq cluster.
|
|
933
|
+
# It tracks the process and thread working on each job.
|
|
786
934
|
#
|
|
787
935
|
# WARNING WARNING WARNING
|
|
788
936
|
#
|
|
@@ -790,33 +938,40 @@ module Sidekiq
|
|
|
790
938
|
# If you call #size => 5 and then expect #each to be
|
|
791
939
|
# called 5 times, you're going to have a bad time.
|
|
792
940
|
#
|
|
793
|
-
#
|
|
794
|
-
#
|
|
795
|
-
#
|
|
941
|
+
# works = Sidekiq::WorkSet.new
|
|
942
|
+
# works.size => 2
|
|
943
|
+
# works.each do |process_id, thread_id, work|
|
|
796
944
|
# # process_id is a unique identifier per Sidekiq process
|
|
797
945
|
# # thread_id is a unique identifier per thread
|
|
798
946
|
# # work is a Hash which looks like:
|
|
799
|
-
# # { 'queue' => name, 'run_at' => timestamp, 'payload' =>
|
|
947
|
+
# # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash }
|
|
800
948
|
# # run_at is an epoch Integer.
|
|
801
949
|
# end
|
|
802
950
|
#
|
|
803
|
-
class
|
|
951
|
+
class WorkSet
|
|
804
952
|
include Enumerable
|
|
805
953
|
|
|
806
|
-
def each
|
|
954
|
+
def each(&block)
|
|
955
|
+
results = []
|
|
807
956
|
Sidekiq.redis do |conn|
|
|
808
|
-
procs = conn.
|
|
957
|
+
procs = conn.sscan_each("processes").to_a
|
|
809
958
|
procs.sort.each do |key|
|
|
810
|
-
valid, workers = conn.pipelined
|
|
811
|
-
conn.exists(key)
|
|
959
|
+
valid, workers = conn.pipelined {
|
|
960
|
+
conn.exists?(key)
|
|
812
961
|
conn.hgetall("#{key}:workers")
|
|
813
|
-
|
|
962
|
+
}
|
|
814
963
|
next unless valid
|
|
815
964
|
workers.each_pair do |tid, json|
|
|
816
|
-
|
|
965
|
+
hsh = Sidekiq.load_json(json)
|
|
966
|
+
p = hsh["payload"]
|
|
967
|
+
# avoid breaking API, this is a side effect of the JSON optimization in #4316
|
|
968
|
+
hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String)
|
|
969
|
+
results << [key, tid, hsh]
|
|
817
970
|
end
|
|
818
971
|
end
|
|
819
972
|
end
|
|
973
|
+
|
|
974
|
+
results.sort_by { |(_, _, hsh)| hsh["run_at"] }.each(&block)
|
|
820
975
|
end
|
|
821
976
|
|
|
822
977
|
# Note that #size is only as accurate as Sidekiq's heartbeat,
|
|
@@ -827,18 +982,21 @@ module Sidekiq
|
|
|
827
982
|
# which can easily get out of sync with crashy processes.
|
|
828
983
|
def size
|
|
829
984
|
Sidekiq.redis do |conn|
|
|
830
|
-
procs = conn.
|
|
985
|
+
procs = conn.sscan_each("processes").to_a
|
|
831
986
|
if procs.empty?
|
|
832
987
|
0
|
|
833
988
|
else
|
|
834
|
-
conn.pipelined
|
|
989
|
+
conn.pipelined {
|
|
835
990
|
procs.each do |key|
|
|
836
|
-
conn.hget(key,
|
|
991
|
+
conn.hget(key, "busy")
|
|
837
992
|
end
|
|
838
|
-
|
|
993
|
+
}.sum(&:to_i)
|
|
839
994
|
end
|
|
840
995
|
end
|
|
841
996
|
end
|
|
842
997
|
end
|
|
843
|
-
|
|
998
|
+
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
|
999
|
+
# Is "worker" a process, a type of job, a thread? Undefined!
|
|
1000
|
+
# WorkSet better describes the data.
|
|
1001
|
+
Workers = WorkSet
|
|
844
1002
|
end
|