sidekiq 7.3.10 → 8.0.10
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 +4 -4
- data/Changes.md +135 -4
- data/README.md +16 -13
- data/bin/sidekiqload +10 -10
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +104 -58
- data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
- data/lib/sidekiq/api.rb +152 -44
- data/lib/sidekiq/capsule.rb +10 -6
- data/lib/sidekiq/cli.rb +30 -23
- data/lib/sidekiq/client.rb +28 -17
- data/lib/sidekiq/component.rb +42 -3
- data/lib/sidekiq/config.rb +32 -22
- data/lib/sidekiq/embedded.rb +2 -1
- data/lib/sidekiq/iterable_job.rb +1 -0
- data/lib/sidekiq/job/iterable.rb +46 -18
- data/lib/sidekiq/job.rb +2 -2
- data/lib/sidekiq/job_logger.rb +8 -6
- data/lib/sidekiq/job_retry.rb +33 -10
- data/lib/sidekiq/job_util.rb +5 -1
- data/lib/sidekiq/launcher.rb +20 -10
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +25 -69
- data/lib/sidekiq/manager.rb +0 -1
- data/lib/sidekiq/metrics/query.rb +71 -45
- data/lib/sidekiq/metrics/shared.rb +8 -5
- data/lib/sidekiq/metrics/tracking.rb +12 -7
- data/lib/sidekiq/middleware/current_attributes.rb +11 -19
- data/lib/sidekiq/monitor.rb +4 -8
- data/lib/sidekiq/paginator.rb +8 -1
- data/lib/sidekiq/processor.rb +21 -14
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +46 -67
- data/lib/sidekiq/redis_client_adapter.rb +0 -1
- data/lib/sidekiq/redis_connection.rb +2 -2
- data/lib/sidekiq/scheduled.rb +4 -4
- data/lib/sidekiq/testing.rb +3 -3
- data/lib/sidekiq/version.rb +2 -2
- data/lib/sidekiq/web/action.rb +146 -83
- data/lib/sidekiq/web/application.rb +361 -330
- data/lib/sidekiq/web/config.rb +120 -0
- data/lib/sidekiq/web/helpers.rb +58 -36
- data/lib/sidekiq/web/router.rb +60 -76
- data/lib/sidekiq/web.rb +51 -156
- data/lib/sidekiq.rb +6 -1
- data/sidekiq.gemspec +6 -6
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +42 -26
- data/web/assets/javascripts/base-charts.js +30 -16
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +1 -1
- data/web/assets/javascripts/metrics.js +16 -34
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +2 -0
- data/web/locales/cs.yml +2 -0
- data/web/locales/da.yml +2 -0
- data/web/locales/de.yml +2 -0
- data/web/locales/el.yml +2 -0
- data/web/locales/en.yml +7 -0
- data/web/locales/es.yml +25 -2
- data/web/locales/fa.yml +2 -0
- data/web/locales/fr.yml +3 -1
- data/web/locales/gd.yml +2 -0
- data/web/locales/he.yml +2 -0
- data/web/locales/hi.yml +2 -0
- data/web/locales/it.yml +9 -0
- data/web/locales/ja.yml +2 -0
- data/web/locales/ko.yml +2 -0
- data/web/locales/lt.yml +2 -0
- data/web/locales/nb.yml +2 -0
- data/web/locales/nl.yml +2 -0
- data/web/locales/pl.yml +2 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +3 -1
- data/web/locales/pt.yml +2 -0
- data/web/locales/ru.yml +2 -0
- data/web/locales/sv.yml +2 -0
- data/web/locales/ta.yml +2 -0
- data/web/locales/tr.yml +2 -0
- data/web/locales/uk.yml +7 -5
- data/web/locales/ur.yml +2 -0
- data/web/locales/vi.yml +2 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +86 -73
- data/web/locales/{zh-tw.yml → zh-TW.yml} +3 -1
- data/web/views/_footer.erb +31 -33
- data/web/views/_job_info.erb +91 -89
- data/web/views/_metrics_period_select.erb +13 -10
- data/web/views/_nav.erb +14 -21
- data/web/views/_paging.erb +22 -21
- data/web/views/_poll_link.erb +2 -2
- data/web/views/_summary.erb +16 -16
- data/web/views/busy.erb +123 -125
- data/web/views/dashboard.erb +62 -66
- data/web/views/dead.erb +31 -27
- data/web/views/filtering.erb +4 -4
- data/web/views/layout.erb +12 -28
- data/web/views/metrics.erb +76 -82
- data/web/views/metrics_for_job.erb +45 -46
- data/web/views/morgue.erb +65 -70
- data/web/views/profiles.erb +43 -0
- data/web/views/queue.erb +54 -52
- data/web/views/queues.erb +43 -41
- data/web/views/retries.erb +70 -75
- data/web/views/retry.erb +32 -27
- data/web/views/scheduled.erb +63 -55
- data/web/views/scheduled_job_info.erb +3 -3
- metadata +24 -41
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -163
- data/web/assets/stylesheets/application.css +0 -759
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/views/_status.erb +0 -4
data/lib/sidekiq/api.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sidekiq"
|
|
4
|
-
|
|
5
3
|
require "zlib"
|
|
6
|
-
require "set"
|
|
7
4
|
|
|
5
|
+
require "sidekiq"
|
|
8
6
|
require "sidekiq/metrics/query"
|
|
9
7
|
|
|
10
8
|
#
|
|
@@ -101,11 +99,22 @@ module Sidekiq
|
|
|
101
99
|
rescue
|
|
102
100
|
{}
|
|
103
101
|
end
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
|
|
103
|
+
enqueued_at = job["enqueued_at"]
|
|
104
|
+
if enqueued_at
|
|
105
|
+
if enqueued_at.is_a?(Float)
|
|
106
|
+
# old format
|
|
107
|
+
now = Time.now.to_f
|
|
108
|
+
now - enqueued_at
|
|
109
|
+
else
|
|
110
|
+
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
111
|
+
(now - enqueued_at) / 1000.0
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
0.0
|
|
115
|
+
end
|
|
107
116
|
else
|
|
108
|
-
0
|
|
117
|
+
0.0
|
|
109
118
|
end
|
|
110
119
|
|
|
111
120
|
@stats = {
|
|
@@ -265,11 +274,22 @@ module Sidekiq
|
|
|
265
274
|
entry = Sidekiq.redis { |conn|
|
|
266
275
|
conn.lindex(@rname, -1)
|
|
267
276
|
}
|
|
268
|
-
return 0 unless entry
|
|
277
|
+
return 0.0 unless entry
|
|
278
|
+
|
|
269
279
|
job = Sidekiq.load_json(entry)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
280
|
+
enqueued_at = job["enqueued_at"]
|
|
281
|
+
if enqueued_at
|
|
282
|
+
if enqueued_at.is_a?(Float)
|
|
283
|
+
# old format
|
|
284
|
+
now = Time.now.to_f
|
|
285
|
+
now - enqueued_at
|
|
286
|
+
else
|
|
287
|
+
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
288
|
+
(now - enqueued_at) / 1000.0
|
|
289
|
+
end
|
|
290
|
+
else
|
|
291
|
+
0.0
|
|
292
|
+
end
|
|
273
293
|
end
|
|
274
294
|
|
|
275
295
|
def each
|
|
@@ -421,12 +441,26 @@ module Sidekiq
|
|
|
421
441
|
self["bid"]
|
|
422
442
|
end
|
|
423
443
|
|
|
444
|
+
def failed_at
|
|
445
|
+
if self["failed_at"]
|
|
446
|
+
time_from_timestamp(self["failed_at"])
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def retried_at
|
|
451
|
+
if self["retried_at"]
|
|
452
|
+
time_from_timestamp(self["retried_at"])
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
424
456
|
def enqueued_at
|
|
425
|
-
|
|
457
|
+
if self["enqueued_at"]
|
|
458
|
+
time_from_timestamp(self["enqueued_at"])
|
|
459
|
+
end
|
|
426
460
|
end
|
|
427
461
|
|
|
428
462
|
def created_at
|
|
429
|
-
|
|
463
|
+
time_from_timestamp(self["created_at"] || self["enqueued_at"] || 0)
|
|
430
464
|
end
|
|
431
465
|
|
|
432
466
|
def tags
|
|
@@ -444,8 +478,17 @@ module Sidekiq
|
|
|
444
478
|
end
|
|
445
479
|
|
|
446
480
|
def latency
|
|
447
|
-
|
|
448
|
-
|
|
481
|
+
timestamp = @item["enqueued_at"] || @item["created_at"]
|
|
482
|
+
if timestamp
|
|
483
|
+
if timestamp.is_a?(Float)
|
|
484
|
+
# old format
|
|
485
|
+
Time.now.to_f - timestamp
|
|
486
|
+
else
|
|
487
|
+
(::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) - timestamp) / 1000.0
|
|
488
|
+
end
|
|
489
|
+
else
|
|
490
|
+
0.0
|
|
491
|
+
end
|
|
449
492
|
end
|
|
450
493
|
|
|
451
494
|
# Remove this job from the queue
|
|
@@ -494,6 +537,15 @@ module Sidekiq
|
|
|
494
537
|
uncompressed = Zlib::Inflate.inflate(strict_base64_decoded)
|
|
495
538
|
Sidekiq.load_json(uncompressed)
|
|
496
539
|
end
|
|
540
|
+
|
|
541
|
+
def time_from_timestamp(timestamp)
|
|
542
|
+
if timestamp.is_a?(Float)
|
|
543
|
+
# old format, timestamps were stored as fractional seconds since the epoch
|
|
544
|
+
Time.at(timestamp).utc
|
|
545
|
+
else
|
|
546
|
+
Time.at(timestamp / 1000, timestamp % 1000, :millisecond)
|
|
547
|
+
end
|
|
548
|
+
end
|
|
497
549
|
end
|
|
498
550
|
|
|
499
551
|
# Represents a job within a Redis sorted set where the score
|
|
@@ -1007,9 +1059,9 @@ module Sidekiq
|
|
|
1007
1059
|
# 'started_at' => <process start time>,
|
|
1008
1060
|
# 'pid' => 12345,
|
|
1009
1061
|
# 'tag' => 'myapp'
|
|
1010
|
-
# 'concurrency' =>
|
|
1011
|
-
# '
|
|
1012
|
-
# 'busy' =>
|
|
1062
|
+
# 'concurrency' => 5,
|
|
1063
|
+
# 'capsules' => {"default" => {"mode" => "weighted", "concurrency" => 5, "weights" => {"default" => 2, "low" => 1}}},
|
|
1064
|
+
# 'busy' => 3,
|
|
1013
1065
|
# 'beat' => <last heartbeat>,
|
|
1014
1066
|
# 'identity' => <unique string identifying the process>,
|
|
1015
1067
|
# 'embedded' => true,
|
|
@@ -1037,12 +1089,35 @@ module Sidekiq
|
|
|
1037
1089
|
self["identity"]
|
|
1038
1090
|
end
|
|
1039
1091
|
|
|
1092
|
+
# deprecated, use capsules below
|
|
1040
1093
|
def queues
|
|
1041
|
-
|
|
1094
|
+
# Backwards compatibility with <8.0.8
|
|
1095
|
+
if !self["capsules"]
|
|
1096
|
+
self["queues"]
|
|
1097
|
+
else
|
|
1098
|
+
capsules.values.flat_map { |x| x["weights"].keys }.uniq
|
|
1099
|
+
end
|
|
1042
1100
|
end
|
|
1043
1101
|
|
|
1102
|
+
# deprecated, use capsules below
|
|
1044
1103
|
def weights
|
|
1045
|
-
|
|
1104
|
+
# Backwards compatibility with <8.0.8
|
|
1105
|
+
if !self["capsules"]
|
|
1106
|
+
self["weights"]
|
|
1107
|
+
else
|
|
1108
|
+
hash = {}
|
|
1109
|
+
capsules.values.each do |cap|
|
|
1110
|
+
# Note: will lose data if two capsules are processing the same named queue
|
|
1111
|
+
cap["weights"].each_pair do |queue, weight|
|
|
1112
|
+
hash[queue] = weight
|
|
1113
|
+
end
|
|
1114
|
+
end
|
|
1115
|
+
hash
|
|
1116
|
+
end
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
def capsules
|
|
1120
|
+
self["capsules"]
|
|
1046
1121
|
end
|
|
1047
1122
|
|
|
1048
1123
|
def version
|
|
@@ -1114,9 +1189,8 @@ module Sidekiq
|
|
|
1114
1189
|
# works.each do |process_id, thread_id, work|
|
|
1115
1190
|
# # process_id is a unique identifier per Sidekiq process
|
|
1116
1191
|
# # thread_id is a unique identifier per thread
|
|
1117
|
-
# # work is a
|
|
1118
|
-
# #
|
|
1119
|
-
# # run_at is an epoch Integer.
|
|
1192
|
+
# # work is a `Sidekiq::Work` instance that has the following accessor methods.
|
|
1193
|
+
# # [work.queue, work.run_at, work.payload]
|
|
1120
1194
|
# end
|
|
1121
1195
|
#
|
|
1122
1196
|
class WorkSet
|
|
@@ -1142,7 +1216,7 @@ module Sidekiq
|
|
|
1142
1216
|
end
|
|
1143
1217
|
end
|
|
1144
1218
|
|
|
1145
|
-
results.sort_by { |(_, _,
|
|
1219
|
+
results.sort_by { |(_, _, work)| work.run_at }.each(&block)
|
|
1146
1220
|
end
|
|
1147
1221
|
|
|
1148
1222
|
# Note that #size is only as accurate as Sidekiq's heartbeat,
|
|
@@ -1172,13 +1246,14 @@ module Sidekiq
|
|
|
1172
1246
|
#
|
|
1173
1247
|
# @param jid [String] the job identifier
|
|
1174
1248
|
# @return [Sidekiq::Work] the work or nil
|
|
1175
|
-
def
|
|
1249
|
+
def find_work(jid)
|
|
1176
1250
|
each do |_process_id, _thread_id, work|
|
|
1177
1251
|
job = work.job
|
|
1178
1252
|
return work if job.jid == jid
|
|
1179
1253
|
end
|
|
1180
1254
|
nil
|
|
1181
1255
|
end
|
|
1256
|
+
alias_method :find_work_by_jid, :find_work
|
|
1182
1257
|
end
|
|
1183
1258
|
|
|
1184
1259
|
# Sidekiq::Work represents a job which is currently executing.
|
|
@@ -1208,33 +1283,66 @@ module Sidekiq
|
|
|
1208
1283
|
def payload
|
|
1209
1284
|
@hsh["payload"]
|
|
1210
1285
|
end
|
|
1286
|
+
end
|
|
1211
1287
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
warn("Direct access to `Sidekiq::Work` attributes is deprecated, please use `#payload`, `#queue`, `#run_at` or `#job` instead", **kwargs)
|
|
1288
|
+
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
|
1289
|
+
# Is "worker" a process, a type of job, a thread? Undefined!
|
|
1290
|
+
# WorkSet better describes the data.
|
|
1291
|
+
Workers = WorkSet
|
|
1217
1292
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1293
|
+
class ProfileSet
|
|
1294
|
+
include Enumerable
|
|
1220
1295
|
|
|
1221
|
-
#
|
|
1222
|
-
#
|
|
1223
|
-
def
|
|
1224
|
-
@
|
|
1296
|
+
# This is a point in time/snapshot API, you'll need to instantiate a new instance
|
|
1297
|
+
# if you want to fetch newer records.
|
|
1298
|
+
def initialize
|
|
1299
|
+
@records = Sidekiq.redis do |c|
|
|
1300
|
+
# This throws away expired profiles
|
|
1301
|
+
c.zremrangebyscore("profiles", "-inf", Time.now.to_f.to_s)
|
|
1302
|
+
# retreive records, newest to oldest
|
|
1303
|
+
c.zrange("profiles", "+inf", 0, "byscore", "rev")
|
|
1304
|
+
end
|
|
1225
1305
|
end
|
|
1226
1306
|
|
|
1227
|
-
def
|
|
1228
|
-
@
|
|
1307
|
+
def size
|
|
1308
|
+
@records.size
|
|
1229
1309
|
end
|
|
1230
1310
|
|
|
1231
|
-
def
|
|
1232
|
-
|
|
1311
|
+
def each(&block)
|
|
1312
|
+
fetch_keys = %w[started_at jid type token size elapsed].freeze
|
|
1313
|
+
arrays = Sidekiq.redis do |c|
|
|
1314
|
+
c.pipelined do |p|
|
|
1315
|
+
@records.each do |key|
|
|
1316
|
+
p.hmget(key, *fetch_keys)
|
|
1317
|
+
end
|
|
1318
|
+
end
|
|
1319
|
+
end
|
|
1320
|
+
|
|
1321
|
+
arrays.compact.map { |arr| ProfileRecord.new(arr) }.each(&block)
|
|
1233
1322
|
end
|
|
1234
1323
|
end
|
|
1235
1324
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1325
|
+
class ProfileRecord
|
|
1326
|
+
attr_reader :started_at, :jid, :type, :token, :size, :elapsed
|
|
1327
|
+
|
|
1328
|
+
def initialize(arr)
|
|
1329
|
+
# Must be same order as fetch_keys above
|
|
1330
|
+
@started_at = Time.at(Integer(arr[0]))
|
|
1331
|
+
@jid = arr[1]
|
|
1332
|
+
@type = arr[2]
|
|
1333
|
+
@token = arr[3]
|
|
1334
|
+
@size = Integer(arr[4])
|
|
1335
|
+
@elapsed = Float(arr[5])
|
|
1336
|
+
end
|
|
1337
|
+
|
|
1338
|
+
def key
|
|
1339
|
+
"#{token}-#{jid}"
|
|
1340
|
+
end
|
|
1341
|
+
|
|
1342
|
+
def data
|
|
1343
|
+
Sidekiq.redis { |c| c.hget(key, "data") }
|
|
1344
|
+
end
|
|
1345
|
+
end
|
|
1240
1346
|
end
|
|
1347
|
+
|
|
1348
|
+
Sidekiq.loader.run_load_hooks(:api)
|
data/lib/sidekiq/capsule.rb
CHANGED
|
@@ -11,12 +11,12 @@ module Sidekiq
|
|
|
11
11
|
# This capsule will pull jobs from the "single" queue and process
|
|
12
12
|
# the jobs with one thread, meaning the jobs will be processed serially.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
14
|
+
# Sidekiq.configure_server do |config|
|
|
15
|
+
# config.capsule("single-threaded") do |cap|
|
|
16
|
+
# cap.concurrency = 1
|
|
17
|
+
# cap.queues = %w(single)
|
|
18
|
+
# end
|
|
18
19
|
# end
|
|
19
|
-
# end
|
|
20
20
|
class Capsule
|
|
21
21
|
include Sidekiq::Component
|
|
22
22
|
extend Forwardable
|
|
@@ -27,7 +27,7 @@ module Sidekiq
|
|
|
27
27
|
attr_reader :mode
|
|
28
28
|
attr_reader :weights
|
|
29
29
|
|
|
30
|
-
def_delegators :@config, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
|
|
30
|
+
def_delegators :@config, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig, :thread_priority
|
|
31
31
|
|
|
32
32
|
def initialize(name, config)
|
|
33
33
|
@name = name
|
|
@@ -38,6 +38,10 @@ module Sidekiq
|
|
|
38
38
|
@mode = :strict
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
def to_h
|
|
42
|
+
{concurrency: concurrency, mode: mode, weights: weights}
|
|
43
|
+
end
|
|
44
|
+
|
|
41
45
|
def fetcher
|
|
42
46
|
@fetcher ||= begin
|
|
43
47
|
instance = (config[:fetch_class] || Sidekiq::BasicFetch).new(self)
|
data/lib/sidekiq/cli.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
$stdout.sync = true
|
|
4
4
|
|
|
5
5
|
require "yaml"
|
|
6
|
-
require "singleton"
|
|
7
6
|
require "optparse"
|
|
8
7
|
require "erb"
|
|
9
8
|
require "fileutils"
|
|
@@ -17,7 +16,6 @@ require "sidekiq/launcher"
|
|
|
17
16
|
module Sidekiq # :nodoc:
|
|
18
17
|
class CLI
|
|
19
18
|
include Sidekiq::Component
|
|
20
|
-
include Singleton unless $TESTING
|
|
21
19
|
|
|
22
20
|
attr_accessor :launcher
|
|
23
21
|
attr_accessor :environment
|
|
@@ -31,6 +29,10 @@ module Sidekiq # :nodoc:
|
|
|
31
29
|
validate!
|
|
32
30
|
end
|
|
33
31
|
|
|
32
|
+
def self.instance
|
|
33
|
+
@instance ||= new
|
|
34
|
+
end
|
|
35
|
+
|
|
34
36
|
def jruby?
|
|
35
37
|
defined?(::JRUBY_VERSION)
|
|
36
38
|
end
|
|
@@ -47,7 +49,7 @@ module Sidekiq # :nodoc:
|
|
|
47
49
|
logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
|
|
48
50
|
|
|
49
51
|
self_read, self_write = IO.pipe
|
|
50
|
-
sigs = %w[INT TERM TTIN TSTP]
|
|
52
|
+
sigs = %w[INT TERM INFO TTIN TSTP]
|
|
51
53
|
# USR1 and USR2 don't work on the JVM
|
|
52
54
|
sigs << "USR2" if Sidekiq.pro? && !jruby?
|
|
53
55
|
sigs.each do |sig|
|
|
@@ -74,7 +76,7 @@ module Sidekiq # :nodoc:
|
|
|
74
76
|
# fire startup and start multithreading.
|
|
75
77
|
info = @config.redis_info
|
|
76
78
|
ver = Gem::Version.new(info["redis_version"])
|
|
77
|
-
raise "You are
|
|
79
|
+
raise "You are connected to Redis #{ver}, Sidekiq requires Redis 7.0.0 or greater" if ver < Gem::Version.new("7.0.0")
|
|
78
80
|
|
|
79
81
|
maxmemory_policy = info["maxmemory_policy"]
|
|
80
82
|
if maxmemory_policy != "noeviction" && maxmemory_policy != ""
|
|
@@ -199,6 +201,7 @@ module Sidekiq # :nodoc:
|
|
|
199
201
|
cli.logger.info "Received TSTP, no longer accepting new work"
|
|
200
202
|
cli.launcher.quiet
|
|
201
203
|
},
|
|
204
|
+
# deprecated, use INFO
|
|
202
205
|
"TTIN" => ->(cli) {
|
|
203
206
|
Thread.list.each do |thread|
|
|
204
207
|
cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
|
|
@@ -208,14 +211,24 @@ module Sidekiq # :nodoc:
|
|
|
208
211
|
cli.logger.warn "<no backtrace available>"
|
|
209
212
|
end
|
|
210
213
|
end
|
|
214
|
+
},
|
|
215
|
+
"INFO" => ->(cli) {
|
|
216
|
+
Thread.list.each do |thread|
|
|
217
|
+
cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}"
|
|
218
|
+
if thread.backtrace
|
|
219
|
+
cli.logger.warn thread.backtrace.join("\n")
|
|
220
|
+
else
|
|
221
|
+
cli.logger.warn "<no backtrace available>"
|
|
222
|
+
end
|
|
223
|
+
end
|
|
211
224
|
}
|
|
212
225
|
}
|
|
213
|
-
UNHANDLED_SIGNAL_HANDLER = ->(cli) { cli.logger.info "No signal handler registered, ignoring" }
|
|
214
|
-
SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
|
|
215
226
|
|
|
216
227
|
def handle_signal(sig)
|
|
217
228
|
logger.debug "Got #{sig} signal"
|
|
218
|
-
SIGNAL_HANDLERS[sig]
|
|
229
|
+
hndlr = SIGNAL_HANDLERS[sig]
|
|
230
|
+
hndlr ? hndlr.call(self) :
|
|
231
|
+
logger.warn("No #{sig} signal handler registered, ignoring")
|
|
219
232
|
end
|
|
220
233
|
|
|
221
234
|
private
|
|
@@ -298,29 +311,18 @@ module Sidekiq # :nodoc:
|
|
|
298
311
|
|
|
299
312
|
if File.directory?(@config[:require])
|
|
300
313
|
require "rails"
|
|
301
|
-
if ::Rails::VERSION::MAJOR <
|
|
302
|
-
warn "Sidekiq #{Sidekiq::VERSION} only supports Rails
|
|
314
|
+
if ::Rails::VERSION::MAJOR < 7
|
|
315
|
+
warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 7+"
|
|
303
316
|
end
|
|
304
317
|
require "sidekiq/rails"
|
|
305
318
|
require File.expand_path("#{@config[:require]}/config/environment.rb")
|
|
306
|
-
@config[:tag] ||= default_tag
|
|
319
|
+
@config[:tag] ||= default_tag(::Rails.root)
|
|
307
320
|
else
|
|
308
321
|
require @config[:require]
|
|
322
|
+
@config[:tag] ||= default_tag
|
|
309
323
|
end
|
|
310
324
|
end
|
|
311
325
|
|
|
312
|
-
def default_tag
|
|
313
|
-
dir = ::Rails.root
|
|
314
|
-
name = File.basename(dir)
|
|
315
|
-
prevdir = File.dirname(dir) # Capistrano release directory?
|
|
316
|
-
if name.to_i != 0 && prevdir
|
|
317
|
-
if File.basename(prevdir) == "releases"
|
|
318
|
-
return File.basename(File.dirname(prevdir))
|
|
319
|
-
end
|
|
320
|
-
end
|
|
321
|
-
name
|
|
322
|
-
end
|
|
323
|
-
|
|
324
326
|
def validate!
|
|
325
327
|
if !File.exist?(@config[:require]) ||
|
|
326
328
|
(File.directory?(@config[:require]) && !File.exist?("#{@config[:require]}/config/application.rb"))
|
|
@@ -395,7 +397,12 @@ module Sidekiq # :nodoc:
|
|
|
395
397
|
end
|
|
396
398
|
|
|
397
399
|
def initialize_logger
|
|
398
|
-
|
|
400
|
+
if @config[:verbose] || ENV["DEBUG_INVOCATION"] == "1"
|
|
401
|
+
# DEBUG_INVOCATION is a systemd-ism triggered by
|
|
402
|
+
# RestartMode=debug. We turn on debugging when the
|
|
403
|
+
# sidekiq process crashes and is restarted with this flag.
|
|
404
|
+
@config.logger.level = ::Logger::DEBUG
|
|
405
|
+
end
|
|
399
406
|
end
|
|
400
407
|
|
|
401
408
|
def parse_config(path)
|
data/lib/sidekiq/client.rb
CHANGED
|
@@ -67,9 +67,7 @@ module Sidekiq
|
|
|
67
67
|
c.pipelined do |p|
|
|
68
68
|
p.hsetnx(key, "cancelled", Time.now.to_i)
|
|
69
69
|
p.hget(key, "cancelled")
|
|
70
|
-
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
|
|
71
|
-
# TODO When Redis 7.2 is required
|
|
72
|
-
# p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
|
70
|
+
p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
|
|
73
71
|
end
|
|
74
72
|
end
|
|
75
73
|
result.to_i
|
|
@@ -119,6 +117,9 @@ module Sidekiq
|
|
|
119
117
|
# larger than 1000 but YMMV based on network quality, size of job args, etc.
|
|
120
118
|
# A large number of jobs can cause a bit of Redis command processing latency.
|
|
121
119
|
#
|
|
120
|
+
# Accepts an additional `:spread_interval` option (in seconds) to randomly spread
|
|
121
|
+
# the jobs schedule times over the specified interval.
|
|
122
|
+
#
|
|
122
123
|
# Takes the same arguments as #push except that args is expected to be
|
|
123
124
|
# an Array of Arrays. All other keys are duplicated for each job. Each job
|
|
124
125
|
# is run through the client middleware pipeline and each job gets its own Job ID
|
|
@@ -133,13 +134,24 @@ module Sidekiq
|
|
|
133
134
|
def push_bulk(items)
|
|
134
135
|
batch_size = items.delete(:batch_size) || items.delete("batch_size") || 1_000
|
|
135
136
|
args = items["args"]
|
|
136
|
-
at = items.delete("at")
|
|
137
|
+
at = items.delete("at") || items.delete(:at)
|
|
137
138
|
raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all? { |entry| entry.is_a?(Numeric) })
|
|
138
139
|
raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
|
|
139
140
|
|
|
140
141
|
jid = items.delete("jid")
|
|
141
142
|
raise ArgumentError, "Explicitly passing 'jid' when pushing more than one job is not supported" if jid && args.size > 1
|
|
142
143
|
|
|
144
|
+
spread_interval = items.delete(:spread_interval) || items.delete("spread_interval")
|
|
145
|
+
raise ArgumentError, "Jobs 'spread_interval' must be a positive Numeric" if spread_interval && (!spread_interval.is_a?(Numeric) || spread_interval <= 0)
|
|
146
|
+
raise ArgumentError, "Only one of 'at' or 'spread_interval' can be provided" if at && spread_interval
|
|
147
|
+
|
|
148
|
+
if !at && spread_interval
|
|
149
|
+
# Do not use spread interval smaller than pooling interval.
|
|
150
|
+
spread_interval = [spread_interval, 5].max
|
|
151
|
+
now = Time.now.to_f
|
|
152
|
+
at = args.map { now + rand * spread_interval }
|
|
153
|
+
end
|
|
154
|
+
|
|
143
155
|
normed = normalize_item(items)
|
|
144
156
|
slice_index = 0
|
|
145
157
|
result = args.each_slice(batch_size).flat_map do |slice|
|
|
@@ -266,22 +278,21 @@ module Sidekiq
|
|
|
266
278
|
if payloads.first.key?("at")
|
|
267
279
|
conn.zadd("schedule", payloads.flat_map { |hash|
|
|
268
280
|
at = hash["at"].to_s
|
|
269
|
-
# ActiveJob sets
|
|
270
|
-
hash.
|
|
271
|
-
# TODO: Use hash.except("at") when support for Ruby 2.7 is dropped
|
|
272
|
-
hash = hash.dup
|
|
273
|
-
hash.delete("at")
|
|
281
|
+
# ActiveJob sets enqueued_at but the job has not been enqueued yet
|
|
282
|
+
hash = hash.except("enqueued_at", "at")
|
|
274
283
|
[at, Sidekiq.dump_json(hash)]
|
|
275
284
|
})
|
|
276
285
|
else
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) # milliseconds since the epoch
|
|
287
|
+
grouped_queues = payloads.group_by { |job| job["queue"] }
|
|
288
|
+
conn.sadd("queues", grouped_queues.keys)
|
|
289
|
+
grouped_queues.each do |queue, grouped_payloads|
|
|
290
|
+
to_push = grouped_payloads.map { |entry|
|
|
291
|
+
entry["enqueued_at"] = now
|
|
292
|
+
Sidekiq.dump_json(entry)
|
|
293
|
+
}
|
|
294
|
+
conn.lpush("queue:#{queue}", to_push)
|
|
295
|
+
end
|
|
285
296
|
end
|
|
286
297
|
end
|
|
287
298
|
end
|
data/lib/sidekiq/component.rb
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Sidekiq
|
|
4
|
+
# Ruby's default thread priority is 0, which uses 100ms time slices.
|
|
5
|
+
# This can lead to some surprising thread starvation; if using a lot of
|
|
6
|
+
# CPU-heavy concurrency, it may take several seconds before a Thread gets
|
|
7
|
+
# on the CPU.
|
|
8
|
+
#
|
|
9
|
+
# Negative priorities lower the timeslice by half, so -1 = 50ms, -2 = 25ms, etc.
|
|
10
|
+
# With more frequent timeslices, we reduce the risk of unintentional timeouts
|
|
11
|
+
# and starvation.
|
|
12
|
+
#
|
|
13
|
+
# Customize like so:
|
|
14
|
+
#
|
|
15
|
+
# Sidekiq.configure_server do |cfg|
|
|
16
|
+
# cfg.thread_priority = 0
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
DEFAULT_THREAD_PRIORITY = -1
|
|
20
|
+
|
|
4
21
|
##
|
|
5
|
-
# Sidekiq::Component
|
|
22
|
+
# Sidekiq::Component provides a set of utility methods depending only
|
|
23
|
+
# on Sidekiq::Config. It assumes a config instance is available at @config.
|
|
6
24
|
module Component # :nodoc:
|
|
7
25
|
attr_reader :config
|
|
8
26
|
|
|
27
|
+
# This is epoch milliseconds, appropriate for persistence
|
|
28
|
+
def real_ms
|
|
29
|
+
::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# used for time difference and relative comparisons, not persistence.
|
|
33
|
+
def mono_ms
|
|
34
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
|
|
35
|
+
end
|
|
36
|
+
|
|
9
37
|
def watchdog(last_words)
|
|
10
38
|
yield
|
|
11
39
|
rescue Exception => ex
|
|
@@ -13,11 +41,11 @@ module Sidekiq
|
|
|
13
41
|
raise ex
|
|
14
42
|
end
|
|
15
43
|
|
|
16
|
-
def safe_thread(name, &block)
|
|
44
|
+
def safe_thread(name, priority: nil, &block)
|
|
17
45
|
Thread.new do
|
|
18
46
|
Thread.current.name = "sidekiq.#{name}"
|
|
19
47
|
watchdog(name, &block)
|
|
20
|
-
end
|
|
48
|
+
end.tap { |t| t.priority = (priority || config.thread_priority || DEFAULT_THREAD_PRIORITY) }
|
|
21
49
|
end
|
|
22
50
|
|
|
23
51
|
def logger
|
|
@@ -86,5 +114,16 @@ module Sidekiq
|
|
|
86
114
|
end.join(", ")
|
|
87
115
|
}>"
|
|
88
116
|
end
|
|
117
|
+
|
|
118
|
+
def default_tag(dir = Dir.pwd)
|
|
119
|
+
name = File.basename(dir)
|
|
120
|
+
prevdir = File.dirname(dir) # Capistrano release directory?
|
|
121
|
+
if name.to_i != 0 && prevdir
|
|
122
|
+
if File.basename(prevdir) == "releases"
|
|
123
|
+
return File.basename(File.dirname(prevdir))
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
name
|
|
127
|
+
end
|
|
89
128
|
end
|
|
90
129
|
end
|