sidekiq 5.2.1 → 5.2.8
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 +5 -5
- data/.circleci/config.yml +61 -0
- data/.gitignore +2 -0
- data/.travis.yml +2 -5
- data/COMM-LICENSE +11 -9
- data/Changes.md +51 -0
- data/Ent-Changes.md +18 -1
- data/Gemfile +15 -6
- data/Pro-Changes.md +20 -0
- data/README.md +1 -1
- data/Rakefile +2 -1
- data/bin/sidekiqctl +13 -92
- data/bin/sidekiqload +1 -1
- data/lib/sidekiq.rb +4 -2
- data/lib/sidekiq/api.rb +6 -0
- data/lib/sidekiq/cli.rb +58 -58
- data/lib/sidekiq/ctl.rb +221 -0
- data/lib/sidekiq/job_logger.rb +2 -2
- data/lib/sidekiq/job_retry.rb +33 -12
- data/lib/sidekiq/launcher.rb +1 -1
- data/lib/sidekiq/manager.rb +3 -3
- data/lib/sidekiq/middleware/server/active_record.rb +1 -1
- data/lib/sidekiq/processor.rb +54 -21
- data/lib/sidekiq/rails.rb +2 -1
- data/lib/sidekiq/redis_connection.rb +20 -1
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +7 -1
- data/lib/sidekiq/web/helpers.rb +11 -4
- data/lib/sidekiq/worker.rb +24 -8
- data/sidekiq.gemspec +2 -4
- data/web/assets/javascripts/dashboard.js +15 -5
- data/web/assets/stylesheets/application.css +35 -2
- data/web/assets/stylesheets/bootstrap.css +1 -1
- data/web/locales/ar.yml +1 -0
- data/web/locales/en.yml +1 -0
- data/web/views/_nav.erb +3 -17
- data/web/views/queue.erb +1 -0
- data/web/views/queues.erb +1 -1
- data/web/views/retries.erb +4 -0
- metadata +19 -4
data/lib/sidekiq/job_logger.rb
CHANGED
@@ -3,7 +3,7 @@ module Sidekiq
|
|
3
3
|
class JobLogger
|
4
4
|
|
5
5
|
def call(item, queue)
|
6
|
-
start =
|
6
|
+
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
7
7
|
logger.info("start")
|
8
8
|
yield
|
9
9
|
logger.info("done: #{elapsed(start)} sec")
|
@@ -15,7 +15,7 @@ module Sidekiq
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def elapsed(start)
|
18
|
-
(
|
18
|
+
(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start).round(3)
|
19
19
|
end
|
20
20
|
|
21
21
|
def logger
|
data/lib/sidekiq/job_retry.rb
CHANGED
@@ -56,7 +56,8 @@ module Sidekiq
|
|
56
56
|
# end
|
57
57
|
#
|
58
58
|
class JobRetry
|
59
|
-
class
|
59
|
+
class Handled < ::RuntimeError; end
|
60
|
+
class Skip < Handled; end
|
60
61
|
|
61
62
|
include Sidekiq::Util
|
62
63
|
|
@@ -71,7 +72,7 @@ module Sidekiq
|
|
71
72
|
# require the worker to be instantiated.
|
72
73
|
def global(msg, queue)
|
73
74
|
yield
|
74
|
-
rescue
|
75
|
+
rescue Handled => ex
|
75
76
|
raise ex
|
76
77
|
rescue Sidekiq::Shutdown => ey
|
77
78
|
# ignore, will be pushed back onto queue during hard_shutdown
|
@@ -80,9 +81,19 @@ module Sidekiq
|
|
80
81
|
# ignore, will be pushed back onto queue during hard_shutdown
|
81
82
|
raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
|
82
83
|
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
if msg['retry']
|
85
|
+
attempt_retry(nil, msg, queue, e)
|
86
|
+
else
|
87
|
+
Sidekiq.death_handlers.each do |handler|
|
88
|
+
begin
|
89
|
+
handler.call(msg, e)
|
90
|
+
rescue => handler_ex
|
91
|
+
handle_exception(handler_ex, { context: "Error calling death handler", job: msg })
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
raise Handled
|
86
97
|
end
|
87
98
|
|
88
99
|
|
@@ -96,7 +107,7 @@ module Sidekiq
|
|
96
107
|
# calling the handle_exception handlers.
|
97
108
|
def local(worker, msg, queue)
|
98
109
|
yield
|
99
|
-
rescue
|
110
|
+
rescue Handled => ex
|
100
111
|
raise ex
|
101
112
|
rescue Sidekiq::Shutdown => ey
|
102
113
|
# ignore, will be pushed back onto queue during hard_shutdown
|
@@ -130,9 +141,7 @@ module Sidekiq
|
|
130
141
|
queue
|
131
142
|
end
|
132
143
|
|
133
|
-
|
134
|
-
# that won't convert to JSON.
|
135
|
-
m = exception.message.to_s[0, 10_000]
|
144
|
+
m = exception_message(exception)
|
136
145
|
if m.respond_to?(:scrub!)
|
137
146
|
m.force_encoding("utf-8")
|
138
147
|
m.scrub!
|
@@ -158,7 +167,8 @@ module Sidekiq
|
|
158
167
|
|
159
168
|
if count < max_retry_attempts
|
160
169
|
delay = delay_for(worker, count, exception)
|
161
|
-
|
170
|
+
# Logging here can break retries if the logging device raises ENOSPC #3979
|
171
|
+
#logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
162
172
|
retry_at = Time.now.to_f + delay
|
163
173
|
payload = Sidekiq.dump_json(msg)
|
164
174
|
Sidekiq.redis do |conn|
|
@@ -171,7 +181,6 @@ module Sidekiq
|
|
171
181
|
end
|
172
182
|
|
173
183
|
def retries_exhausted(worker, msg, exception)
|
174
|
-
logger.debug { "Retries exhausted for job" }
|
175
184
|
begin
|
176
185
|
block = worker && worker.sidekiq_retries_exhausted_block
|
177
186
|
block.call(msg, exception) if block
|
@@ -191,7 +200,7 @@ module Sidekiq
|
|
191
200
|
end
|
192
201
|
|
193
202
|
def send_to_morgue(msg)
|
194
|
-
|
203
|
+
logger.info { "Adding dead #{msg['class']} job #{msg['jid']}" }
|
195
204
|
payload = Sidekiq.dump_json(msg)
|
196
205
|
DeadSet.new.kill(payload, notify_failure: false)
|
197
206
|
end
|
@@ -237,5 +246,17 @@ module Sidekiq
|
|
237
246
|
exception_caused_by_shutdown?(e.cause, checked_causes)
|
238
247
|
end
|
239
248
|
|
249
|
+
# Extract message from exception.
|
250
|
+
# Set a default if the message raises an error
|
251
|
+
def exception_message(exception)
|
252
|
+
begin
|
253
|
+
# App code can stuff all sorts of crazy binary data into the error message
|
254
|
+
# that won't convert to JSON.
|
255
|
+
exception.message.to_s[0, 10_000]
|
256
|
+
rescue
|
257
|
+
"!!! ERROR MESSAGE THREW AN ERROR !!!".dup
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
240
261
|
end
|
241
262
|
end
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -40,7 +40,7 @@ module Sidekiq
|
|
40
40
|
# return until all work is complete and cleaned up.
|
41
41
|
# It can take up to the timeout to complete.
|
42
42
|
def stop
|
43
|
-
deadline =
|
43
|
+
deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @options[:timeout]
|
44
44
|
|
45
45
|
@done = true
|
46
46
|
@manager.quiet
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -30,7 +30,7 @@ module Sidekiq
|
|
30
30
|
def initialize(options={})
|
31
31
|
logger.debug { options.inspect }
|
32
32
|
@options = options
|
33
|
-
@count = options[:concurrency] ||
|
33
|
+
@count = options[:concurrency] || 10
|
34
34
|
raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
|
35
35
|
|
36
36
|
@done = false
|
@@ -70,11 +70,11 @@ module Sidekiq
|
|
70
70
|
return if @workers.empty?
|
71
71
|
|
72
72
|
logger.info { "Pausing to allow workers to finish..." }
|
73
|
-
remaining = deadline -
|
73
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
74
74
|
while remaining > PAUSE_TIME
|
75
75
|
return if @workers.empty?
|
76
76
|
sleep PAUSE_TIME
|
77
|
-
remaining = deadline -
|
77
|
+
remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
78
78
|
end
|
79
79
|
return if @workers.empty?
|
80
80
|
|
@@ -7,7 +7,7 @@ module Sidekiq
|
|
7
7
|
def initialize
|
8
8
|
# With Rails 5+ we must use the Reloader **always**.
|
9
9
|
# The reloader handles code loading and db connection management.
|
10
|
-
if defined?(::Rails) && ::Rails::VERSION::MAJOR >= 5
|
10
|
+
if defined?(::Rails) && defined?(::Rails::VERSION) && ::Rails::VERSION::MAJOR >= 5
|
11
11
|
raise ArgumentError, "Rails 5 no longer needs or uses the ActiveRecord middleware."
|
12
12
|
end
|
13
13
|
end
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -87,7 +87,7 @@ module Sidekiq
|
|
87
87
|
def get_one
|
88
88
|
begin
|
89
89
|
work = @strategy.retrieve_work
|
90
|
-
(logger.info { "Redis is online, #{
|
90
|
+
(logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }; @down = nil) if @down
|
91
91
|
work
|
92
92
|
rescue Sidekiq::Shutdown
|
93
93
|
rescue => ex
|
@@ -107,7 +107,7 @@ module Sidekiq
|
|
107
107
|
|
108
108
|
def handle_fetch_exception(ex)
|
109
109
|
if !@down
|
110
|
-
@down =
|
110
|
+
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
111
111
|
logger.error("Error fetching job: #{ex}")
|
112
112
|
handle_exception(ex)
|
113
113
|
end
|
@@ -147,21 +147,19 @@ module Sidekiq
|
|
147
147
|
jobstr = work.job
|
148
148
|
queue = work.queue_name
|
149
149
|
|
150
|
-
|
150
|
+
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
151
|
+
job_hash = nil
|
151
152
|
begin
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
DeadSet.new.kill(jobstr, notify_failure: false)
|
160
|
-
ack = true
|
161
|
-
raise
|
162
|
-
end
|
153
|
+
job_hash = Sidekiq.load_json(jobstr)
|
154
|
+
rescue => ex
|
155
|
+
handle_exception(ex, { :context => "Invalid JSON for job", :jobstr => jobstr })
|
156
|
+
# we can't notify because the job isn't a valid hash payload.
|
157
|
+
DeadSet.new.kill(jobstr, notify_failure: false)
|
158
|
+
return work.acknowledge
|
159
|
+
end
|
163
160
|
|
164
|
-
|
161
|
+
ack = true
|
162
|
+
begin
|
165
163
|
dispatch(job_hash, queue) do |worker|
|
166
164
|
Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
|
167
165
|
execute_job(worker, cloned(job_hash['args']))
|
@@ -172,10 +170,19 @@ module Sidekiq
|
|
172
170
|
# within the timeout. Don't acknowledge the work since
|
173
171
|
# we didn't properly finish it.
|
174
172
|
ack = false
|
175
|
-
rescue
|
176
|
-
|
173
|
+
rescue Sidekiq::JobRetry::Handled => h
|
174
|
+
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
175
|
+
# signals that we created a retry successfully. We can acknowlege the job.
|
176
|
+
e = h.cause ? h.cause : h
|
177
177
|
handle_exception(e, { :context => "Job raised exception", :job => job_hash, :jobstr => jobstr })
|
178
178
|
raise e
|
179
|
+
rescue Exception => ex
|
180
|
+
# Unexpected error! This is very bad and indicates an exception that got past
|
181
|
+
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
182
|
+
# so it can be rescued when using Sidekiq Pro.
|
183
|
+
ack = false
|
184
|
+
handle_exception(ex, { :context => "Internal exception!", :job => job_hash, :jobstr => jobstr })
|
185
|
+
raise e
|
179
186
|
ensure
|
180
187
|
work.acknowledge if ack
|
181
188
|
end
|
@@ -203,15 +210,41 @@ module Sidekiq
|
|
203
210
|
end
|
204
211
|
end
|
205
212
|
|
213
|
+
# jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
|
214
|
+
class SharedWorkerState
|
215
|
+
def initialize
|
216
|
+
@worker_state = {}
|
217
|
+
@lock = Mutex.new
|
218
|
+
end
|
219
|
+
|
220
|
+
def set(tid, hash)
|
221
|
+
@lock.synchronize { @worker_state[tid] = hash }
|
222
|
+
end
|
223
|
+
|
224
|
+
def delete(tid)
|
225
|
+
@lock.synchronize { @worker_state.delete(tid) }
|
226
|
+
end
|
227
|
+
|
228
|
+
def dup
|
229
|
+
@lock.synchronize { @worker_state.dup }
|
230
|
+
end
|
231
|
+
|
232
|
+
def size
|
233
|
+
@lock.synchronize { @worker_state.size }
|
234
|
+
end
|
235
|
+
|
236
|
+
def clear
|
237
|
+
@lock.synchronize { @worker_state.clear }
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
206
241
|
PROCESSED = Counter.new
|
207
242
|
FAILURE = Counter.new
|
208
|
-
|
209
|
-
# its own unique key/value, there's no thread-safety issue AFAIK.
|
210
|
-
WORKER_STATE = {}
|
243
|
+
WORKER_STATE = SharedWorkerState.new
|
211
244
|
|
212
245
|
def stats(job_hash, queue)
|
213
246
|
tid = Sidekiq::Logging.tid
|
214
|
-
WORKER_STATE
|
247
|
+
WORKER_STATE.set(tid, {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i })
|
215
248
|
|
216
249
|
begin
|
217
250
|
yield
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Sidekiq
|
3
4
|
class Rails < ::Rails::Engine
|
4
5
|
# We need to setup this up before any application configuration which might
|
@@ -54,4 +55,4 @@ if defined?(::Rails) && ::Rails::VERSION::MAJOR < 4
|
|
54
55
|
$stderr.puts("**************************************************")
|
55
56
|
$stderr.puts("⛔️ WARNING: Sidekiq server is no longer supported by Rails 3.2 - please ensure your server/workers are updated")
|
56
57
|
$stderr.puts("**************************************************")
|
57
|
-
end
|
58
|
+
end
|
@@ -78,7 +78,7 @@ module Sidekiq
|
|
78
78
|
opts.delete(:network_timeout)
|
79
79
|
end
|
80
80
|
|
81
|
-
opts[:driver] ||= 'ruby'
|
81
|
+
opts[:driver] ||= Redis::Connection.drivers.last || 'ruby'
|
82
82
|
|
83
83
|
# Issue #3303, redis-rb will silently retry an operation.
|
84
84
|
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
@@ -115,6 +115,25 @@ module Sidekiq
|
|
115
115
|
# REDIS_PROVIDER=MY_REDIS_URL
|
116
116
|
# and Sidekiq will find your custom URL variable with no custom
|
117
117
|
# initialization code at all.
|
118
|
+
p = ENV['REDIS_PROVIDER']
|
119
|
+
if p && p =~ /\:/
|
120
|
+
Sidekiq.logger.error <<-EOM
|
121
|
+
|
122
|
+
#################################################################################
|
123
|
+
|
124
|
+
REDIS_PROVIDER should be set to the **name** of the variable which contains the Redis URL, not a URL itself.
|
125
|
+
Platforms like Heroku sell addons that publish a *_URL variable. You tell Sidekiq with REDIS_PROVIDER, e.g.:
|
126
|
+
|
127
|
+
REDIS_PROVIDER=REDISTOGO_URL
|
128
|
+
REDISTOGO_URL=redis://somehost.example.com:6379/4
|
129
|
+
|
130
|
+
Use REDIS_URL if you wish to point Sidekiq to a URL directly.
|
131
|
+
|
132
|
+
This configuration error will crash starting in Sidekiq 5.3.
|
133
|
+
|
134
|
+
#################################################################################
|
135
|
+
EOM
|
136
|
+
end
|
118
137
|
ENV[
|
119
138
|
ENV['REDIS_PROVIDER'] || 'REDIS_URL'
|
120
139
|
]
|
data/lib/sidekiq/version.rb
CHANGED
@@ -17,7 +17,7 @@ module Sidekiq
|
|
17
17
|
"manifest-src 'self'",
|
18
18
|
"media-src 'self'",
|
19
19
|
"object-src 'none'",
|
20
|
-
"script-src 'self' https: http:",
|
20
|
+
"script-src 'self' https: http: 'unsafe-inline'",
|
21
21
|
"style-src 'self' https: http: 'unsafe-inline'",
|
22
22
|
"worker-src 'self'",
|
23
23
|
"base-uri 'self'"
|
@@ -196,6 +196,12 @@ module Sidekiq
|
|
196
196
|
redirect "#{root_path}retries"
|
197
197
|
end
|
198
198
|
|
199
|
+
post "/retries/all/kill" do
|
200
|
+
Sidekiq::RetrySet.new.kill_all
|
201
|
+
|
202
|
+
redirect "#{root_path}retries"
|
203
|
+
end
|
204
|
+
|
199
205
|
post "/retries/:key" do
|
200
206
|
job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
|
201
207
|
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -121,7 +121,7 @@ module Sidekiq
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def t(msg, options={})
|
124
|
-
string = get_locale[msg] || msg
|
124
|
+
string = get_locale[msg] || strings('en')[msg] || msg
|
125
125
|
if options.empty?
|
126
126
|
string
|
127
127
|
else
|
@@ -207,9 +207,16 @@ module Sidekiq
|
|
207
207
|
end
|
208
208
|
|
209
209
|
def display_args(args, truncate_after_chars = 2000)
|
210
|
-
args
|
211
|
-
|
212
|
-
|
210
|
+
return "Invalid job payload, args is nil" if args == nil
|
211
|
+
return "Invalid job payload, args must be an Array, not #{args.class.name}" if !args.is_a?(Array)
|
212
|
+
|
213
|
+
begin
|
214
|
+
args.map do |arg|
|
215
|
+
h(truncate(to_display(arg), truncate_after_chars))
|
216
|
+
end.join(", ")
|
217
|
+
rescue
|
218
|
+
"Illegal job arguments: #{h args.inspect}"
|
219
|
+
end
|
213
220
|
end
|
214
221
|
|
215
222
|
def csrf_tag
|
data/lib/sidekiq/worker.rb
CHANGED
@@ -7,13 +7,13 @@ module Sidekiq
|
|
7
7
|
# Include this module in your worker class and you can easily create
|
8
8
|
# asynchronous jobs:
|
9
9
|
#
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# class HardWorker
|
11
|
+
# include Sidekiq::Worker
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
13
|
+
# def perform(*args)
|
14
|
+
# # do some work
|
15
|
+
# end
|
15
16
|
# end
|
16
|
-
# end
|
17
17
|
#
|
18
18
|
# Then in your Rails app, you can do this:
|
19
19
|
#
|
@@ -46,6 +46,11 @@ module Sidekiq
|
|
46
46
|
@opts = opts
|
47
47
|
end
|
48
48
|
|
49
|
+
def set(options)
|
50
|
+
@opts.merge!(options)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
49
54
|
def perform_async(*args)
|
50
55
|
@klass.client_push(@opts.merge('args' => args, 'class' => @klass))
|
51
56
|
end
|
@@ -66,6 +71,7 @@ module Sidekiq
|
|
66
71
|
end
|
67
72
|
|
68
73
|
module ClassMethods
|
74
|
+
ACCESSOR_MUTEX = Mutex.new
|
69
75
|
|
70
76
|
def delay(*args)
|
71
77
|
raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
|
@@ -148,10 +154,18 @@ module Sidekiq
|
|
148
154
|
instance_writer = true
|
149
155
|
|
150
156
|
attrs.each do |name|
|
157
|
+
synchronized_getter = "__synchronized_#{name}"
|
158
|
+
|
151
159
|
singleton_class.instance_eval do
|
152
160
|
undef_method(name) if method_defined?(name) || private_method_defined?(name)
|
153
161
|
end
|
154
|
-
|
162
|
+
|
163
|
+
define_singleton_method(synchronized_getter) { nil }
|
164
|
+
singleton_class.class_eval do
|
165
|
+
private(synchronized_getter)
|
166
|
+
end
|
167
|
+
|
168
|
+
define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } }
|
155
169
|
|
156
170
|
ivar = "@#{name}"
|
157
171
|
|
@@ -161,8 +175,10 @@ module Sidekiq
|
|
161
175
|
end
|
162
176
|
define_singleton_method("#{name}=") do |val|
|
163
177
|
singleton_class.class_eval do
|
164
|
-
|
165
|
-
|
178
|
+
ACCESSOR_MUTEX.synchronize do
|
179
|
+
undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
|
180
|
+
define_method(synchronized_getter) { val }
|
181
|
+
end
|
166
182
|
end
|
167
183
|
|
168
184
|
if singleton_class?
|