sidekiq 6.0.7 → 6.4.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +189 -2
- data/LICENSE +3 -3
- data/README.md +11 -10
- data/bin/sidekiq +8 -3
- data/bin/sidekiqload +57 -65
- data/bin/sidekiqmon +1 -1
- 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/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +164 -116
- data/lib/sidekiq/cli.rb +49 -15
- data/lib/sidekiq/client.rb +51 -70
- data/lib/sidekiq/delay.rb +2 -0
- data/lib/sidekiq/extensions/action_mailer.rb +3 -2
- data/lib/sidekiq/extensions/active_record.rb +4 -3
- data/lib/sidekiq/extensions/class_methods.rb +5 -4
- data/lib/sidekiq/extensions/generic_proxy.rb +4 -2
- data/lib/sidekiq/fetch.rb +32 -23
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +16 -28
- data/lib/sidekiq/job_retry.rb +32 -33
- data/lib/sidekiq/job_util.rb +67 -0
- data/lib/sidekiq/launcher.rb +113 -54
- data/lib/sidekiq/logger.rb +11 -20
- data/lib/sidekiq/manager.rb +16 -18
- data/lib/sidekiq/middleware/chain.rb +10 -8
- data/lib/sidekiq/middleware/current_attributes.rb +57 -0
- data/lib/sidekiq/middleware/i18n.rb +4 -4
- data/lib/sidekiq/monitor.rb +1 -1
- data/lib/sidekiq/paginator.rb +8 -8
- data/lib/sidekiq/processor.rb +31 -31
- data/lib/sidekiq/rails.rb +36 -20
- data/lib/sidekiq/redis_connection.rb +16 -15
- data/lib/sidekiq/scheduled.rb +51 -16
- data/lib/sidekiq/sd_notify.rb +1 -1
- data/lib/sidekiq/testing/inline.rb +4 -4
- data/lib/sidekiq/testing.rb +38 -39
- data/lib/sidekiq/util.rb +41 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +2 -2
- data/lib/sidekiq/web/application.rb +21 -12
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +39 -33
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +36 -72
- data/lib/sidekiq/worker.rb +135 -16
- data/lib/sidekiq.rb +33 -17
- data/sidekiq.gemspec +11 -4
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +113 -65
- data/web/assets/javascripts/dashboard.js +51 -51
- data/web/assets/stylesheets/application-dark.css +64 -43
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +42 -239
- data/web/locales/ar.yml +8 -2
- data/web/locales/en.yml +4 -1
- data/web/locales/es.yml +18 -2
- data/web/locales/fr.yml +8 -1
- data/web/locales/ja.yml +3 -0
- data/web/locales/lt.yml +1 -1
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/views/_footer.erb +1 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +51 -20
- data/web/views/dashboard.erb +22 -14
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +2 -1
- data/web/views/morgue.erb +6 -6
- data/web/views/queue.erb +11 -11
- data/web/views/queues.erb +4 -4
- data/web/views/retries.erb +7 -7
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +1 -1
- metadata +24 -49
- data/.circleci/config.yml +0 -60
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -11
- data/.gitignore +0 -13
- data/.standard.yml +0 -20
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/5.0-Upgrade.md +0 -56
- data/6.0-Upgrade.md +0 -72
- data/COMM-LICENSE +0 -97
- data/Ent-2.0-Upgrade.md +0 -37
- data/Ent-Changes.md +0 -256
- data/Gemfile +0 -24
- data/Gemfile.lock +0 -208
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-4.0-Upgrade.md +0 -35
- data/Pro-5.0-Upgrade.md +0 -25
- data/Pro-Changes.md +0 -782
- data/Rakefile +0 -10
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
@@ -0,0 +1,67 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
module Sidekiq
|
5
|
+
module JobUtil
|
6
|
+
# These functions encapsulate various job utilities.
|
7
|
+
# They must be simple and free from side effects.
|
8
|
+
|
9
|
+
def validate(item)
|
10
|
+
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
|
11
|
+
raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array)
|
12
|
+
raise(ArgumentError, "Job class must be either a Class or String representation of the class name: `#{item}`") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
|
13
|
+
raise(ArgumentError, "Job 'at' must be a Numeric timestamp: `#{item}`") if item.key?("at") && !item["at"].is_a?(Numeric)
|
14
|
+
raise(ArgumentError, "Job tags must be an Array: `#{item}`") if item["tags"] && !item["tags"].is_a?(Array)
|
15
|
+
end
|
16
|
+
|
17
|
+
def verify_json(item)
|
18
|
+
job_class = item["wrapped"] || item["class"]
|
19
|
+
if Sidekiq.options[:on_complex_arguments] == :raise
|
20
|
+
msg = <<~EOM
|
21
|
+
Job arguments to #{job_class} must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices.
|
22
|
+
To disable this error, remove `Sidekiq.strict_args!` from your initializer.
|
23
|
+
EOM
|
24
|
+
raise(ArgumentError, msg) unless json_safe?(item)
|
25
|
+
elsif Sidekiq.options[:on_complex_arguments] == :warn
|
26
|
+
Sidekiq.logger.warn <<~EOM unless json_safe?(item)
|
27
|
+
Job arguments to #{job_class} do not serialize to JSON safely. This will raise an error in
|
28
|
+
Sidekiq 7.0. See https://github.com/mperham/sidekiq/wiki/Best-Practices or raise an error today
|
29
|
+
by calling `Sidekiq.strict_args!` during Sidekiq initialization.
|
30
|
+
EOM
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize_item(item)
|
35
|
+
validate(item)
|
36
|
+
|
37
|
+
# merge in the default sidekiq_options for the item's class and/or wrapped element
|
38
|
+
# this allows ActiveJobs to control sidekiq_options too.
|
39
|
+
defaults = normalized_hash(item["class"])
|
40
|
+
defaults = defaults.merge(item["wrapped"].get_sidekiq_options) if item["wrapped"].respond_to?(:get_sidekiq_options)
|
41
|
+
item = defaults.merge(item)
|
42
|
+
|
43
|
+
raise(ArgumentError, "Job must include a valid queue name") if item["queue"].nil? || item["queue"] == ""
|
44
|
+
|
45
|
+
item["jid"] ||= SecureRandom.hex(12)
|
46
|
+
item["class"] = item["class"].to_s
|
47
|
+
item["queue"] = item["queue"].to_s
|
48
|
+
item["created_at"] ||= Time.now.to_f
|
49
|
+
item
|
50
|
+
end
|
51
|
+
|
52
|
+
def normalized_hash(item_class)
|
53
|
+
if item_class.is_a?(Class)
|
54
|
+
raise(ArgumentError, "Message must include a Sidekiq::Job class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?(:get_sidekiq_options)
|
55
|
+
item_class.get_sidekiq_options
|
56
|
+
else
|
57
|
+
Sidekiq.default_job_options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def json_safe?(item)
|
64
|
+
JSON.parse(JSON.dump(item["args"])) == item["args"]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -15,13 +15,14 @@ module Sidekiq
|
|
15
15
|
proc { "sidekiq" },
|
16
16
|
proc { Sidekiq::VERSION },
|
17
17
|
proc { |me, data| data["tag"] },
|
18
|
-
proc { |me, data| "[#{Processor::
|
18
|
+
proc { |me, data| "[#{Processor::WORK_STATE.size} of #{data["concurrency"]} busy]" },
|
19
19
|
proc { |me, data| "stopping" if me.stopping? }
|
20
20
|
]
|
21
21
|
|
22
22
|
attr_accessor :manager, :poller, :fetcher
|
23
23
|
|
24
24
|
def initialize(options)
|
25
|
+
options[:fetch] ||= BasicFetch.new(options)
|
25
26
|
@manager = Sidekiq::Manager.new(options)
|
26
27
|
@poller = Sidekiq::Scheduled::Poller.new
|
27
28
|
@done = false
|
@@ -42,9 +43,7 @@ module Sidekiq
|
|
42
43
|
@poller.terminate
|
43
44
|
end
|
44
45
|
|
45
|
-
# Shuts down
|
46
|
-
# return until all work is complete and cleaned up.
|
47
|
-
# It can take up to the timeout to complete.
|
46
|
+
# Shuts down this Sidekiq instance. Waits up to the deadline for all jobs to complete.
|
48
47
|
def stop
|
49
48
|
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @options[:timeout]
|
50
49
|
|
@@ -54,9 +53,9 @@ module Sidekiq
|
|
54
53
|
|
55
54
|
@manager.stop(deadline)
|
56
55
|
|
57
|
-
# Requeue everything in case there was a
|
56
|
+
# Requeue everything in case there was a thread which fetched a job while the process was stopped.
|
58
57
|
# This call is a no-op in Sidekiq but necessary for Sidekiq Pro.
|
59
|
-
strategy =
|
58
|
+
strategy = @options[:fetch]
|
60
59
|
strategy.bulk_requeue([], @options)
|
61
60
|
|
62
61
|
clear_heartbeat
|
@@ -68,10 +67,12 @@ module Sidekiq
|
|
68
67
|
|
69
68
|
private unless $TESTING
|
70
69
|
|
70
|
+
BEAT_PAUSE = 5
|
71
|
+
|
71
72
|
def start_heartbeat
|
72
73
|
loop do
|
73
74
|
heartbeat
|
74
|
-
sleep
|
75
|
+
sleep BEAT_PAUSE
|
75
76
|
end
|
76
77
|
Sidekiq.logger.info("Heartbeat stopping...")
|
77
78
|
end
|
@@ -81,9 +82,9 @@ module Sidekiq
|
|
81
82
|
# Note we don't stop the heartbeat thread; if the process
|
82
83
|
# doesn't actually exit, it'll reappear in the Web UI.
|
83
84
|
Sidekiq.redis do |conn|
|
84
|
-
conn.pipelined do
|
85
|
-
|
86
|
-
|
85
|
+
conn.pipelined do |pipeline|
|
86
|
+
pipeline.srem("processes", identity)
|
87
|
+
pipeline.unlink("#{identity}:work")
|
87
88
|
end
|
88
89
|
end
|
89
90
|
rescue
|
@@ -104,14 +105,14 @@ module Sidekiq
|
|
104
105
|
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
105
106
|
begin
|
106
107
|
Sidekiq.redis do |conn|
|
107
|
-
conn.pipelined do
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
108
|
+
conn.pipelined do |pipeline|
|
109
|
+
pipeline.incrby("stat:processed", procd)
|
110
|
+
pipeline.incrby("stat:processed:#{nowdate}", procd)
|
111
|
+
pipeline.expire("stat:processed:#{nowdate}", STATS_TTL)
|
112
|
+
|
113
|
+
pipeline.incrby("stat:failed", fails)
|
114
|
+
pipeline.incrby("stat:failed:#{nowdate}", fails)
|
115
|
+
pipeline.expire("stat:failed:#{nowdate}", STATS_TTL)
|
115
116
|
end
|
116
117
|
end
|
117
118
|
rescue => ex
|
@@ -129,38 +130,49 @@ module Sidekiq
|
|
129
130
|
begin
|
130
131
|
fails = Processor::FAILURE.reset
|
131
132
|
procd = Processor::PROCESSED.reset
|
132
|
-
curstate = Processor::
|
133
|
+
curstate = Processor::WORK_STATE.dup
|
133
134
|
|
134
|
-
workers_key = "#{key}:workers"
|
135
135
|
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
136
136
|
|
137
137
|
Sidekiq.redis do |conn|
|
138
|
-
conn.multi do
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
138
|
+
conn.multi do |transaction|
|
139
|
+
transaction.incrby("stat:processed", procd)
|
140
|
+
transaction.incrby("stat:processed:#{nowdate}", procd)
|
141
|
+
transaction.expire("stat:processed:#{nowdate}", STATS_TTL)
|
142
|
+
|
143
|
+
transaction.incrby("stat:failed", fails)
|
144
|
+
transaction.incrby("stat:failed:#{nowdate}", fails)
|
145
|
+
transaction.expire("stat:failed:#{nowdate}", STATS_TTL)
|
146
|
+
end
|
146
147
|
|
147
|
-
|
148
|
+
# work is the current set of executing jobs
|
149
|
+
work_key = "#{key}:work"
|
150
|
+
conn.pipelined do |transaction|
|
151
|
+
transaction.unlink(work_key)
|
148
152
|
curstate.each_pair do |tid, hash|
|
149
|
-
|
153
|
+
transaction.hset(work_key, tid, Sidekiq.dump_json(hash))
|
150
154
|
end
|
151
|
-
|
155
|
+
transaction.expire(work_key, 60)
|
152
156
|
end
|
153
157
|
end
|
154
158
|
|
159
|
+
rtt = check_rtt
|
160
|
+
|
155
161
|
fails = procd = 0
|
162
|
+
kb = memory_usage(::Process.pid)
|
156
163
|
|
157
164
|
_, exists, _, _, msg = Sidekiq.redis { |conn|
|
158
|
-
conn.multi {
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
165
|
+
conn.multi { |transaction|
|
166
|
+
transaction.sadd("processes", key)
|
167
|
+
transaction.exists?(key)
|
168
|
+
transaction.hmset(key, "info", to_json,
|
169
|
+
"busy", curstate.size,
|
170
|
+
"beat", Time.now.to_f,
|
171
|
+
"rtt_us", rtt,
|
172
|
+
"quiet", @done,
|
173
|
+
"rss", kb)
|
174
|
+
transaction.expire(key, 60)
|
175
|
+
transaction.rpop("#{key}-signals")
|
164
176
|
}
|
165
177
|
}
|
166
178
|
|
@@ -179,27 +191,74 @@ module Sidekiq
|
|
179
191
|
end
|
180
192
|
end
|
181
193
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
+
# We run the heartbeat every five seconds.
|
195
|
+
# Capture five samples of RTT, log a warning if each sample
|
196
|
+
# is above our warning threshold.
|
197
|
+
RTT_READINGS = RingBuffer.new(5)
|
198
|
+
RTT_WARNING_LEVEL = 50_000
|
199
|
+
|
200
|
+
def check_rtt
|
201
|
+
a = b = 0
|
202
|
+
Sidekiq.redis do |x|
|
203
|
+
a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
204
|
+
x.ping
|
205
|
+
b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
194
206
|
end
|
207
|
+
rtt = b - a
|
208
|
+
RTT_READINGS << rtt
|
209
|
+
# Ideal RTT for Redis is < 1000µs
|
210
|
+
# Workable is < 10,000µs
|
211
|
+
# Log a warning if it's a disaster.
|
212
|
+
if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
|
213
|
+
Sidekiq.logger.warn <<~EOM
|
214
|
+
Your Redis network connection is performing extremely poorly.
|
215
|
+
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
216
|
+
Ensure Redis is running in the same AZ or datacenter as Sidekiq.
|
217
|
+
If these values are close to 100,000, that means your Sidekiq process may be
|
218
|
+
CPU-saturated; reduce your concurrency and/or see https://github.com/mperham/sidekiq/discussions/5039
|
219
|
+
EOM
|
220
|
+
RTT_READINGS.reset
|
221
|
+
end
|
222
|
+
rtt
|
223
|
+
end
|
224
|
+
|
225
|
+
MEMORY_GRABBER = case RUBY_PLATFORM
|
226
|
+
when /linux/
|
227
|
+
->(pid) {
|
228
|
+
IO.readlines("/proc/#{$$}/status").each do |line|
|
229
|
+
next unless line.start_with?("VmRSS:")
|
230
|
+
break line.split[1].to_i
|
231
|
+
end
|
232
|
+
}
|
233
|
+
when /darwin|bsd/
|
234
|
+
->(pid) {
|
235
|
+
`ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i
|
236
|
+
}
|
237
|
+
else
|
238
|
+
->(pid) { 0 }
|
239
|
+
end
|
240
|
+
|
241
|
+
def memory_usage(pid)
|
242
|
+
MEMORY_GRABBER.call(pid)
|
243
|
+
end
|
244
|
+
|
245
|
+
def to_data
|
246
|
+
@data ||= {
|
247
|
+
"hostname" => hostname,
|
248
|
+
"started_at" => Time.now.to_f,
|
249
|
+
"pid" => ::Process.pid,
|
250
|
+
"tag" => @options[:tag] || "",
|
251
|
+
"concurrency" => @options[:concurrency],
|
252
|
+
"queues" => @options[:queues].uniq,
|
253
|
+
"labels" => @options[:labels],
|
254
|
+
"identity" => identity
|
255
|
+
}
|
195
256
|
end
|
196
257
|
|
197
258
|
def to_json
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
Sidekiq.dump_json(to_data)
|
202
|
-
end
|
259
|
+
# this data changes infrequently so dump it to a string
|
260
|
+
# now so we don't need to dump it every heartbeat.
|
261
|
+
@json ||= Sidekiq.dump_json(to_data)
|
203
262
|
end
|
204
263
|
end
|
205
264
|
end
|
data/lib/sidekiq/logger.rb
CHANGED
@@ -6,15 +6,20 @@ require "time"
|
|
6
6
|
module Sidekiq
|
7
7
|
module Context
|
8
8
|
def self.with(hash)
|
9
|
+
orig_context = current.dup
|
9
10
|
current.merge!(hash)
|
10
11
|
yield
|
11
12
|
ensure
|
12
|
-
|
13
|
+
Thread.current[:sidekiq_context] = orig_context
|
13
14
|
end
|
14
15
|
|
15
16
|
def self.current
|
16
17
|
Thread.current[:sidekiq_context] ||= {}
|
17
18
|
end
|
19
|
+
|
20
|
+
def self.add(k, v)
|
21
|
+
Thread.current[:sidekiq_context][k] = v
|
22
|
+
end
|
18
23
|
end
|
19
24
|
|
20
25
|
module LoggingUtils
|
@@ -30,24 +35,10 @@ module Sidekiq
|
|
30
35
|
nil
|
31
36
|
end
|
32
37
|
|
33
|
-
|
34
|
-
level
|
35
|
-
|
36
|
-
|
37
|
-
def info?
|
38
|
-
level <= 1
|
39
|
-
end
|
40
|
-
|
41
|
-
def warn?
|
42
|
-
level <= 2
|
43
|
-
end
|
44
|
-
|
45
|
-
def error?
|
46
|
-
level <= 3
|
47
|
-
end
|
48
|
-
|
49
|
-
def fatal?
|
50
|
-
level <= 4
|
38
|
+
LEVELS.each do |level, numeric_level|
|
39
|
+
define_method("#{level}?") do
|
40
|
+
local_level.nil? ? super() : local_level <= numeric_level
|
41
|
+
end
|
51
42
|
end
|
52
43
|
|
53
44
|
def local_level
|
@@ -89,7 +80,7 @@ module Sidekiq
|
|
89
80
|
return true if @logdev.nil? || severity < level
|
90
81
|
|
91
82
|
if message.nil?
|
92
|
-
if
|
83
|
+
if block
|
93
84
|
message = yield
|
94
85
|
else
|
95
86
|
message = progname
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -35,7 +35,7 @@ module Sidekiq
|
|
35
35
|
@done = false
|
36
36
|
@workers = Set.new
|
37
37
|
@count.times do
|
38
|
-
@workers << Processor.new(self)
|
38
|
+
@workers << Processor.new(self, options)
|
39
39
|
end
|
40
40
|
@plock = Mutex.new
|
41
41
|
end
|
@@ -50,14 +50,11 @@ module Sidekiq
|
|
50
50
|
return if @done
|
51
51
|
@done = true
|
52
52
|
|
53
|
-
logger.info { "Terminating quiet
|
53
|
+
logger.info { "Terminating quiet threads" }
|
54
54
|
@workers.each { |x| x.terminate }
|
55
55
|
fire_event(:quiet, reverse: true)
|
56
56
|
end
|
57
57
|
|
58
|
-
# hack for quicker development / testing environment #2774
|
59
|
-
PAUSE_TIME = STDOUT.tty? ? 0.1 : 0.5
|
60
|
-
|
61
58
|
def stop(deadline)
|
62
59
|
quiet
|
63
60
|
fire_event(:shutdown, reverse: true)
|
@@ -68,13 +65,8 @@ module Sidekiq
|
|
68
65
|
sleep PAUSE_TIME
|
69
66
|
return if @workers.empty?
|
70
67
|
|
71
|
-
logger.info { "Pausing to allow
|
72
|
-
|
73
|
-
while remaining > PAUSE_TIME
|
74
|
-
return if @workers.empty?
|
75
|
-
sleep PAUSE_TIME
|
76
|
-
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
77
|
-
end
|
68
|
+
logger.info { "Pausing to allow jobs to finish..." }
|
69
|
+
wait_for(deadline) { @workers.empty? }
|
78
70
|
return if @workers.empty?
|
79
71
|
|
80
72
|
hard_shutdown
|
@@ -90,7 +82,7 @@ module Sidekiq
|
|
90
82
|
@plock.synchronize do
|
91
83
|
@workers.delete(processor)
|
92
84
|
unless @done
|
93
|
-
p = Processor.new(self)
|
85
|
+
p = Processor.new(self, options)
|
94
86
|
@workers << p
|
95
87
|
p.start
|
96
88
|
end
|
@@ -104,7 +96,7 @@ module Sidekiq
|
|
104
96
|
private
|
105
97
|
|
106
98
|
def hard_shutdown
|
107
|
-
# We've reached the timeout and we still have busy
|
99
|
+
# We've reached the timeout and we still have busy threads.
|
108
100
|
# They must die but their jobs shall live on.
|
109
101
|
cleanup = nil
|
110
102
|
@plock.synchronize do
|
@@ -114,22 +106,28 @@ module Sidekiq
|
|
114
106
|
if cleanup.size > 0
|
115
107
|
jobs = cleanup.map { |p| p.job }.compact
|
116
108
|
|
117
|
-
logger.warn { "Terminating #{cleanup.size} busy
|
118
|
-
logger.warn { "
|
109
|
+
logger.warn { "Terminating #{cleanup.size} busy threads" }
|
110
|
+
logger.warn { "Jobs still in progress #{jobs.inspect}" }
|
119
111
|
|
120
112
|
# Re-enqueue unfinished jobs
|
121
113
|
# NOTE: You may notice that we may push a job back to redis before
|
122
|
-
# the
|
114
|
+
# the thread is terminated. This is ok because Sidekiq's
|
123
115
|
# contract says that jobs are run AT LEAST once. Process termination
|
124
116
|
# is delayed until we're certain the jobs are back in Redis because
|
125
117
|
# it is worse to lose a job than to run it twice.
|
126
|
-
strategy =
|
118
|
+
strategy = @options[:fetch]
|
127
119
|
strategy.bulk_requeue(jobs, @options)
|
128
120
|
end
|
129
121
|
|
130
122
|
cleanup.each do |processor|
|
131
123
|
processor.kill
|
132
124
|
end
|
125
|
+
|
126
|
+
# when this method returns, we immediately call `exit` which may not give
|
127
|
+
# the remaining threads time to run `ensure` blocks, etc. We pause here up
|
128
|
+
# to 3 seconds to give threads a minimal amount of time to run `ensure` blocks.
|
129
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + 3
|
130
|
+
wait_for(deadline) { @workers.empty? }
|
133
131
|
end
|
134
132
|
end
|
135
133
|
end
|
@@ -44,10 +44,10 @@ module Sidekiq
|
|
44
44
|
# This is an example of a minimal server middleware:
|
45
45
|
#
|
46
46
|
# class MyServerHook
|
47
|
-
# def call(
|
48
|
-
# puts "Before
|
47
|
+
# def call(job_instance, msg, queue)
|
48
|
+
# puts "Before job"
|
49
49
|
# yield
|
50
|
-
# puts "After
|
50
|
+
# puts "After job"
|
51
51
|
# end
|
52
52
|
# end
|
53
53
|
#
|
@@ -56,7 +56,7 @@ module Sidekiq
|
|
56
56
|
# to Redis:
|
57
57
|
#
|
58
58
|
# class MyClientHook
|
59
|
-
# def call(
|
59
|
+
# def call(job_class, msg, queue, redis_pool)
|
60
60
|
# puts "Before push"
|
61
61
|
# result = yield
|
62
62
|
# puts "After push"
|
@@ -90,12 +90,12 @@ module Sidekiq
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def add(klass, *args)
|
93
|
-
remove(klass)
|
93
|
+
remove(klass)
|
94
94
|
entries << Entry.new(klass, *args)
|
95
95
|
end
|
96
96
|
|
97
97
|
def prepend(klass, *args)
|
98
|
-
remove(klass)
|
98
|
+
remove(klass)
|
99
99
|
entries.insert(0, Entry.new(klass, *args))
|
100
100
|
end
|
101
101
|
|
@@ -132,8 +132,8 @@ module Sidekiq
|
|
132
132
|
def invoke(*args)
|
133
133
|
return yield if empty?
|
134
134
|
|
135
|
-
chain = retrieve
|
136
|
-
traverse_chain =
|
135
|
+
chain = retrieve
|
136
|
+
traverse_chain = proc do
|
137
137
|
if chain.empty?
|
138
138
|
yield
|
139
139
|
else
|
@@ -144,6 +144,8 @@ module Sidekiq
|
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
+
private
|
148
|
+
|
147
149
|
class Entry
|
148
150
|
attr_reader :klass
|
149
151
|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "active_support/current_attributes"
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
##
|
5
|
+
# Automatically save and load any current attributes in the execution context
|
6
|
+
# so context attributes "flow" from Rails actions into any associated jobs.
|
7
|
+
# This can be useful for multi-tenancy, i18n locale, timezone, any implicit
|
8
|
+
# per-request attribute. See +ActiveSupport::CurrentAttributes+.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
#
|
12
|
+
# # in your initializer
|
13
|
+
# require "sidekiq/middleware/current_attributes"
|
14
|
+
# Sidekiq::CurrentAttributes.persist(Myapp::Current)
|
15
|
+
#
|
16
|
+
module CurrentAttributes
|
17
|
+
class Save
|
18
|
+
def initialize(cattr)
|
19
|
+
@klass = cattr
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(_, job, _, _)
|
23
|
+
attrs = @klass.attributes
|
24
|
+
if job.has_key?("cattr")
|
25
|
+
job["cattr"].merge!(attrs)
|
26
|
+
else
|
27
|
+
job["cattr"] = attrs
|
28
|
+
end
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Load
|
34
|
+
def initialize(cattr)
|
35
|
+
@klass = cattr
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(_, job, _, &block)
|
39
|
+
if job.has_key?("cattr")
|
40
|
+
@klass.set(job["cattr"], &block)
|
41
|
+
else
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.persist(klass)
|
48
|
+
Sidekiq.configure_client do |config|
|
49
|
+
config.client_middleware.add Save, klass
|
50
|
+
end
|
51
|
+
Sidekiq.configure_server do |config|
|
52
|
+
config.client_middleware.add Save, klass
|
53
|
+
config.server_middleware.add Load, klass
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -10,16 +10,16 @@ module Sidekiq::Middleware::I18n
|
|
10
10
|
# Get the current locale and store it in the message
|
11
11
|
# to be sent to Sidekiq.
|
12
12
|
class Client
|
13
|
-
def call(
|
14
|
-
|
13
|
+
def call(_jobclass, job, _queue, _redis)
|
14
|
+
job["locale"] ||= I18n.locale
|
15
15
|
yield
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
# Pull the msg locale out and set the current thread to use it.
|
20
20
|
class Server
|
21
|
-
def call(
|
22
|
-
I18n.with_locale(
|
21
|
+
def call(_jobclass, job, _queue, &block)
|
22
|
+
I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
data/lib/sidekiq/monitor.rb
CHANGED
data/lib/sidekiq/paginator.rb
CHANGED
@@ -16,22 +16,22 @@ module Sidekiq
|
|
16
16
|
|
17
17
|
case type
|
18
18
|
when "zset"
|
19
|
-
total_size, items = conn.multi {
|
20
|
-
|
19
|
+
total_size, items = conn.multi { |transaction|
|
20
|
+
transaction.zcard(key)
|
21
21
|
if rev
|
22
|
-
|
22
|
+
transaction.zrevrange(key, starting, ending, with_scores: true)
|
23
23
|
else
|
24
|
-
|
24
|
+
transaction.zrange(key, starting, ending, with_scores: true)
|
25
25
|
end
|
26
26
|
}
|
27
27
|
[current_page, total_size, items]
|
28
28
|
when "list"
|
29
|
-
total_size, items = conn.multi {
|
30
|
-
|
29
|
+
total_size, items = conn.multi { |transaction|
|
30
|
+
transaction.llen(key)
|
31
31
|
if rev
|
32
|
-
|
32
|
+
transaction.lrange(key, -ending - 1, -starting - 1)
|
33
33
|
else
|
34
|
-
|
34
|
+
transaction.lrange(key, starting, ending)
|
35
35
|
end
|
36
36
|
}
|
37
37
|
items.reverse! if rev
|