sidekiq 4.2.10 → 5.0.0
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/.gitignore +1 -0
- data/5.0-Upgrade.md +56 -0
- data/Changes.md +24 -1
- data/Ent-Changes.md +3 -2
- data/Pro-Changes.md +6 -2
- data/README.md +2 -2
- data/bin/sidekiqctl +1 -1
- data/bin/sidekiqload +3 -8
- data/lib/sidekiq/api.rb +33 -14
- data/lib/sidekiq/cli.rb +12 -5
- data/lib/sidekiq/client.rb +15 -13
- data/lib/sidekiq/delay.rb +21 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
- data/lib/sidekiq/job_logger.rb +27 -0
- data/lib/sidekiq/job_retry.rb +235 -0
- data/lib/sidekiq/launcher.rb +1 -7
- data/lib/sidekiq/middleware/server/active_record.rb +9 -0
- data/lib/sidekiq/processor.rb +68 -31
- data/lib/sidekiq/rails.rb +2 -65
- data/lib/sidekiq/redis_connection.rb +1 -1
- data/lib/sidekiq/testing.rb +1 -1
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +0 -4
- data/lib/sidekiq/web/application.rb +6 -11
- data/lib/sidekiq/web/helpers.rb +9 -1
- data/lib/sidekiq/worker.rb +34 -11
- data/lib/sidekiq.rb +4 -13
- data/sidekiq.gemspec +1 -1
- data/web/assets/javascripts/dashboard.js +10 -12
- data/web/assets/stylesheets/application-rtl.css +246 -0
- data/web/assets/stylesheets/application.css +336 -4
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/locales/ar.yml +80 -0
- data/web/locales/fa.yml +1 -0
- data/web/locales/he.yml +79 -0
- data/web/locales/ur.yml +80 -0
- data/web/views/_footer.erb +1 -1
- data/web/views/_nav.erb +1 -1
- data/web/views/_paging.erb +1 -1
- data/web/views/busy.erb +4 -4
- data/web/views/dashboard.erb +1 -1
- data/web/views/layout.erb +10 -1
- data/web/views/morgue.erb +4 -4
- data/web/views/queue.erb +7 -7
- data/web/views/retries.erb +5 -5
- data/web/views/scheduled.erb +2 -2
- metadata +15 -8
- data/lib/sidekiq/middleware/server/logging.rb +0 -31
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'sidekiq/scheduled'
|
2
|
+
require 'sidekiq/api'
|
3
|
+
|
4
|
+
module Sidekiq
|
5
|
+
##
|
6
|
+
# Automatically retry jobs that fail in Sidekiq.
|
7
|
+
# Sidekiq's retry support assumes a typical development lifecycle:
|
8
|
+
#
|
9
|
+
# 0. Push some code changes with a bug in it.
|
10
|
+
# 1. Bug causes job processing to fail, Sidekiq's middleware captures
|
11
|
+
# the job and pushes it onto a retry queue.
|
12
|
+
# 2. Sidekiq retries jobs in the retry queue multiple times with
|
13
|
+
# an exponential delay, the job continues to fail.
|
14
|
+
# 3. After a few days, a developer deploys a fix. The job is
|
15
|
+
# reprocessed successfully.
|
16
|
+
# 4. Once retries are exhausted, Sidekiq will give up and move the
|
17
|
+
# job to the Dead Job Queue (aka morgue) where it must be dealt with
|
18
|
+
# manually in the Web UI.
|
19
|
+
# 5. After 6 months on the DJQ, Sidekiq will discard the job.
|
20
|
+
#
|
21
|
+
# A job looks like:
|
22
|
+
#
|
23
|
+
# { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'], 'retry' => true }
|
24
|
+
#
|
25
|
+
# The 'retry' option also accepts a number (in place of 'true'):
|
26
|
+
#
|
27
|
+
# { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'], 'retry' => 5 }
|
28
|
+
#
|
29
|
+
# The job will be retried this number of times before giving up. (If simply
|
30
|
+
# 'true', Sidekiq retries 25 times)
|
31
|
+
#
|
32
|
+
# We'll add a bit more data to the job to support retries:
|
33
|
+
#
|
34
|
+
# * 'queue' - the queue to use
|
35
|
+
# * 'retry_count' - number of times we've retried so far.
|
36
|
+
# * 'error_message' - the message from the exception
|
37
|
+
# * 'error_class' - the exception class
|
38
|
+
# * 'failed_at' - the first time it failed
|
39
|
+
# * 'retried_at' - the last time it was retried
|
40
|
+
# * 'backtrace' - the number of lines of error backtrace to store
|
41
|
+
#
|
42
|
+
# We don't store the backtrace by default as that can add a lot of overhead
|
43
|
+
# to the job and everyone is using an error service, right?
|
44
|
+
#
|
45
|
+
# The default number of retries is 25 which works out to about 3 weeks
|
46
|
+
# You can change the default maximum number of retries in your initializer:
|
47
|
+
#
|
48
|
+
# Sidekiq.options[:max_retries] = 7
|
49
|
+
#
|
50
|
+
# or limit the number of retries for a particular worker with:
|
51
|
+
#
|
52
|
+
# class MyWorker
|
53
|
+
# include Sidekiq::Worker
|
54
|
+
# sidekiq_options :retry => 10
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
class JobRetry
|
58
|
+
class Skip < ::RuntimeError; end
|
59
|
+
|
60
|
+
include Sidekiq::Util
|
61
|
+
|
62
|
+
DEFAULT_MAX_RETRY_ATTEMPTS = 25
|
63
|
+
|
64
|
+
def initialize(options = {})
|
65
|
+
@max_retries = Sidekiq.options.merge(options).fetch(:max_retries, DEFAULT_MAX_RETRY_ATTEMPTS)
|
66
|
+
end
|
67
|
+
|
68
|
+
# The global retry handler requires only the barest of data.
|
69
|
+
# We want to be able to retry as much as possible so we don't
|
70
|
+
# require the worker to be instantiated.
|
71
|
+
def global(msg, queue)
|
72
|
+
yield
|
73
|
+
rescue Skip => ex
|
74
|
+
raise ex
|
75
|
+
rescue Sidekiq::Shutdown => ey
|
76
|
+
# ignore, will be pushed back onto queue during hard_shutdown
|
77
|
+
raise ey
|
78
|
+
rescue Exception => e
|
79
|
+
# ignore, will be pushed back onto queue during hard_shutdown
|
80
|
+
raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
|
81
|
+
|
82
|
+
raise e unless msg['retry']
|
83
|
+
attempt_retry(nil, msg, queue, e)
|
84
|
+
raise e
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# The local retry support means that any errors that occur within
|
89
|
+
# this block can be associated with the given worker instance.
|
90
|
+
# This is required to support the `sidekiq_retries_exhausted` block.
|
91
|
+
#
|
92
|
+
# Note that any exception from the block is wrapped in the Skip
|
93
|
+
# exception so the global block does not reprocess the error. The
|
94
|
+
# Skip exception is unwrapped within Sidekiq::Processor#process before
|
95
|
+
# calling the handle_exception handlers.
|
96
|
+
def local(worker, msg, queue)
|
97
|
+
yield
|
98
|
+
rescue Skip => ex
|
99
|
+
raise ex
|
100
|
+
rescue Sidekiq::Shutdown => ey
|
101
|
+
# ignore, will be pushed back onto queue during hard_shutdown
|
102
|
+
raise ey
|
103
|
+
rescue Exception => e
|
104
|
+
# ignore, will be pushed back onto queue during hard_shutdown
|
105
|
+
raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
|
106
|
+
|
107
|
+
if msg['retry'] == nil
|
108
|
+
msg['retry'] = worker.class.get_sidekiq_options['retry']
|
109
|
+
end
|
110
|
+
|
111
|
+
raise e unless msg['retry']
|
112
|
+
attempt_retry(worker, msg, queue, e)
|
113
|
+
# We've handled this error associated with this job, don't
|
114
|
+
# need to handle it at the global level
|
115
|
+
raise Skip
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# Note that +worker+ can be nil here if an error is raised before we can
|
121
|
+
# instantiate the worker instance. All access must be guarded and
|
122
|
+
# best effort.
|
123
|
+
def attempt_retry(worker, msg, queue, exception)
|
124
|
+
max_retry_attempts = retry_attempts_from(msg['retry'], @max_retries)
|
125
|
+
|
126
|
+
msg['queue'] = if msg['retry_queue']
|
127
|
+
msg['retry_queue']
|
128
|
+
else
|
129
|
+
queue
|
130
|
+
end
|
131
|
+
|
132
|
+
# App code can stuff all sorts of crazy binary data into the error message
|
133
|
+
# that won't convert to JSON.
|
134
|
+
m = exception.message.to_s[0, 10_000]
|
135
|
+
if m.respond_to?(:scrub!)
|
136
|
+
m.force_encoding("utf-8")
|
137
|
+
m.scrub!
|
138
|
+
end
|
139
|
+
|
140
|
+
msg['error_message'] = m
|
141
|
+
msg['error_class'] = exception.class.name
|
142
|
+
count = if msg['retry_count']
|
143
|
+
msg['retried_at'] = Time.now.to_f
|
144
|
+
msg['retry_count'] += 1
|
145
|
+
else
|
146
|
+
msg['failed_at'] = Time.now.to_f
|
147
|
+
msg['retry_count'] = 0
|
148
|
+
end
|
149
|
+
|
150
|
+
if msg['backtrace'] == true
|
151
|
+
msg['error_backtrace'] = exception.backtrace
|
152
|
+
elsif !msg['backtrace']
|
153
|
+
# do nothing
|
154
|
+
elsif msg['backtrace'].to_i != 0
|
155
|
+
msg['error_backtrace'] = exception.backtrace[0...msg['backtrace'].to_i]
|
156
|
+
end
|
157
|
+
|
158
|
+
if count < max_retry_attempts
|
159
|
+
delay = delay_for(worker, count, exception)
|
160
|
+
logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
161
|
+
retry_at = Time.now.to_f + delay
|
162
|
+
payload = Sidekiq.dump_json(msg)
|
163
|
+
Sidekiq.redis do |conn|
|
164
|
+
conn.zadd('retry', retry_at.to_s, payload)
|
165
|
+
end
|
166
|
+
else
|
167
|
+
# Goodbye dear message, you (re)tried your best I'm sure.
|
168
|
+
retries_exhausted(worker, msg, exception)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def retries_exhausted(worker, msg, exception)
|
173
|
+
logger.debug { "Retries exhausted for job" }
|
174
|
+
begin
|
175
|
+
block = worker && worker.sidekiq_retries_exhausted_block || Sidekiq.default_retries_exhausted
|
176
|
+
block.call(msg, exception) if block
|
177
|
+
rescue => e
|
178
|
+
handle_exception(e, { context: "Error calling retries_exhausted for #{msg['class']}", job: msg })
|
179
|
+
end
|
180
|
+
|
181
|
+
send_to_morgue(msg) unless msg['dead'] == false
|
182
|
+
end
|
183
|
+
|
184
|
+
def send_to_morgue(msg)
|
185
|
+
Sidekiq.logger.info { "Adding dead #{msg['class']} job #{msg['jid']}" }
|
186
|
+
payload = Sidekiq.dump_json(msg)
|
187
|
+
now = Time.now.to_f
|
188
|
+
Sidekiq.redis do |conn|
|
189
|
+
conn.multi do
|
190
|
+
conn.zadd('dead', now, payload)
|
191
|
+
conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout)
|
192
|
+
conn.zremrangebyrank('dead', 0, -DeadSet.max_jobs)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def retry_attempts_from(msg_retry, default)
|
198
|
+
if msg_retry.is_a?(Integer)
|
199
|
+
msg_retry
|
200
|
+
else
|
201
|
+
default
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def delay_for(worker, count, exception)
|
206
|
+
worker && worker.sidekiq_retry_in_block? && retry_in(worker, count, exception) || seconds_to_delay(count)
|
207
|
+
end
|
208
|
+
|
209
|
+
# delayed_job uses the same basic formula
|
210
|
+
def seconds_to_delay(count)
|
211
|
+
(count ** 4) + 15 + (rand(30)*(count+1))
|
212
|
+
end
|
213
|
+
|
214
|
+
def retry_in(worker, count, exception)
|
215
|
+
begin
|
216
|
+
worker.sidekiq_retry_in_block.call(count, exception).to_i
|
217
|
+
rescue Exception => e
|
218
|
+
handle_exception(e, { context: "Failure scheduling retry using the defined `sidekiq_retry_in` in #{worker.class.name}, falling back to default" })
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def exception_caused_by_shutdown?(e, checked_causes = [])
|
224
|
+
return false unless e.cause
|
225
|
+
|
226
|
+
# Handle circular causes
|
227
|
+
checked_causes << e.object_id
|
228
|
+
return false if checked_causes.include?(e.cause.object_id)
|
229
|
+
|
230
|
+
e.cause.instance_of?(Sidekiq::Shutdown) ||
|
231
|
+
exception_caused_by_shutdown?(e.cause, checked_causes)
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
data/lib/sidekiq/launcher.rb
CHANGED
@@ -61,8 +61,6 @@ module Sidekiq
|
|
61
61
|
|
62
62
|
private unless $TESTING
|
63
63
|
|
64
|
-
JVM_RESERVED_SIGNALS = ['USR1', 'USR2'] # Don't Process#kill if we get these signals via the API
|
65
|
-
|
66
64
|
def heartbeat
|
67
65
|
results = Sidekiq::CLI::PROCTITLES.map {|x| x.(self, to_data) }
|
68
66
|
results.compact!
|
@@ -110,11 +108,7 @@ module Sidekiq
|
|
110
108
|
|
111
109
|
return unless msg
|
112
110
|
|
113
|
-
|
114
|
-
Sidekiq::CLI.instance.handle_signal(msg)
|
115
|
-
else
|
116
|
-
::Process.kill(msg, $$)
|
117
|
-
end
|
111
|
+
::Process.kill(msg, $$)
|
118
112
|
rescue => e
|
119
113
|
# ignore all redis/network issues
|
120
114
|
logger.error("heartbeat: #{e.message}")
|
@@ -2,6 +2,15 @@ module Sidekiq
|
|
2
2
|
module Middleware
|
3
3
|
module Server
|
4
4
|
class ActiveRecord
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
# With Rails 5+ we must use the Reloader **always**.
|
8
|
+
# The reloader handles code loading and db connection management.
|
9
|
+
if ::Rails::VERSION::MAJOR >= 5
|
10
|
+
raise ArgumentError, "Rails 5 no longer needs or uses the ActiveRecord middleware."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
5
14
|
def call(*args)
|
6
15
|
yield
|
7
16
|
ensure
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'sidekiq/util'
|
3
3
|
require 'sidekiq/fetch'
|
4
|
+
require 'sidekiq/job_logger'
|
5
|
+
require 'sidekiq/job_retry'
|
4
6
|
require 'thread'
|
5
7
|
require 'concurrent/map'
|
6
8
|
require 'concurrent/atomic/atomic_fixnum'
|
@@ -37,7 +39,8 @@ module Sidekiq
|
|
37
39
|
@thread = nil
|
38
40
|
@strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
|
39
41
|
@reloader = Sidekiq.options[:reloader]
|
40
|
-
@
|
42
|
+
@logging = Sidekiq::JobLogger.new
|
43
|
+
@retrier = Sidekiq::JobRetry.new
|
41
44
|
end
|
42
45
|
|
43
46
|
def terminate(wait=false)
|
@@ -116,32 +119,61 @@ module Sidekiq
|
|
116
119
|
nil
|
117
120
|
end
|
118
121
|
|
122
|
+
def dispatch(job_hash, queue)
|
123
|
+
# since middleware can mutate the job hash
|
124
|
+
# we clone here so we report the original
|
125
|
+
# job structure to the Web UI
|
126
|
+
pristine = cloned(job_hash)
|
127
|
+
|
128
|
+
# If we're using a wrapper class, like ActiveJob, use the "wrapped"
|
129
|
+
# attribute to expose the underlying thing.
|
130
|
+
klass = job_hash['wrapped'.freeze] || job_hash["class".freeze]
|
131
|
+
ctx = "#{klass} JID-#{job_hash['jid'.freeze]}#{" BID-#{job_hash['bid'.freeze]}" if job_hash['bid'.freeze]}"
|
132
|
+
|
133
|
+
Sidekiq::Logging.with_context(ctx) do
|
134
|
+
@retrier.global(job_hash, queue) do
|
135
|
+
@logging.call(job_hash, queue) do
|
136
|
+
stats(pristine, queue) do
|
137
|
+
# Rails 5 requires a Reloader to wrap code execution. In order to
|
138
|
+
# constantize the worker and instantiate an instance, we have to call
|
139
|
+
# the Reloader. It handles code loading, db connection management, etc.
|
140
|
+
# Effectively this block denotes a "unit of work" to Rails.
|
141
|
+
@reloader.call do
|
142
|
+
klass = job_hash['class'.freeze].constantize
|
143
|
+
worker = klass.new
|
144
|
+
worker.jid = job_hash['jid'.freeze]
|
145
|
+
@retrier.local(worker, job_hash, queue) do
|
146
|
+
yield worker
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
119
155
|
def process(work)
|
120
156
|
jobstr = work.job
|
121
157
|
queue = work.queue_name
|
122
158
|
|
123
159
|
ack = false
|
124
160
|
begin
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
Sidekiq::Logging.with_context(log_context(job_hash)) do
|
133
|
-
ack = true
|
134
|
-
Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
|
135
|
-
@executor.call do
|
136
|
-
# Only ack if we either attempted to start this job or
|
137
|
-
# successfully completed it. This prevents us from
|
138
|
-
# losing jobs if a middleware raises an exception before yielding
|
139
|
-
execute_job(worker, cloned(job_hash['args'.freeze]))
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
161
|
+
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
162
|
+
job_hash = nil
|
163
|
+
begin
|
164
|
+
job_hash = Sidekiq.load_json(jobstr)
|
165
|
+
rescue => ex
|
166
|
+
handle_exception(ex, { :context => "Invalid JSON for job", :jobstr => jobstr })
|
167
|
+
send_to_morgue(jobstr)
|
144
168
|
ack = true
|
169
|
+
raise
|
170
|
+
end
|
171
|
+
|
172
|
+
ack = true
|
173
|
+
dispatch(job_hash, queue) do |worker|
|
174
|
+
Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
|
175
|
+
execute_job(worker, cloned(job_hash['args'.freeze]))
|
176
|
+
end
|
145
177
|
end
|
146
178
|
rescue Sidekiq::Shutdown
|
147
179
|
# Had to force kill this job because it didn't finish
|
@@ -149,18 +181,23 @@ module Sidekiq
|
|
149
181
|
# we didn't properly finish it.
|
150
182
|
ack = false
|
151
183
|
rescue Exception => ex
|
152
|
-
|
153
|
-
|
184
|
+
e = ex.is_a?(::Sidekiq::JobRetry::Skip) && ex.cause ? ex.cause : ex
|
185
|
+
handle_exception(e, { :context => "Job raised exception", :job => job_hash, :jobstr => jobstr })
|
186
|
+
raise e
|
154
187
|
ensure
|
155
188
|
work.acknowledge if ack
|
156
189
|
end
|
157
190
|
end
|
158
191
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
192
|
+
def send_to_morgue(msg)
|
193
|
+
now = Time.now.to_f
|
194
|
+
Sidekiq.redis do |conn|
|
195
|
+
conn.multi do
|
196
|
+
conn.zadd('dead', now, msg)
|
197
|
+
conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout)
|
198
|
+
conn.zremrangebyrank('dead', 0, -DeadSet.max_jobs)
|
199
|
+
end
|
200
|
+
end
|
164
201
|
end
|
165
202
|
|
166
203
|
def execute_job(worker, cloned_args)
|
@@ -175,9 +212,9 @@ module Sidekiq
|
|
175
212
|
PROCESSED = Concurrent::AtomicFixnum.new
|
176
213
|
FAILURE = Concurrent::AtomicFixnum.new
|
177
214
|
|
178
|
-
def stats(
|
215
|
+
def stats(job_hash, queue)
|
179
216
|
tid = thread_identity
|
180
|
-
WORKER_STATE[tid] = {:queue => queue, :payload =>
|
217
|
+
WORKER_STATE[tid] = {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i }
|
181
218
|
|
182
219
|
begin
|
183
220
|
yield
|
@@ -193,8 +230,8 @@ module Sidekiq
|
|
193
230
|
# Deep clone the arguments passed to the worker so that if
|
194
231
|
# the job fails, what is pushed back onto Redis hasn't
|
195
232
|
# been mutated by the worker.
|
196
|
-
def cloned(
|
197
|
-
Marshal.load(Marshal.dump(
|
233
|
+
def cloned(thing)
|
234
|
+
Marshal.load(Marshal.dump(thing))
|
198
235
|
end
|
199
236
|
|
200
237
|
end
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -1,36 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module Sidekiq
|
3
|
-
def self.hook_rails!
|
4
|
-
return if defined?(@delay_removed)
|
5
|
-
|
6
|
-
ActiveSupport.on_load(:active_record) do
|
7
|
-
include Sidekiq::Extensions::ActiveRecord
|
8
|
-
end
|
9
|
-
|
10
|
-
ActiveSupport.on_load(:action_mailer) do
|
11
|
-
extend Sidekiq::Extensions::ActionMailer
|
12
|
-
end
|
13
|
-
|
14
|
-
Module.__send__(:include, Sidekiq::Extensions::Klass)
|
15
|
-
end
|
16
|
-
|
17
|
-
# Removes the generic aliases which MAY clash with names of already
|
18
|
-
# created methods by other applications. The methods `sidekiq_delay`,
|
19
|
-
# `sidekiq_delay_for` and `sidekiq_delay_until` can be used instead.
|
20
|
-
def self.remove_delay!
|
21
|
-
@delay_removed = true
|
22
|
-
|
23
|
-
[Extensions::ActiveRecord,
|
24
|
-
Extensions::ActionMailer,
|
25
|
-
Extensions::Klass].each do |mod|
|
26
|
-
mod.module_eval do
|
27
|
-
remove_method :delay if respond_to?(:delay)
|
28
|
-
remove_method :delay_for if respond_to?(:delay_for)
|
29
|
-
remove_method :delay_until if respond_to?(:delay_until)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
3
|
class Rails < ::Rails::Engine
|
35
4
|
# We need to setup this up before any application configuration which might
|
36
5
|
# change Sidekiq middleware.
|
@@ -48,10 +17,6 @@ module Sidekiq
|
|
48
17
|
end
|
49
18
|
end
|
50
19
|
|
51
|
-
initializer 'sidekiq' do
|
52
|
-
Sidekiq.hook_rails!
|
53
|
-
end
|
54
|
-
|
55
20
|
config.after_initialize do
|
56
21
|
# This hook happens after all initializers are run, just before returning
|
57
22
|
# from config/environment.rb back to sidekiq/cli.rb.
|
@@ -62,40 +27,12 @@ module Sidekiq
|
|
62
27
|
#
|
63
28
|
Sidekiq.configure_server do |_|
|
64
29
|
if ::Rails::VERSION::MAJOR >= 5
|
65
|
-
|
66
|
-
|
67
|
-
if defined?(Sidekiq::Middleware::Server::ActiveRecord) && Sidekiq.server_middleware.exists?(Sidekiq::Middleware::Server::ActiveRecord)
|
68
|
-
raise ArgumentError, "You are using the Sidekiq ActiveRecord middleware and the new Rails 5 reloader which are incompatible. Please remove the ActiveRecord middleware from your Sidekiq middleware configuration."
|
69
|
-
elsif ::Rails.application.config.cache_classes
|
70
|
-
# The reloader API has proven to be troublesome under load in production.
|
71
|
-
# We won't use it at all when classes are cached, see #3154
|
72
|
-
Sidekiq.logger.debug { "Autoload disabled in #{::Rails.env}, Sidekiq will not reload changed classes" }
|
73
|
-
Sidekiq.options[:executor] = Sidekiq::Rails::Executor.new
|
74
|
-
else
|
75
|
-
Sidekiq.logger.debug { "Enabling Rails 5+ live code reloading, so hot!" }
|
76
|
-
Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
|
77
|
-
Psych::Visitors::ToRuby.prepend(Sidekiq::Rails::PsychAutoload)
|
78
|
-
end
|
30
|
+
Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
|
31
|
+
Psych::Visitors::ToRuby.prepend(Sidekiq::Rails::PsychAutoload)
|
79
32
|
end
|
80
33
|
end
|
81
34
|
end
|
82
35
|
|
83
|
-
class Executor
|
84
|
-
def initialize(app = ::Rails.application)
|
85
|
-
@app = app
|
86
|
-
end
|
87
|
-
|
88
|
-
def call
|
89
|
-
@app.executor.wrap do
|
90
|
-
yield
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def inspect
|
95
|
-
"#<Sidekiq::Rails::Executor @app=#{@app.class.name}>"
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
36
|
class Reloader
|
100
37
|
def initialize(app = ::Rails.application)
|
101
38
|
@app = app
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -317,7 +317,7 @@ module Sidekiq
|
|
317
317
|
end
|
318
318
|
end
|
319
319
|
|
320
|
-
if defined?(::Rails) && !Rails.env.test?
|
320
|
+
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
|
321
321
|
puts("**************************************************")
|
322
322
|
puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
|
323
323
|
puts("**************************************************")
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -274,19 +274,14 @@ module Sidekiq
|
|
274
274
|
resp = case resp
|
275
275
|
when Array
|
276
276
|
resp
|
277
|
-
when Integer
|
278
|
-
[resp, {}, []]
|
279
277
|
else
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
else
|
286
|
-
{ "Content-Type" => "text/html", "Cache-Control" => "no-cache" }
|
287
|
-
end
|
278
|
+
headers = {
|
279
|
+
"Content-Type" => "text/html",
|
280
|
+
"Cache-Control" => "no-cache",
|
281
|
+
"Content-Language" => action.locale,
|
282
|
+
}
|
288
283
|
|
289
|
-
[200,
|
284
|
+
[200, headers, [resp]]
|
290
285
|
end
|
291
286
|
|
292
287
|
resp[1] = resp[1].dup
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -65,6 +65,14 @@ module Sidekiq
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
def text_direction
|
69
|
+
get_locale['TextDirection'] || 'ltr'
|
70
|
+
end
|
71
|
+
|
72
|
+
def rtl?
|
73
|
+
text_direction == 'rtl'
|
74
|
+
end
|
75
|
+
|
68
76
|
# Given a browser request Accept-Language header like
|
69
77
|
# "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2", this function
|
70
78
|
# will return "fr" since that's the first code with a matching
|
@@ -144,7 +152,7 @@ module Sidekiq
|
|
144
152
|
|
145
153
|
def relative_time(time)
|
146
154
|
stamp = time.getutc.iso8601
|
147
|
-
%{<time title="#{stamp}" datetime="#{stamp}">#{time}</time>}
|
155
|
+
%{<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>}
|
148
156
|
end
|
149
157
|
|
150
158
|
def job_params(job, score)
|
data/lib/sidekiq/worker.rb
CHANGED
@@ -4,6 +4,7 @@ require 'sidekiq/core_ext'
|
|
4
4
|
|
5
5
|
module Sidekiq
|
6
6
|
|
7
|
+
|
7
8
|
##
|
8
9
|
# Include this module in your worker class and you can easily create
|
9
10
|
# asynchronous jobs:
|
@@ -37,6 +38,34 @@ module Sidekiq
|
|
37
38
|
Sidekiq.logger
|
38
39
|
end
|
39
40
|
|
41
|
+
# This helper class encapsulates the set options for `set`, e.g.
|
42
|
+
#
|
43
|
+
# SomeWorker.set(queue: 'foo').perform_async(....)
|
44
|
+
#
|
45
|
+
class Setter
|
46
|
+
def initialize(opts)
|
47
|
+
@opts = opts
|
48
|
+
end
|
49
|
+
|
50
|
+
def perform_async(*args)
|
51
|
+
@opts['class'.freeze].client_push(@opts.merge!('args'.freeze => args))
|
52
|
+
end
|
53
|
+
|
54
|
+
# +interval+ must be a timestamp, numeric or something that acts
|
55
|
+
# numeric (like an activesupport time interval).
|
56
|
+
def perform_in(interval, *args)
|
57
|
+
int = interval.to_f
|
58
|
+
now = Time.now.to_f
|
59
|
+
ts = (int < 1_000_000_000 ? now + int : int)
|
60
|
+
|
61
|
+
@opts.merge! 'args'.freeze => args, 'at'.freeze => ts
|
62
|
+
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
63
|
+
@opts.delete('at'.freeze) if ts <= now
|
64
|
+
@opts['class'.freeze].client_push(@opts)
|
65
|
+
end
|
66
|
+
alias_method :perform_at, :perform_in
|
67
|
+
end
|
68
|
+
|
40
69
|
module ClassMethods
|
41
70
|
|
42
71
|
def delay(*args)
|
@@ -52,12 +81,11 @@ module Sidekiq
|
|
52
81
|
end
|
53
82
|
|
54
83
|
def set(options)
|
55
|
-
|
56
|
-
self
|
84
|
+
Setter.new(options.merge!('class'.freeze => self))
|
57
85
|
end
|
58
86
|
|
59
87
|
def perform_async(*args)
|
60
|
-
client_push('class' => self, 'args' => args)
|
88
|
+
client_push('class'.freeze => self, 'args'.freeze => args)
|
61
89
|
end
|
62
90
|
|
63
91
|
# +interval+ must be a timestamp, numeric or something that acts
|
@@ -67,7 +95,7 @@ module Sidekiq
|
|
67
95
|
now = Time.now.to_f
|
68
96
|
ts = (int < 1_000_000_000 ? now + int : int)
|
69
97
|
|
70
|
-
item = { 'class' => self, 'args' => args, 'at' => ts }
|
98
|
+
item = { 'class'.freeze => self, 'args'.freeze => args, 'at'.freeze => ts }
|
71
99
|
|
72
100
|
# Optimization to enqueue something now that is scheduled to go out now or in the past
|
73
101
|
item.delete('at'.freeze) if ts <= now
|
@@ -106,13 +134,8 @@ module Sidekiq
|
|
106
134
|
end
|
107
135
|
|
108
136
|
def client_push(item) # :nodoc:
|
109
|
-
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'] || Sidekiq.redis_pool
|
110
|
-
hash =
|
111
|
-
x, Thread.current[:sidekiq_worker_set] = Thread.current[:sidekiq_worker_set], nil
|
112
|
-
x.stringify_keys.merge(item.stringify_keys)
|
113
|
-
else
|
114
|
-
item.stringify_keys
|
115
|
-
end
|
137
|
+
pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'.freeze] || Sidekiq.redis_pool
|
138
|
+
hash = item.stringify_keys
|
116
139
|
Sidekiq::Client.new(pool).push(hash)
|
117
140
|
end
|
118
141
|
|