sidekiq 4.2.4 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/issue_template.md +8 -1
- data/.gitignore +1 -0
- data/.travis.yml +5 -3
- data/5.0-Upgrade.md +56 -0
- data/COMM-LICENSE +1 -1
- data/Changes.md +151 -0
- data/Ent-Changes.md +77 -2
- data/Gemfile +10 -25
- data/LICENSE +1 -1
- data/Pro-4.0-Upgrade.md +35 -0
- data/Pro-Changes.md +156 -2
- data/README.md +9 -6
- data/Rakefile +1 -2
- data/bin/sidekiqctl +1 -1
- data/bin/sidekiqload +15 -33
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
- data/lib/sidekiq/api.rb +157 -67
- data/lib/sidekiq/cli.rb +71 -26
- data/lib/sidekiq/client.rb +25 -18
- data/lib/sidekiq/core_ext.rb +1 -106
- data/lib/sidekiq/delay.rb +42 -0
- data/lib/sidekiq/exception_handler.rb +2 -4
- data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
- data/lib/sidekiq/fetch.rb +1 -1
- data/lib/sidekiq/job_logger.rb +25 -0
- data/lib/sidekiq/job_retry.rb +241 -0
- data/lib/sidekiq/launcher.rb +45 -37
- data/lib/sidekiq/logging.rb +18 -2
- data/lib/sidekiq/manager.rb +3 -4
- data/lib/sidekiq/middleware/server/active_record.rb +10 -0
- data/lib/sidekiq/processor.rb +91 -34
- data/lib/sidekiq/rails.rb +15 -51
- data/lib/sidekiq/redis_connection.rb +31 -5
- data/lib/sidekiq/scheduled.rb +35 -8
- data/lib/sidekiq/testing.rb +24 -7
- data/lib/sidekiq/util.rb +6 -2
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +2 -6
- data/lib/sidekiq/web/application.rb +28 -21
- data/lib/sidekiq/web/helpers.rb +67 -23
- data/lib/sidekiq/web/router.rb +14 -10
- data/lib/sidekiq/web.rb +4 -4
- data/lib/sidekiq/worker.rb +97 -14
- data/lib/sidekiq.rb +23 -24
- data/sidekiq.gemspec +7 -10
- data/web/assets/javascripts/application.js +0 -0
- data/web/assets/javascripts/dashboard.js +18 -13
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +336 -4
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +80 -0
- data/web/locales/en.yml +1 -0
- data/web/locales/es.yml +4 -3
- data/web/locales/fa.yml +80 -0
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +5 -3
- data/web/locales/ur.yml +80 -0
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +1 -1
- data/web/views/_nav.erb +1 -1
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +9 -5
- data/web/views/dashboard.erb +3 -3
- data/web/views/layout.erb +11 -2
- data/web/views/morgue.erb +14 -10
- data/web/views/queue.erb +10 -10
- data/web/views/queues.erb +4 -2
- data/web/views/retries.erb +13 -11
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +2 -2
- metadata +26 -160
- 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 -235
- 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 -58
- 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 -726
- data/test/test_web_helpers.rb +0 -54
data/bin/sidekiqload
CHANGED
@@ -12,22 +12,9 @@ require_relative '../lib/sidekiq/launcher'
|
|
12
12
|
|
13
13
|
include Sidekiq::Util
|
14
14
|
|
15
|
-
# brew tap shopify/shopify
|
16
|
-
# brew install toxiproxy
|
17
|
-
# gem install toxiproxy
|
18
|
-
require 'toxiproxy'
|
19
|
-
# simulate a non-localhost network for realer-world conditions.
|
20
|
-
# adding 1ms of network latency has an ENORMOUS impact on benchmarks
|
21
|
-
Toxiproxy.populate([{
|
22
|
-
"name": "redis",
|
23
|
-
"listen": "127.0.0.1:6380",
|
24
|
-
"upstream": "127.0.0.1:6379"
|
25
|
-
}])
|
26
|
-
|
27
|
-
|
28
15
|
Sidekiq.configure_server do |config|
|
29
16
|
#config.options[:concurrency] = 1
|
30
|
-
config.redis = {
|
17
|
+
config.redis = { db: 13 }
|
31
18
|
config.options[:queues] << 'default'
|
32
19
|
config.logger.level = Logger::ERROR
|
33
20
|
config.average_scheduled_poll_interval = 2
|
@@ -49,17 +36,17 @@ end
|
|
49
36
|
# brew tap shopify/shopify
|
50
37
|
# brew install toxiproxy
|
51
38
|
# gem install toxiproxy
|
52
|
-
require 'toxiproxy'
|
39
|
+
#require 'toxiproxy'
|
53
40
|
# simulate a non-localhost network for realer-world conditions.
|
54
41
|
# adding 1ms of network latency has an ENORMOUS impact on benchmarks
|
55
|
-
Toxiproxy.populate([{
|
56
|
-
"name": "redis",
|
57
|
-
"listen": "127.0.0.1:6380",
|
58
|
-
"upstream": "127.0.0.1:6379"
|
59
|
-
}])
|
42
|
+
#Toxiproxy.populate([{
|
43
|
+
#"name": "redis",
|
44
|
+
#"listen": "127.0.0.1:6380",
|
45
|
+
#"upstream": "127.0.0.1:6379"
|
46
|
+
#}])
|
60
47
|
|
61
48
|
self_read, self_write = IO.pipe
|
62
|
-
%w(INT TERM
|
49
|
+
%w(INT TERM TSTP TTIN).each do |sig|
|
63
50
|
begin
|
64
51
|
trap sig do
|
65
52
|
self_write.puts(sig)
|
@@ -80,17 +67,12 @@ def handle_signal(launcher, sig)
|
|
80
67
|
when 'TERM'
|
81
68
|
# Heroku sends TERM and then waits 10 seconds for process to exit.
|
82
69
|
raise Interrupt
|
83
|
-
when '
|
84
|
-
Sidekiq.logger.info "Received
|
70
|
+
when 'TSTP'
|
71
|
+
Sidekiq.logger.info "Received TSTP, no longer accepting new work"
|
85
72
|
launcher.quiet
|
86
|
-
when 'USR2'
|
87
|
-
if Sidekiq.options[:logfile]
|
88
|
-
Sidekiq.logger.info "Received USR2, reopening log file"
|
89
|
-
Sidekiq::Logging.reopen_logs
|
90
|
-
end
|
91
73
|
when 'TTIN'
|
92
74
|
Thread.list.each do |thread|
|
93
|
-
Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
75
|
+
Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['label']}"
|
94
76
|
if thread.backtrace
|
95
77
|
Sidekiq.logger.warn thread.backtrace.join("\n")
|
96
78
|
else
|
@@ -121,7 +103,7 @@ Sidekiq.logger.error "Created #{count*iter} jobs"
|
|
121
103
|
Monitoring = Thread.new do
|
122
104
|
watchdog("monitor thread") do
|
123
105
|
while true
|
124
|
-
sleep
|
106
|
+
sleep 1
|
125
107
|
qsize, retries = Sidekiq.redis do |conn|
|
126
108
|
conn.pipelined do
|
127
109
|
conn.llen "queue:default"
|
@@ -143,8 +125,8 @@ begin
|
|
143
125
|
#RubyProf::exclude_threads = [ Monitoring ]
|
144
126
|
#RubyProf.start
|
145
127
|
fire_event(:startup)
|
146
|
-
Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
|
147
|
-
Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
|
128
|
+
#Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
|
129
|
+
#Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
|
148
130
|
launcher = Sidekiq::Launcher.new(Sidekiq.options)
|
149
131
|
launcher.run
|
150
132
|
|
@@ -152,7 +134,7 @@ begin
|
|
152
134
|
signal = readable_io.first[0].gets.strip
|
153
135
|
handle_signal(launcher, signal)
|
154
136
|
end
|
155
|
-
end
|
137
|
+
#end
|
156
138
|
rescue SystemExit => e
|
157
139
|
#Sidekiq.logger.error("Profiling...")
|
158
140
|
#result = RubyProf.stop
|
data/lib/sidekiq/api.rb
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# encoding: utf-8
|
3
2
|
require 'sidekiq'
|
4
3
|
|
5
4
|
module Sidekiq
|
5
|
+
|
6
|
+
module RedisScanner
|
7
|
+
def sscan(conn, key)
|
8
|
+
cursor = '0'
|
9
|
+
result = []
|
10
|
+
loop do
|
11
|
+
cursor, values = conn.sscan(key, cursor)
|
12
|
+
result.push(*values)
|
13
|
+
break if cursor == '0'
|
14
|
+
end
|
15
|
+
result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
6
19
|
class Stats
|
20
|
+
include RedisScanner
|
21
|
+
|
7
22
|
def initialize
|
8
23
|
fetch_stats!
|
9
24
|
end
|
@@ -51,31 +66,40 @@ module Sidekiq
|
|
51
66
|
def fetch_stats!
|
52
67
|
pipe1_res = Sidekiq.redis do |conn|
|
53
68
|
conn.pipelined do
|
54
|
-
conn.get('stat:processed'
|
55
|
-
conn.get('stat:failed'
|
56
|
-
conn.zcard('schedule'
|
57
|
-
conn.zcard('retry'
|
58
|
-
conn.zcard('dead'
|
59
|
-
conn.scard('processes'
|
60
|
-
conn.lrange('queue:default'
|
61
|
-
conn.smembers('processes'.freeze)
|
62
|
-
conn.smembers('queues'.freeze)
|
69
|
+
conn.get('stat:processed')
|
70
|
+
conn.get('stat:failed')
|
71
|
+
conn.zcard('schedule')
|
72
|
+
conn.zcard('retry')
|
73
|
+
conn.zcard('dead')
|
74
|
+
conn.scard('processes')
|
75
|
+
conn.lrange('queue:default', -1, -1)
|
63
76
|
end
|
64
77
|
end
|
65
78
|
|
79
|
+
processes = Sidekiq.redis do |conn|
|
80
|
+
sscan(conn, 'processes')
|
81
|
+
end
|
82
|
+
|
83
|
+
queues = Sidekiq.redis do |conn|
|
84
|
+
sscan(conn, 'queues')
|
85
|
+
end
|
86
|
+
|
66
87
|
pipe2_res = Sidekiq.redis do |conn|
|
67
88
|
conn.pipelined do
|
68
|
-
|
69
|
-
|
89
|
+
processes.each {|key| conn.hget(key, 'busy') }
|
90
|
+
queues.each {|queue| conn.llen("queue:#{queue}") }
|
70
91
|
end
|
71
92
|
end
|
72
93
|
|
73
|
-
s =
|
94
|
+
s = processes.size
|
74
95
|
workers_size = pipe2_res[0...s].map(&:to_i).inject(0, &:+)
|
75
96
|
enqueued = pipe2_res[s..-1].map(&:to_i).inject(0, &:+)
|
76
97
|
|
77
98
|
default_queue_latency = if (entry = pipe1_res[6].first)
|
78
|
-
|
99
|
+
job = Sidekiq.load_json(entry) rescue {}
|
100
|
+
now = Time.now.to_f
|
101
|
+
thence = job['enqueued_at'] || now
|
102
|
+
now - thence
|
79
103
|
else
|
80
104
|
0
|
81
105
|
end
|
@@ -114,9 +138,11 @@ module Sidekiq
|
|
114
138
|
end
|
115
139
|
|
116
140
|
class Queues
|
141
|
+
include RedisScanner
|
142
|
+
|
117
143
|
def lengths
|
118
144
|
Sidekiq.redis do |conn|
|
119
|
-
queues = conn
|
145
|
+
queues = sscan(conn, 'queues')
|
120
146
|
|
121
147
|
lengths = conn.pipelined do
|
122
148
|
queues.each do |queue|
|
@@ -143,11 +169,11 @@ module Sidekiq
|
|
143
169
|
end
|
144
170
|
|
145
171
|
def processed
|
146
|
-
date_stat_hash("processed")
|
172
|
+
@processed ||= date_stat_hash("processed")
|
147
173
|
end
|
148
174
|
|
149
175
|
def failed
|
150
|
-
date_stat_hash("failed")
|
176
|
+
@failed ||= date_stat_hash("failed")
|
151
177
|
end
|
152
178
|
|
153
179
|
private
|
@@ -160,16 +186,21 @@ module Sidekiq
|
|
160
186
|
|
161
187
|
while i < @days_previous
|
162
188
|
date = @start_date - i
|
163
|
-
datestr = date.strftime("%Y-%m-%d"
|
189
|
+
datestr = date.strftime("%Y-%m-%d")
|
164
190
|
keys << "stat:#{stat}:#{datestr}"
|
165
191
|
dates << datestr
|
166
192
|
i += 1
|
167
193
|
end
|
168
194
|
|
169
|
-
|
170
|
-
|
171
|
-
|
195
|
+
begin
|
196
|
+
Sidekiq.redis do |conn|
|
197
|
+
conn.mget(keys).each_with_index do |value, idx|
|
198
|
+
stat_hash[dates[idx]] = value ? value.to_i : 0
|
199
|
+
end
|
172
200
|
end
|
201
|
+
rescue Redis::CommandError
|
202
|
+
# mget will trigger a CROSSSLOT error when run against a Cluster
|
203
|
+
# TODO Someone want to add Cluster support?
|
173
204
|
end
|
174
205
|
|
175
206
|
stat_hash
|
@@ -191,18 +222,19 @@ module Sidekiq
|
|
191
222
|
#
|
192
223
|
class Queue
|
193
224
|
include Enumerable
|
225
|
+
extend RedisScanner
|
194
226
|
|
195
227
|
##
|
196
228
|
# Return all known queues within Redis.
|
197
229
|
#
|
198
230
|
def self.all
|
199
|
-
Sidekiq.redis { |c| c
|
231
|
+
Sidekiq.redis { |c| sscan(c, 'queues') }.sort.map { |q| Sidekiq::Queue.new(q) }
|
200
232
|
end
|
201
233
|
|
202
234
|
attr_reader :name
|
203
235
|
|
204
236
|
def initialize(name="default")
|
205
|
-
@name = name
|
237
|
+
@name = name.to_s
|
206
238
|
@rname = "queue:#{name}"
|
207
239
|
end
|
208
240
|
|
@@ -225,7 +257,10 @@ module Sidekiq
|
|
225
257
|
conn.lrange(@rname, -1, -1)
|
226
258
|
end.first
|
227
259
|
return 0 unless entry
|
228
|
-
|
260
|
+
job = Sidekiq.load_json(entry)
|
261
|
+
now = Time.now.to_f
|
262
|
+
thence = job['enqueued_at'] || now
|
263
|
+
now - thence
|
229
264
|
end
|
230
265
|
|
231
266
|
def each
|
@@ -262,7 +297,7 @@ module Sidekiq
|
|
262
297
|
Sidekiq.redis do |conn|
|
263
298
|
conn.multi do
|
264
299
|
conn.del(@rname)
|
265
|
-
conn.srem("queues"
|
300
|
+
conn.srem("queues", name)
|
266
301
|
end
|
267
302
|
end
|
268
303
|
end
|
@@ -281,13 +316,25 @@ module Sidekiq
|
|
281
316
|
attr_reader :value
|
282
317
|
|
283
318
|
def initialize(item, queue_name=nil)
|
319
|
+
@args = nil
|
284
320
|
@value = item
|
285
|
-
@item = item.is_a?(Hash) ? item :
|
321
|
+
@item = item.is_a?(Hash) ? item : parse(item)
|
286
322
|
@queue = queue_name || @item['queue']
|
287
323
|
end
|
288
324
|
|
325
|
+
def parse(item)
|
326
|
+
Sidekiq.load_json(item)
|
327
|
+
rescue JSON::ParserError
|
328
|
+
# If the job payload in Redis is invalid JSON, we'll load
|
329
|
+
# the item as an empty hash and store the invalid JSON as
|
330
|
+
# the job 'args' for display in the Web UI.
|
331
|
+
@invalid = true
|
332
|
+
@args = [item]
|
333
|
+
{}
|
334
|
+
end
|
335
|
+
|
289
336
|
def klass
|
290
|
-
|
337
|
+
self['class']
|
291
338
|
end
|
292
339
|
|
293
340
|
def display_class
|
@@ -312,38 +359,42 @@ module Sidekiq
|
|
312
359
|
|
313
360
|
def display_args
|
314
361
|
# Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
|
315
|
-
@
|
362
|
+
@display_args ||= case klass
|
316
363
|
when /\ASidekiq::Extensions::Delayed/
|
317
364
|
safe_load(args[0], args) do |_, _, arg|
|
318
365
|
arg
|
319
366
|
end
|
320
367
|
when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
321
|
-
job_args =
|
322
|
-
if 'ActionMailer::DeliveryJob' == (
|
323
|
-
|
324
|
-
|
368
|
+
job_args = self['wrapped'] ? args[0]["arguments"] : []
|
369
|
+
if 'ActionMailer::DeliveryJob' == (self['wrapped'] || args[0])
|
370
|
+
# remove MailerClass, mailer_method and 'deliver_now'
|
371
|
+
job_args.drop(3)
|
325
372
|
else
|
326
|
-
|
373
|
+
job_args
|
327
374
|
end
|
328
375
|
else
|
376
|
+
if self['encrypt']
|
377
|
+
# no point in showing 150+ bytes of random garbage
|
378
|
+
args[-1] = '[encrypted data]'
|
379
|
+
end
|
329
380
|
args
|
330
381
|
end
|
331
382
|
end
|
332
383
|
|
333
384
|
def args
|
334
|
-
@item['args']
|
385
|
+
@args || @item['args']
|
335
386
|
end
|
336
387
|
|
337
388
|
def jid
|
338
|
-
|
389
|
+
self['jid']
|
339
390
|
end
|
340
391
|
|
341
392
|
def enqueued_at
|
342
|
-
|
393
|
+
self['enqueued_at'] ? Time.at(self['enqueued_at']).utc : nil
|
343
394
|
end
|
344
395
|
|
345
396
|
def created_at
|
346
|
-
Time.at(
|
397
|
+
Time.at(self['created_at'] || self['enqueued_at'] || 0).utc
|
347
398
|
end
|
348
399
|
|
349
400
|
def queue
|
@@ -351,7 +402,8 @@ module Sidekiq
|
|
351
402
|
end
|
352
403
|
|
353
404
|
def latency
|
354
|
-
Time.now.to_f
|
405
|
+
now = Time.now.to_f
|
406
|
+
now - (@item['enqueued_at'] || @item['created_at'] || now)
|
355
407
|
end
|
356
408
|
|
357
409
|
##
|
@@ -364,7 +416,10 @@ module Sidekiq
|
|
364
416
|
end
|
365
417
|
|
366
418
|
def [](name)
|
367
|
-
|
419
|
+
# nil will happen if the JSON fails to parse.
|
420
|
+
# We don't guarantee Sidekiq will work with bad job JSON but we should
|
421
|
+
# make a best effort to minimize the damage.
|
422
|
+
@item ? @item[name] : nil
|
368
423
|
end
|
369
424
|
|
370
425
|
private
|
@@ -416,10 +471,9 @@ module Sidekiq
|
|
416
471
|
end
|
417
472
|
|
418
473
|
def retry
|
419
|
-
raise "Retry not available on jobs which have not failed" unless item["failed_at"]
|
420
474
|
remove_job do |message|
|
421
475
|
msg = Sidekiq.load_json(message)
|
422
|
-
msg['retry_count'] -= 1
|
476
|
+
msg['retry_count'] -= 1 if msg['retry_count']
|
423
477
|
Sidekiq::Client.push(msg)
|
424
478
|
end
|
425
479
|
end
|
@@ -427,20 +481,15 @@ module Sidekiq
|
|
427
481
|
##
|
428
482
|
# Place job in the dead set
|
429
483
|
def kill
|
430
|
-
raise 'Kill not available on jobs which have not failed' unless item['failed_at']
|
431
484
|
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
|
485
|
+
DeadSet.new.kill(message)
|
441
486
|
end
|
442
487
|
end
|
443
488
|
|
489
|
+
def error?
|
490
|
+
!!item['error_class']
|
491
|
+
end
|
492
|
+
|
444
493
|
private
|
445
494
|
|
446
495
|
def remove_job
|
@@ -523,7 +572,7 @@ module Sidekiq
|
|
523
572
|
end
|
524
573
|
break if elements.empty?
|
525
574
|
page -= 1
|
526
|
-
elements.each do |element, score|
|
575
|
+
elements.reverse.each do |element, score|
|
527
576
|
yield SortedEntry.new(self, score, element)
|
528
577
|
end
|
529
578
|
offset_size = initial_size - @_size
|
@@ -585,13 +634,13 @@ module Sidekiq
|
|
585
634
|
# Allows enumeration of scheduled jobs within Sidekiq.
|
586
635
|
# Based on this, you can search/filter for jobs. Here's an
|
587
636
|
# example where I'm selecting all jobs of a certain type
|
588
|
-
# and deleting them from the
|
637
|
+
# and deleting them from the schedule queue.
|
589
638
|
#
|
590
639
|
# r = Sidekiq::ScheduledSet.new
|
591
|
-
# r.select do |
|
592
|
-
#
|
593
|
-
#
|
594
|
-
#
|
640
|
+
# r.select do |scheduled|
|
641
|
+
# scheduled.klass == 'Sidekiq::Extensions::DelayedClass' &&
|
642
|
+
# scheduled.args[0] == 'User' &&
|
643
|
+
# scheduled.args[1] == 'setup_new_subscriber'
|
595
644
|
# end.map(&:delete)
|
596
645
|
class ScheduledSet < JobSet
|
597
646
|
def initialize
|
@@ -631,6 +680,27 @@ module Sidekiq
|
|
631
680
|
super 'dead'
|
632
681
|
end
|
633
682
|
|
683
|
+
def kill(message, opts={})
|
684
|
+
now = Time.now.to_f
|
685
|
+
Sidekiq.redis do |conn|
|
686
|
+
conn.multi do
|
687
|
+
conn.zadd(name, now.to_s, message)
|
688
|
+
conn.zremrangebyscore(name, '-inf', now - self.class.timeout)
|
689
|
+
conn.zremrangebyrank(name, 0, - self.class.max_jobs)
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
if opts[:notify_failure] != false
|
694
|
+
job = Sidekiq.load_json(message)
|
695
|
+
r = RuntimeError.new("Job killed by API")
|
696
|
+
r.set_backtrace(caller)
|
697
|
+
Sidekiq.death_handlers.each do |handle|
|
698
|
+
handle.call(job, r)
|
699
|
+
end
|
700
|
+
end
|
701
|
+
true
|
702
|
+
end
|
703
|
+
|
634
704
|
def retry_all
|
635
705
|
while size > 0
|
636
706
|
each(&:retry)
|
@@ -655,17 +725,18 @@ module Sidekiq
|
|
655
725
|
#
|
656
726
|
class ProcessSet
|
657
727
|
include Enumerable
|
728
|
+
include RedisScanner
|
658
729
|
|
659
730
|
def initialize(clean_plz=true)
|
660
|
-
|
731
|
+
cleanup if clean_plz
|
661
732
|
end
|
662
733
|
|
663
734
|
# Cleans up dead processes recorded in Redis.
|
664
735
|
# Returns the number of processes cleaned.
|
665
|
-
def
|
736
|
+
def cleanup
|
666
737
|
count = 0
|
667
738
|
Sidekiq.redis do |conn|
|
668
|
-
procs = conn
|
739
|
+
procs = sscan(conn, 'processes').sort
|
669
740
|
heartbeats = conn.pipelined do
|
670
741
|
procs.each do |key|
|
671
742
|
conn.hget(key, 'info')
|
@@ -685,7 +756,7 @@ module Sidekiq
|
|
685
756
|
end
|
686
757
|
|
687
758
|
def each
|
688
|
-
procs = Sidekiq.redis { |conn| conn
|
759
|
+
procs = Sidekiq.redis { |conn| sscan(conn, 'processes') }.sort
|
689
760
|
|
690
761
|
Sidekiq.redis do |conn|
|
691
762
|
# We're making a tradeoff here between consuming more memory instead of
|
@@ -698,6 +769,11 @@ module Sidekiq
|
|
698
769
|
end
|
699
770
|
|
700
771
|
result.each do |info, busy, at_s, quiet|
|
772
|
+
# If a process is stopped between when we query Redis for `procs` and
|
773
|
+
# when we query for `result`, we will have an item in `result` that is
|
774
|
+
# composed of `nil` values.
|
775
|
+
next if info.nil?
|
776
|
+
|
701
777
|
hash = Sidekiq.load_json(info)
|
702
778
|
yield Process.new(hash.merge('busy' => busy.to_i, 'beat' => at_s.to_f, 'quiet' => quiet))
|
703
779
|
end
|
@@ -713,6 +789,18 @@ module Sidekiq
|
|
713
789
|
def size
|
714
790
|
Sidekiq.redis { |conn| conn.scard('processes') }
|
715
791
|
end
|
792
|
+
|
793
|
+
# Returns the identity of the current cluster leader or "" if no leader.
|
794
|
+
# This is a Sidekiq Enterprise feature, will always return "" in Sidekiq
|
795
|
+
# or Sidekiq Pro.
|
796
|
+
def leader
|
797
|
+
@leader ||= begin
|
798
|
+
x = Sidekiq.redis {|c| c.get("dear-leader") }
|
799
|
+
# need a non-falsy value so we can memoize
|
800
|
+
x = "" unless x
|
801
|
+
x
|
802
|
+
end
|
803
|
+
end
|
716
804
|
end
|
717
805
|
|
718
806
|
#
|
@@ -747,8 +835,12 @@ module Sidekiq
|
|
747
835
|
@attribs[key]
|
748
836
|
end
|
749
837
|
|
838
|
+
def identity
|
839
|
+
self['identity']
|
840
|
+
end
|
841
|
+
|
750
842
|
def quiet!
|
751
|
-
signal('
|
843
|
+
signal('TSTP')
|
752
844
|
end
|
753
845
|
|
754
846
|
def stop!
|
@@ -775,9 +867,6 @@ module Sidekiq
|
|
775
867
|
end
|
776
868
|
end
|
777
869
|
|
778
|
-
def identity
|
779
|
-
self['identity']
|
780
|
-
end
|
781
870
|
end
|
782
871
|
|
783
872
|
##
|
@@ -802,10 +891,11 @@ module Sidekiq
|
|
802
891
|
#
|
803
892
|
class Workers
|
804
893
|
include Enumerable
|
894
|
+
include RedisScanner
|
805
895
|
|
806
896
|
def each
|
807
897
|
Sidekiq.redis do |conn|
|
808
|
-
procs = conn
|
898
|
+
procs = sscan(conn, 'processes')
|
809
899
|
procs.sort.each do |key|
|
810
900
|
valid, workers = conn.pipelined do
|
811
901
|
conn.exists(key)
|
@@ -827,7 +917,7 @@ module Sidekiq
|
|
827
917
|
# which can easily get out of sync with crashy processes.
|
828
918
|
def size
|
829
919
|
Sidekiq.redis do |conn|
|
830
|
-
procs = conn
|
920
|
+
procs = sscan(conn, 'processes')
|
831
921
|
if procs.empty?
|
832
922
|
0
|
833
923
|
else
|