sidekiq 7.3.10 → 8.0.9
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 +125 -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 +142 -44
- data/lib/sidekiq/capsule.rb +10 -6
- data/lib/sidekiq/cli.rb +15 -19
- data/lib/sidekiq/client.rb +28 -17
- data/lib/sidekiq/component.rb +42 -3
- data/lib/sidekiq/config.rb +31 -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 +16 -6
- 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/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 +56 -34
- 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 +2 -0
- 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 +3 -3
- 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,25 @@ module Sidekiq
|
|
|
1037
1089
|
self["identity"]
|
|
1038
1090
|
end
|
|
1039
1091
|
|
|
1092
|
+
# deprecated, use capsules below
|
|
1040
1093
|
def queues
|
|
1041
|
-
|
|
1094
|
+
capsules.values.flat_map { |x| x["weights"].keys }.uniq
|
|
1042
1095
|
end
|
|
1043
1096
|
|
|
1097
|
+
# deprecated, use capsules below
|
|
1044
1098
|
def weights
|
|
1045
|
-
|
|
1099
|
+
hash = {}
|
|
1100
|
+
capsules.values.each do |cap|
|
|
1101
|
+
# Note: will lose data if two capsules are processing the same named queue
|
|
1102
|
+
cap["weights"].each_pair do |queue, weight|
|
|
1103
|
+
hash[queue] = weight
|
|
1104
|
+
end
|
|
1105
|
+
end
|
|
1106
|
+
hash
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
def capsules
|
|
1110
|
+
self["capsules"]
|
|
1046
1111
|
end
|
|
1047
1112
|
|
|
1048
1113
|
def version
|
|
@@ -1114,9 +1179,8 @@ module Sidekiq
|
|
|
1114
1179
|
# works.each do |process_id, thread_id, work|
|
|
1115
1180
|
# # process_id is a unique identifier per Sidekiq process
|
|
1116
1181
|
# # thread_id is a unique identifier per thread
|
|
1117
|
-
# # work is a
|
|
1118
|
-
# #
|
|
1119
|
-
# # run_at is an epoch Integer.
|
|
1182
|
+
# # work is a `Sidekiq::Work` instance that has the following accessor methods.
|
|
1183
|
+
# # [work.queue, work.run_at, work.payload]
|
|
1120
1184
|
# end
|
|
1121
1185
|
#
|
|
1122
1186
|
class WorkSet
|
|
@@ -1142,7 +1206,7 @@ module Sidekiq
|
|
|
1142
1206
|
end
|
|
1143
1207
|
end
|
|
1144
1208
|
|
|
1145
|
-
results.sort_by { |(_, _,
|
|
1209
|
+
results.sort_by { |(_, _, work)| work.run_at }.each(&block)
|
|
1146
1210
|
end
|
|
1147
1211
|
|
|
1148
1212
|
# Note that #size is only as accurate as Sidekiq's heartbeat,
|
|
@@ -1172,13 +1236,14 @@ module Sidekiq
|
|
|
1172
1236
|
#
|
|
1173
1237
|
# @param jid [String] the job identifier
|
|
1174
1238
|
# @return [Sidekiq::Work] the work or nil
|
|
1175
|
-
def
|
|
1239
|
+
def find_work(jid)
|
|
1176
1240
|
each do |_process_id, _thread_id, work|
|
|
1177
1241
|
job = work.job
|
|
1178
1242
|
return work if job.jid == jid
|
|
1179
1243
|
end
|
|
1180
1244
|
nil
|
|
1181
1245
|
end
|
|
1246
|
+
alias_method :find_work_by_jid, :find_work
|
|
1182
1247
|
end
|
|
1183
1248
|
|
|
1184
1249
|
# Sidekiq::Work represents a job which is currently executing.
|
|
@@ -1208,33 +1273,66 @@ module Sidekiq
|
|
|
1208
1273
|
def payload
|
|
1209
1274
|
@hsh["payload"]
|
|
1210
1275
|
end
|
|
1276
|
+
end
|
|
1211
1277
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
warn("Direct access to `Sidekiq::Work` attributes is deprecated, please use `#payload`, `#queue`, `#run_at` or `#job` instead", **kwargs)
|
|
1278
|
+
# Since "worker" is a nebulous term, we've deprecated the use of this class name.
|
|
1279
|
+
# Is "worker" a process, a type of job, a thread? Undefined!
|
|
1280
|
+
# WorkSet better describes the data.
|
|
1281
|
+
Workers = WorkSet
|
|
1217
1282
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1283
|
+
class ProfileSet
|
|
1284
|
+
include Enumerable
|
|
1220
1285
|
|
|
1221
|
-
#
|
|
1222
|
-
#
|
|
1223
|
-
def
|
|
1224
|
-
@
|
|
1286
|
+
# This is a point in time/snapshot API, you'll need to instantiate a new instance
|
|
1287
|
+
# if you want to fetch newer records.
|
|
1288
|
+
def initialize
|
|
1289
|
+
@records = Sidekiq.redis do |c|
|
|
1290
|
+
# This throws away expired profiles
|
|
1291
|
+
c.zremrangebyscore("profiles", "-inf", Time.now.to_f.to_s)
|
|
1292
|
+
# retreive records, newest to oldest
|
|
1293
|
+
c.zrange("profiles", "+inf", 0, "byscore", "rev")
|
|
1294
|
+
end
|
|
1225
1295
|
end
|
|
1226
1296
|
|
|
1227
|
-
def
|
|
1228
|
-
@
|
|
1297
|
+
def size
|
|
1298
|
+
@records.size
|
|
1229
1299
|
end
|
|
1230
1300
|
|
|
1231
|
-
def
|
|
1232
|
-
|
|
1301
|
+
def each(&block)
|
|
1302
|
+
fetch_keys = %w[started_at jid type token size elapsed].freeze
|
|
1303
|
+
arrays = Sidekiq.redis do |c|
|
|
1304
|
+
c.pipelined do |p|
|
|
1305
|
+
@records.each do |key|
|
|
1306
|
+
p.hmget(key, *fetch_keys)
|
|
1307
|
+
end
|
|
1308
|
+
end
|
|
1309
|
+
end
|
|
1310
|
+
|
|
1311
|
+
arrays.compact.map { |arr| ProfileRecord.new(arr) }.each(&block)
|
|
1233
1312
|
end
|
|
1234
1313
|
end
|
|
1235
1314
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1315
|
+
class ProfileRecord
|
|
1316
|
+
attr_reader :started_at, :jid, :type, :token, :size, :elapsed
|
|
1317
|
+
|
|
1318
|
+
def initialize(arr)
|
|
1319
|
+
# Must be same order as fetch_keys above
|
|
1320
|
+
@started_at = Time.at(Integer(arr[0]))
|
|
1321
|
+
@jid = arr[1]
|
|
1322
|
+
@type = arr[2]
|
|
1323
|
+
@token = arr[3]
|
|
1324
|
+
@size = Integer(arr[4])
|
|
1325
|
+
@elapsed = Float(arr[5])
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1328
|
+
def key
|
|
1329
|
+
"#{token}-#{jid}"
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
def data
|
|
1333
|
+
Sidekiq.redis { |c| c.hget(key, "data") }
|
|
1334
|
+
end
|
|
1335
|
+
end
|
|
1240
1336
|
end
|
|
1337
|
+
|
|
1338
|
+
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
|
|
@@ -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 != ""
|
|
@@ -298,29 +300,18 @@ module Sidekiq # :nodoc:
|
|
|
298
300
|
|
|
299
301
|
if File.directory?(@config[:require])
|
|
300
302
|
require "rails"
|
|
301
|
-
if ::Rails::VERSION::MAJOR <
|
|
302
|
-
warn "Sidekiq #{Sidekiq::VERSION} only supports Rails
|
|
303
|
+
if ::Rails::VERSION::MAJOR < 7
|
|
304
|
+
warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 7+"
|
|
303
305
|
end
|
|
304
306
|
require "sidekiq/rails"
|
|
305
307
|
require File.expand_path("#{@config[:require]}/config/environment.rb")
|
|
306
|
-
@config[:tag] ||= default_tag
|
|
308
|
+
@config[:tag] ||= default_tag(::Rails.root)
|
|
307
309
|
else
|
|
308
310
|
require @config[:require]
|
|
311
|
+
@config[:tag] ||= default_tag
|
|
309
312
|
end
|
|
310
313
|
end
|
|
311
314
|
|
|
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
315
|
def validate!
|
|
325
316
|
if !File.exist?(@config[:require]) ||
|
|
326
317
|
(File.directory?(@config[:require]) && !File.exist?("#{@config[:require]}/config/application.rb"))
|
|
@@ -395,7 +386,12 @@ module Sidekiq # :nodoc:
|
|
|
395
386
|
end
|
|
396
387
|
|
|
397
388
|
def initialize_logger
|
|
398
|
-
|
|
389
|
+
if @config[:verbose] || ENV["DEBUG_INVOCATION"] == "1"
|
|
390
|
+
# DEBUG_INVOCATION is a systemd-ism triggered by
|
|
391
|
+
# RestartMode=debug. We turn on debugging when the
|
|
392
|
+
# sidekiq process crashes and is restarted with this flag.
|
|
393
|
+
@config.logger.level = ::Logger::DEBUG
|
|
394
|
+
end
|
|
399
395
|
end
|
|
400
396
|
|
|
401
397
|
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
|
data/lib/sidekiq/config.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "forwardable"
|
|
4
|
-
|
|
5
|
-
require "set"
|
|
6
4
|
require "sidekiq/redis_connection"
|
|
7
5
|
|
|
8
6
|
module Sidekiq
|
|
@@ -19,16 +17,16 @@ module Sidekiq
|
|
|
19
17
|
poll_interval_average: nil,
|
|
20
18
|
average_scheduled_poll_interval: 5,
|
|
21
19
|
on_complex_arguments: :raise,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
20
|
+
# if the Iterable job runs longer than this value (in seconds), then the job
|
|
21
|
+
# will be interrupted after the current iteration and re-enqueued at the back of the queue
|
|
22
|
+
max_iteration_runtime: nil,
|
|
26
23
|
error_handlers: [],
|
|
27
24
|
death_handlers: [],
|
|
28
25
|
lifecycle_events: {
|
|
29
26
|
startup: [],
|
|
30
27
|
quiet: [],
|
|
31
28
|
shutdown: [],
|
|
29
|
+
exit: [],
|
|
32
30
|
# triggers when we fire the first heartbeat on startup OR repairing a network partition
|
|
33
31
|
heartbeat: [],
|
|
34
32
|
# triggers on EVERY heartbeat call, every 10 seconds
|
|
@@ -37,16 +35,27 @@ module Sidekiq
|
|
|
37
35
|
dead_max_jobs: 10_000,
|
|
38
36
|
dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
|
|
39
37
|
reloader: proc { |&block| block.call },
|
|
40
|
-
backtrace_cleaner: ->(backtrace) { backtrace }
|
|
38
|
+
backtrace_cleaner: ->(backtrace) { backtrace },
|
|
39
|
+
logged_job_attributes: ["bid", "tags"]
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
ERROR_HANDLER = ->(ex, ctx, cfg = Sidekiq.default_configuration) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
Sidekiq::Context.with(ctx) do
|
|
44
|
+
dev = cfg[:environment] == "development"
|
|
45
|
+
fancy = dev && $stdout.tty? # 🎩
|
|
46
|
+
# Weird logic here but we want to show the backtrace in local
|
|
47
|
+
# development or if verbose logging is enabled.
|
|
48
|
+
#
|
|
49
|
+
# `full_message` contains the error class, message and backtrace
|
|
50
|
+
# `detailed_message` contains the error class and message
|
|
51
|
+
#
|
|
52
|
+
# Absolutely terrible API names. Not useful at all to have two
|
|
53
|
+
# methods with similar but obscure names.
|
|
54
|
+
if dev || cfg.logger.debug?
|
|
55
|
+
cfg.logger.info { ex.full_message(highlight: fancy) }
|
|
56
|
+
else
|
|
57
|
+
cfg.logger.info { ex.detailed_message(highlight: fancy) }
|
|
58
|
+
end
|
|
50
59
|
end
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -60,6 +69,7 @@ module Sidekiq
|
|
|
60
69
|
|
|
61
70
|
def_delegators :@options, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
|
|
62
71
|
attr_reader :capsules
|
|
72
|
+
attr_accessor :thread_priority
|
|
63
73
|
|
|
64
74
|
def inspect
|
|
65
75
|
"#<#{self.class.name} @options=#{
|
|
@@ -135,11 +145,16 @@ module Sidekiq
|
|
|
135
145
|
@redis_config = @redis_config.merge(hash)
|
|
136
146
|
end
|
|
137
147
|
|
|
148
|
+
def reap_idle_redis_connections(timeout = nil)
|
|
149
|
+
self[:reap_connections] = timeout
|
|
150
|
+
end
|
|
151
|
+
alias_method :reap, :reap_idle_redis_connections
|
|
152
|
+
|
|
138
153
|
def redis_pool
|
|
139
154
|
Thread.current[:sidekiq_redis_pool] || Thread.current[:sidekiq_capsule]&.redis_pool || local_redis_pool
|
|
140
155
|
end
|
|
141
156
|
|
|
142
|
-
|
|
157
|
+
def local_redis_pool
|
|
143
158
|
# this is our internal client/housekeeping pool. each capsule has its
|
|
144
159
|
# own pool for executing threads.
|
|
145
160
|
@redis ||= new_redis_pool(10, "internal")
|
|
@@ -249,7 +264,7 @@ module Sidekiq
|
|
|
249
264
|
end
|
|
250
265
|
|
|
251
266
|
# Register a block to run at a point in the Sidekiq lifecycle.
|
|
252
|
-
# :startup, :quiet or :
|
|
267
|
+
# :startup, :quiet, :shutdown, or :exit are valid events.
|
|
253
268
|
#
|
|
254
269
|
# Sidekiq.configure_server do |config|
|
|
255
270
|
# config.on(:shutdown) do
|
|
@@ -293,13 +308,7 @@ module Sidekiq
|
|
|
293
308
|
p ["!!!!!", ex]
|
|
294
309
|
end
|
|
295
310
|
@options[:error_handlers].each do |handler|
|
|
296
|
-
|
|
297
|
-
# TODO Remove in 8.0
|
|
298
|
-
logger.info { "DEPRECATION: Sidekiq exception handlers now take three arguments, see #{handler}" }
|
|
299
|
-
handler.call(ex, {_config: self}.merge(ctx))
|
|
300
|
-
else
|
|
301
|
-
handler.call(ex, ctx, self)
|
|
302
|
-
end
|
|
311
|
+
handler.call(ex, ctx, self)
|
|
303
312
|
rescue Exception => e
|
|
304
313
|
l = logger
|
|
305
314
|
l.error "!!! ERROR HANDLER THREW AN ERROR !!!"
|