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