sidekiq 6.1.2 → 6.5.6
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.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +215 -2
- data/LICENSE +3 -3
- data/README.md +9 -4
- data/bin/sidekiq +3 -3
- data/bin/sidekiqload +70 -66
- 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 +321 -145
- data/lib/sidekiq/cli.rb +73 -40
- data/lib/sidekiq/client.rb +48 -72
- data/lib/sidekiq/{util.rb → component.rb} +12 -14
- data/lib/sidekiq/delay.rb +3 -1
- data/lib/sidekiq/extensions/generic_proxy.rb +4 -2
- data/lib/sidekiq/fetch.rb +31 -20
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +16 -28
- data/lib/sidekiq/job_retry.rb +79 -59
- data/lib/sidekiq/job_util.rb +71 -0
- data/lib/sidekiq/launcher.rb +126 -65
- data/lib/sidekiq/logger.rb +11 -20
- data/lib/sidekiq/manager.rb +35 -34
- data/lib/sidekiq/metrics/deploy.rb +47 -0
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +94 -0
- data/lib/sidekiq/metrics/tracking.rb +134 -0
- data/lib/sidekiq/middleware/chain.rb +87 -41
- data/lib/sidekiq/middleware/current_attributes.rb +63 -0
- data/lib/sidekiq/middleware/i18n.rb +6 -4
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +1 -1
- data/lib/sidekiq/paginator.rb +8 -8
- data/lib/sidekiq/processor.rb +47 -41
- data/lib/sidekiq/rails.rb +22 -4
- data/lib/sidekiq/redis_client_adapter.rb +154 -0
- data/lib/sidekiq/redis_connection.rb +84 -55
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +55 -25
- data/lib/sidekiq/testing/inline.rb +4 -4
- data/lib/sidekiq/testing.rb +38 -39
- data/lib/sidekiq/transaction_aware_client.rb +45 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -3
- data/lib/sidekiq/web/application.rb +37 -13
- data/lib/sidekiq/web/csrf_protection.rb +30 -8
- data/lib/sidekiq/web/helpers.rb +60 -28
- data/lib/sidekiq/web/router.rb +4 -1
- data/lib/sidekiq/web.rb +38 -78
- data/lib/sidekiq/worker.rb +136 -13
- data/lib/sidekiq.rb +114 -31
- data/sidekiq.gemspec +12 -4
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +113 -60
- 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.js +50 -67
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application-dark.css +36 -36
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +82 -237
- data/web/locales/ar.yml +8 -2
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +11 -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/pt-br.yml +27 -9
- data/web/views/_footer.erb +1 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/_nav.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +50 -19
- data/web/views/dashboard.erb +23 -14
- data/web/views/dead.erb +1 -1
- data/web/views/layout.erb +2 -1
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/morgue.erb +6 -6
- data/web/views/queue.erb +15 -11
- data/web/views/queues.erb +3 -3
- data/web/views/retries.erb +7 -7
- data/web/views/retry.erb +1 -1
- data/web/views/scheduled.erb +1 -1
- metadata +43 -36
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
- data/.github/contributing.md +0 -32
- data/.github/workflows/ci.yml +0 -41
- 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 -281
- data/Gemfile +0 -24
- data/Gemfile.lock +0 -192
- 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 -805
- data/Rakefile +0 -10
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
- data/lib/sidekiq/exception_handler.rb +0 -27
data/lib/sidekiq/launcher.rb
CHANGED
@@ -3,11 +3,12 @@
|
|
3
3
|
require "sidekiq/manager"
|
4
4
|
require "sidekiq/fetch"
|
5
5
|
require "sidekiq/scheduled"
|
6
|
+
require "sidekiq/ring_buffer"
|
6
7
|
|
7
8
|
module Sidekiq
|
8
9
|
# The Launcher starts the Manager and Poller threads and provides the process heartbeat.
|
9
10
|
class Launcher
|
10
|
-
include
|
11
|
+
include Sidekiq::Component
|
11
12
|
|
12
13
|
STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years
|
13
14
|
|
@@ -15,18 +16,18 @@ module Sidekiq
|
|
15
16
|
proc { "sidekiq" },
|
16
17
|
proc { Sidekiq::VERSION },
|
17
18
|
proc { |me, data| data["tag"] },
|
18
|
-
proc { |me, data| "[#{Processor::
|
19
|
+
proc { |me, data| "[#{Processor::WORK_STATE.size} of #{data["concurrency"]} busy]" },
|
19
20
|
proc { |me, data| "stopping" if me.stopping? }
|
20
21
|
]
|
21
22
|
|
22
23
|
attr_accessor :manager, :poller, :fetcher
|
23
24
|
|
24
25
|
def initialize(options)
|
26
|
+
@config = options
|
25
27
|
options[:fetch] ||= BasicFetch.new(options)
|
26
28
|
@manager = Sidekiq::Manager.new(options)
|
27
|
-
@poller = Sidekiq::Scheduled::Poller.new
|
29
|
+
@poller = Sidekiq::Scheduled::Poller.new(options)
|
28
30
|
@done = false
|
29
|
-
@options = options
|
30
31
|
end
|
31
32
|
|
32
33
|
def run
|
@@ -43,11 +44,9 @@ module Sidekiq
|
|
43
44
|
@poller.terminate
|
44
45
|
end
|
45
46
|
|
46
|
-
# Shuts down
|
47
|
-
# return until all work is complete and cleaned up.
|
48
|
-
# It can take up to the timeout to complete.
|
47
|
+
# Shuts down this Sidekiq instance. Waits up to the deadline for all jobs to complete.
|
49
48
|
def stop
|
50
|
-
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @
|
49
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @config[:timeout]
|
51
50
|
|
52
51
|
@done = true
|
53
52
|
@manager.quiet
|
@@ -55,10 +54,10 @@ module Sidekiq
|
|
55
54
|
|
56
55
|
@manager.stop(deadline)
|
57
56
|
|
58
|
-
# Requeue everything in case there was a
|
57
|
+
# Requeue everything in case there was a thread which fetched a job while the process was stopped.
|
59
58
|
# This call is a no-op in Sidekiq but necessary for Sidekiq Pro.
|
60
|
-
strategy = @
|
61
|
-
strategy.bulk_requeue([], @
|
59
|
+
strategy = @config[:fetch]
|
60
|
+
strategy.bulk_requeue([], @config)
|
62
61
|
|
63
62
|
clear_heartbeat
|
64
63
|
end
|
@@ -69,22 +68,26 @@ module Sidekiq
|
|
69
68
|
|
70
69
|
private unless $TESTING
|
71
70
|
|
71
|
+
BEAT_PAUSE = 5
|
72
|
+
|
72
73
|
def start_heartbeat
|
73
74
|
loop do
|
74
75
|
heartbeat
|
75
|
-
sleep
|
76
|
+
sleep BEAT_PAUSE
|
76
77
|
end
|
77
|
-
|
78
|
+
logger.info("Heartbeat stopping...")
|
78
79
|
end
|
79
80
|
|
80
81
|
def clear_heartbeat
|
82
|
+
flush_stats
|
83
|
+
|
81
84
|
# Remove record from Redis since we are shutting down.
|
82
85
|
# Note we don't stop the heartbeat thread; if the process
|
83
86
|
# doesn't actually exit, it'll reappear in the Web UI.
|
84
|
-
|
85
|
-
conn.pipelined do
|
86
|
-
|
87
|
-
|
87
|
+
redis do |conn|
|
88
|
+
conn.pipelined do |pipeline|
|
89
|
+
pipeline.srem("processes", [identity])
|
90
|
+
pipeline.unlink("#{identity}:work")
|
88
91
|
end
|
89
92
|
end
|
90
93
|
rescue
|
@@ -97,7 +100,7 @@ module Sidekiq
|
|
97
100
|
❤
|
98
101
|
end
|
99
102
|
|
100
|
-
def
|
103
|
+
def flush_stats
|
101
104
|
fails = Processor::FAILURE.reset
|
102
105
|
procd = Processor::PROCESSED.reset
|
103
106
|
return if fails + procd == 0
|
@@ -105,14 +108,14 @@ module Sidekiq
|
|
105
108
|
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
106
109
|
begin
|
107
110
|
Sidekiq.redis do |conn|
|
108
|
-
conn.pipelined do
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
conn.pipelined do |pipeline|
|
112
|
+
pipeline.incrby("stat:processed", procd)
|
113
|
+
pipeline.incrby("stat:processed:#{nowdate}", procd)
|
114
|
+
pipeline.expire("stat:processed:#{nowdate}", STATS_TTL)
|
115
|
+
|
116
|
+
pipeline.incrby("stat:failed", fails)
|
117
|
+
pipeline.incrby("stat:failed:#{nowdate}", fails)
|
118
|
+
pipeline.expire("stat:failed:#{nowdate}", STATS_TTL)
|
116
119
|
end
|
117
120
|
end
|
118
121
|
rescue => ex
|
@@ -121,7 +124,6 @@ module Sidekiq
|
|
121
124
|
Sidekiq.logger.warn("Unable to flush stats: #{ex}")
|
122
125
|
end
|
123
126
|
end
|
124
|
-
at_exit(&method(:flush_stats))
|
125
127
|
|
126
128
|
def ❤
|
127
129
|
key = identity
|
@@ -130,43 +132,55 @@ module Sidekiq
|
|
130
132
|
begin
|
131
133
|
fails = Processor::FAILURE.reset
|
132
134
|
procd = Processor::PROCESSED.reset
|
133
|
-
curstate = Processor::
|
135
|
+
curstate = Processor::WORK_STATE.dup
|
134
136
|
|
135
|
-
workers_key = "#{key}:workers"
|
136
137
|
nowdate = Time.now.utc.strftime("%Y-%m-%d")
|
137
138
|
|
138
|
-
|
139
|
-
conn.multi do
|
140
|
-
|
141
|
-
|
142
|
-
|
139
|
+
redis do |conn|
|
140
|
+
conn.multi do |transaction|
|
141
|
+
transaction.incrby("stat:processed", procd)
|
142
|
+
transaction.incrby("stat:processed:#{nowdate}", procd)
|
143
|
+
transaction.expire("stat:processed:#{nowdate}", STATS_TTL)
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
145
|
+
transaction.incrby("stat:failed", fails)
|
146
|
+
transaction.incrby("stat:failed:#{nowdate}", fails)
|
147
|
+
transaction.expire("stat:failed:#{nowdate}", STATS_TTL)
|
148
|
+
end
|
147
149
|
|
148
|
-
|
150
|
+
# work is the current set of executing jobs
|
151
|
+
work_key = "#{key}:work"
|
152
|
+
conn.pipelined do |transaction|
|
153
|
+
transaction.unlink(work_key)
|
149
154
|
curstate.each_pair do |tid, hash|
|
150
|
-
|
155
|
+
transaction.hset(work_key, tid, Sidekiq.dump_json(hash))
|
151
156
|
end
|
152
|
-
|
157
|
+
transaction.expire(work_key, 60)
|
153
158
|
end
|
154
159
|
end
|
155
160
|
|
156
|
-
|
161
|
+
rtt = check_rtt
|
157
162
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
163
|
+
fails = procd = 0
|
164
|
+
kb = memory_usage(::Process.pid)
|
165
|
+
|
166
|
+
_, exists, _, _, msg = redis { |conn|
|
167
|
+
conn.multi { |transaction|
|
168
|
+
transaction.sadd("processes", [key])
|
169
|
+
transaction.exists?(key)
|
170
|
+
transaction.hmset(key, "info", to_json,
|
171
|
+
"busy", curstate.size,
|
172
|
+
"beat", Time.now.to_f,
|
173
|
+
"rtt_us", rtt,
|
174
|
+
"quiet", @done.to_s,
|
175
|
+
"rss", kb)
|
176
|
+
transaction.expire(key, 60)
|
177
|
+
transaction.rpop("#{key}-signals")
|
165
178
|
}
|
166
179
|
}
|
167
180
|
|
168
181
|
# first heartbeat or recovering from an outage and need to reestablish our heartbeat
|
169
182
|
fire_event(:heartbeat) unless exists
|
183
|
+
fire_event(:beat, oneshot: false)
|
170
184
|
|
171
185
|
return unless msg
|
172
186
|
|
@@ -180,27 +194,74 @@ module Sidekiq
|
|
180
194
|
end
|
181
195
|
end
|
182
196
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
197
|
+
# We run the heartbeat every five seconds.
|
198
|
+
# Capture five samples of RTT, log a warning if each sample
|
199
|
+
# is above our warning threshold.
|
200
|
+
RTT_READINGS = RingBuffer.new(5)
|
201
|
+
RTT_WARNING_LEVEL = 50_000
|
202
|
+
|
203
|
+
def check_rtt
|
204
|
+
a = b = 0
|
205
|
+
redis do |x|
|
206
|
+
a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
207
|
+
x.ping
|
208
|
+
b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
|
209
|
+
end
|
210
|
+
rtt = b - a
|
211
|
+
RTT_READINGS << rtt
|
212
|
+
# Ideal RTT for Redis is < 1000µs
|
213
|
+
# Workable is < 10,000µs
|
214
|
+
# Log a warning if it's a disaster.
|
215
|
+
if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL }
|
216
|
+
logger.warn <<~EOM
|
217
|
+
Your Redis network connection is performing extremely poorly.
|
218
|
+
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
219
|
+
Ensure Redis is running in the same AZ or datacenter as Sidekiq.
|
220
|
+
If these values are close to 100,000, that means your Sidekiq process may be
|
221
|
+
CPU-saturated; reduce your concurrency and/or see https://github.com/mperham/sidekiq/discussions/5039
|
222
|
+
EOM
|
223
|
+
RTT_READINGS.reset
|
195
224
|
end
|
225
|
+
rtt
|
226
|
+
end
|
227
|
+
|
228
|
+
MEMORY_GRABBER = case RUBY_PLATFORM
|
229
|
+
when /linux/
|
230
|
+
->(pid) {
|
231
|
+
IO.readlines("/proc/#{$$}/status").each do |line|
|
232
|
+
next unless line.start_with?("VmRSS:")
|
233
|
+
break line.split[1].to_i
|
234
|
+
end
|
235
|
+
}
|
236
|
+
when /darwin|bsd/
|
237
|
+
->(pid) {
|
238
|
+
`ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i
|
239
|
+
}
|
240
|
+
else
|
241
|
+
->(pid) { 0 }
|
242
|
+
end
|
243
|
+
|
244
|
+
def memory_usage(pid)
|
245
|
+
MEMORY_GRABBER.call(pid)
|
246
|
+
end
|
247
|
+
|
248
|
+
def to_data
|
249
|
+
@data ||= {
|
250
|
+
"hostname" => hostname,
|
251
|
+
"started_at" => Time.now.to_f,
|
252
|
+
"pid" => ::Process.pid,
|
253
|
+
"tag" => @config[:tag] || "",
|
254
|
+
"concurrency" => @config[:concurrency],
|
255
|
+
"queues" => @config[:queues].uniq,
|
256
|
+
"labels" => @config[:labels],
|
257
|
+
"identity" => identity
|
258
|
+
}
|
196
259
|
end
|
197
260
|
|
198
261
|
def to_json
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
Sidekiq.dump_json(to_data)
|
203
|
-
end
|
262
|
+
# this data changes infrequently so dump it to a string
|
263
|
+
# now so we don't need to dump it every heartbeat.
|
264
|
+
@json ||= Sidekiq.dump_json(to_data)
|
204
265
|
end
|
205
266
|
end
|
206
267
|
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
|
+
current[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
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "sidekiq/util"
|
4
3
|
require "sidekiq/processor"
|
5
4
|
require "sidekiq/fetch"
|
6
5
|
require "set"
|
@@ -21,43 +20,37 @@ module Sidekiq
|
|
21
20
|
# the shutdown process. The other tasks are performed by other threads.
|
22
21
|
#
|
23
22
|
class Manager
|
24
|
-
include
|
23
|
+
include Sidekiq::Component
|
25
24
|
|
26
25
|
attr_reader :workers
|
27
|
-
attr_reader :options
|
28
26
|
|
29
27
|
def initialize(options = {})
|
28
|
+
@config = options
|
30
29
|
logger.debug { options.inspect }
|
31
|
-
@options = options
|
32
30
|
@count = options[:concurrency] || 10
|
33
31
|
raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
|
34
32
|
|
35
33
|
@done = false
|
36
34
|
@workers = Set.new
|
37
35
|
@count.times do
|
38
|
-
@workers << Processor.new(
|
36
|
+
@workers << Processor.new(@config, &method(:processor_result))
|
39
37
|
end
|
40
38
|
@plock = Mutex.new
|
41
39
|
end
|
42
40
|
|
43
41
|
def start
|
44
|
-
@workers.each
|
45
|
-
x.start
|
46
|
-
end
|
42
|
+
@workers.each(&:start)
|
47
43
|
end
|
48
44
|
|
49
45
|
def quiet
|
50
46
|
return if @done
|
51
47
|
@done = true
|
52
48
|
|
53
|
-
logger.info { "Terminating quiet
|
54
|
-
@workers.each
|
49
|
+
logger.info { "Terminating quiet threads" }
|
50
|
+
@workers.each(&:terminate)
|
55
51
|
fire_event(:quiet, reverse: true)
|
56
52
|
end
|
57
53
|
|
58
|
-
# hack for quicker development / testing environment #2774
|
59
|
-
PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
|
60
|
-
|
61
54
|
def stop(deadline)
|
62
55
|
quiet
|
63
56
|
fire_event(:shutdown, reverse: true)
|
@@ -68,29 +61,18 @@ module Sidekiq
|
|
68
61
|
sleep PAUSE_TIME
|
69
62
|
return if @workers.empty?
|
70
63
|
|
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
|
64
|
+
logger.info { "Pausing to allow jobs to finish..." }
|
65
|
+
wait_for(deadline) { @workers.empty? }
|
78
66
|
return if @workers.empty?
|
79
67
|
|
80
68
|
hard_shutdown
|
81
69
|
end
|
82
70
|
|
83
|
-
def
|
84
|
-
@plock.synchronize do
|
85
|
-
@workers.delete(processor)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def processor_died(processor, reason)
|
71
|
+
def processor_result(processor, reason = nil)
|
90
72
|
@plock.synchronize do
|
91
73
|
@workers.delete(processor)
|
92
74
|
unless @done
|
93
|
-
p = Processor.new(
|
75
|
+
p = Processor.new(@config, &method(:processor_result))
|
94
76
|
@workers << p
|
95
77
|
p.start
|
96
78
|
end
|
@@ -104,7 +86,7 @@ module Sidekiq
|
|
104
86
|
private
|
105
87
|
|
106
88
|
def hard_shutdown
|
107
|
-
# We've reached the timeout and we still have busy
|
89
|
+
# We've reached the timeout and we still have busy threads.
|
108
90
|
# They must die but their jobs shall live on.
|
109
91
|
cleanup = nil
|
110
92
|
@plock.synchronize do
|
@@ -114,22 +96,41 @@ module Sidekiq
|
|
114
96
|
if cleanup.size > 0
|
115
97
|
jobs = cleanup.map { |p| p.job }.compact
|
116
98
|
|
117
|
-
logger.warn { "Terminating #{cleanup.size} busy
|
118
|
-
logger.
|
99
|
+
logger.warn { "Terminating #{cleanup.size} busy threads" }
|
100
|
+
logger.debug { "Jobs still in progress #{jobs.inspect}" }
|
119
101
|
|
120
102
|
# Re-enqueue unfinished jobs
|
121
103
|
# NOTE: You may notice that we may push a job back to redis before
|
122
|
-
# the
|
104
|
+
# the thread is terminated. This is ok because Sidekiq's
|
123
105
|
# contract says that jobs are run AT LEAST once. Process termination
|
124
106
|
# is delayed until we're certain the jobs are back in Redis because
|
125
107
|
# it is worse to lose a job than to run it twice.
|
126
|
-
strategy = @
|
127
|
-
strategy.bulk_requeue(jobs, @
|
108
|
+
strategy = @config[:fetch]
|
109
|
+
strategy.bulk_requeue(jobs, @config)
|
128
110
|
end
|
129
111
|
|
130
112
|
cleanup.each do |processor|
|
131
113
|
processor.kill
|
132
114
|
end
|
115
|
+
|
116
|
+
# when this method returns, we immediately call `exit` which may not give
|
117
|
+
# the remaining threads time to run `ensure` blocks, etc. We pause here up
|
118
|
+
# to 3 seconds to give threads a minimal amount of time to run `ensure` blocks.
|
119
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + 3
|
120
|
+
wait_for(deadline) { @workers.empty? }
|
121
|
+
end
|
122
|
+
|
123
|
+
# hack for quicker development / testing environment #2774
|
124
|
+
PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5
|
125
|
+
|
126
|
+
# Wait for the orblock to be true or the deadline passed.
|
127
|
+
def wait_for(deadline, &condblock)
|
128
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
129
|
+
while remaining > PAUSE_TIME
|
130
|
+
return if condblock.call
|
131
|
+
sleep PAUSE_TIME
|
132
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
133
|
+
end
|
133
134
|
end
|
134
135
|
end
|
135
136
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "sidekiq"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
# This file is designed to be required within the user's
|
5
|
+
# deployment script; it should need a bare minimum of dependencies.
|
6
|
+
#
|
7
|
+
# require "sidekiq/metrics/deploy"
|
8
|
+
# gitdesc = `git log -1 --format="%h %s"`.strip
|
9
|
+
# d = Sidekiq::Metrics::Deploy.new
|
10
|
+
# d.mark(label: gitdesc)
|
11
|
+
#
|
12
|
+
# Note that you cannot mark more than once per minute. This is a feature, not a bug.
|
13
|
+
module Sidekiq
|
14
|
+
module Metrics
|
15
|
+
class Deploy
|
16
|
+
MARK_TTL = 90 * 24 * 60 * 60 # 90 days
|
17
|
+
|
18
|
+
def initialize(pool = Sidekiq.redis_pool)
|
19
|
+
@pool = pool
|
20
|
+
end
|
21
|
+
|
22
|
+
def mark(at: Time.now, label: "")
|
23
|
+
# we need to round the timestamp so that we gracefully
|
24
|
+
# handle an excepted common error in marking deploys:
|
25
|
+
# having every process mark its deploy, leading
|
26
|
+
# to N marks for each deploy. Instead we round the time
|
27
|
+
# to the minute so that multple marks within that minute
|
28
|
+
# will all naturally rollup into one mark per minute.
|
29
|
+
whence = at.utc
|
30
|
+
floor = Time.utc(whence.year, whence.month, whence.mday, whence.hour, whence.min, 0)
|
31
|
+
datecode = floor.strftime("%Y%m%d")
|
32
|
+
key = "#{datecode}-marks"
|
33
|
+
@pool.with do |c|
|
34
|
+
c.pipelined do |pipe|
|
35
|
+
pipe.hsetnx(key, floor.iso8601, label)
|
36
|
+
pipe.expire(key, MARK_TTL)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch(date = Time.now.utc.to_date)
|
42
|
+
datecode = date.strftime("%Y%m%d")
|
43
|
+
@pool.with { |c| c.hgetall("#{datecode}-marks") }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|