sidekiq 4.2.10 → 6.5.7
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/Changes.md +573 -1
- data/LICENSE +3 -3
- data/README.md +25 -34
- data/bin/sidekiq +27 -3
- data/bin/sidekiqload +81 -74
- data/bin/sidekiqmon +8 -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/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +585 -285
- data/lib/sidekiq/cli.rb +256 -233
- data/lib/sidekiq/client.rb +86 -83
- data/lib/sidekiq/component.rb +65 -0
- data/lib/sidekiq/delay.rb +43 -0
- data/lib/sidekiq/extensions/action_mailer.rb +13 -22
- data/lib/sidekiq/extensions/active_record.rb +13 -10
- data/lib/sidekiq/extensions/class_methods.rb +14 -11
- data/lib/sidekiq/extensions/generic_proxy.rb +13 -5
- data/lib/sidekiq/fetch.rb +50 -40
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +51 -0
- data/lib/sidekiq/job_retry.rb +282 -0
- data/lib/sidekiq/job_util.rb +71 -0
- data/lib/sidekiq/launcher.rb +184 -90
- data/lib/sidekiq/logger.rb +156 -0
- data/lib/sidekiq/manager.rb +43 -45
- 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 +102 -46
- data/lib/sidekiq/middleware/current_attributes.rb +63 -0
- data/lib/sidekiq/middleware/i18n.rb +7 -7
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +133 -0
- data/lib/sidekiq/paginator.rb +20 -16
- data/lib/sidekiq/processor.rb +176 -91
- data/lib/sidekiq/rails.rb +41 -96
- data/lib/sidekiq/redis_client_adapter.rb +154 -0
- data/lib/sidekiq/redis_connection.rb +117 -48
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +134 -44
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing/inline.rb +6 -5
- data/lib/sidekiq/testing.rb +80 -61
- data/lib/sidekiq/transaction_aware_client.rb +45 -0
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +15 -15
- data/lib/sidekiq/web/application.rb +129 -86
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +170 -83
- data/lib/sidekiq/web/router.rb +23 -19
- data/lib/sidekiq/web.rb +69 -109
- data/lib/sidekiq/worker.rb +290 -41
- data/lib/sidekiq.rb +185 -77
- data/sidekiq.gemspec +23 -27
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +112 -61
- 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 +70 -91
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application-dark.css +143 -0
- data/web/assets/stylesheets/application-rtl.css +242 -0
- data/web/assets/stylesheets/application.css +364 -144
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +87 -0
- data/web/locales/de.yml +14 -2
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +15 -1
- data/web/locales/es.yml +22 -5
- data/web/locales/fa.yml +1 -0
- data/web/locales/fr.yml +10 -3
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +19 -4
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/pt-br.yml +27 -9
- data/web/locales/ru.yml +4 -0
- data/web/locales/ur.yml +80 -0
- data/web/locales/vi.yml +83 -0
- data/web/locales/zh-cn.yml +36 -11
- data/web/locales/zh-tw.yml +32 -7
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +3 -2
- data/web/views/_nav.erb +5 -19
- data/web/views/_paging.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +62 -24
- data/web/views/dashboard.erb +24 -15
- data/web/views/dead.erb +3 -3
- data/web/views/layout.erb +14 -3
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/morgue.erb +9 -6
- data/web/views/queue.erb +26 -12
- data/web/views/queues.erb +12 -2
- data/web/views/retries.erb +14 -7
- data/web/views/retry.erb +3 -3
- data/web/views/scheduled.erb +7 -4
- metadata +66 -206
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -9
- data/.gitignore +0 -12
- data/.travis.yml +0 -18
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/COMM-LICENSE +0 -95
- data/Ent-Changes.md +0 -173
- data/Gemfile +0 -29
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-Changes.md +0 -628
- data/Rakefile +0 -12
- data/bin/sidekiqctl +0 -99
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
- data/lib/generators/sidekiq/worker_generator.rb +0 -49
- data/lib/sidekiq/core_ext.rb +0 -119
- data/lib/sidekiq/exception_handler.rb +0 -31
- data/lib/sidekiq/logging.rb +0 -106
- data/lib/sidekiq/middleware/server/active_record.rb +0 -13
- data/lib/sidekiq/middleware/server/logging.rb +0 -31
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
- data/lib/sidekiq/util.rb +0 -63
data/lib/sidekiq/client.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require "sidekiq/middleware/chain"
|
5
|
+
require "sidekiq/job_util"
|
4
6
|
|
5
7
|
module Sidekiq
|
6
8
|
class Client
|
9
|
+
include Sidekiq::JobUtil
|
7
10
|
|
8
11
|
##
|
9
12
|
# Define client-side middleware:
|
@@ -12,14 +15,14 @@ module Sidekiq
|
|
12
15
|
# client.middleware do |chain|
|
13
16
|
# chain.use MyClientMiddleware
|
14
17
|
# end
|
15
|
-
# client.push('class' => '
|
18
|
+
# client.push('class' => 'SomeJob', 'args' => [1,2,3])
|
16
19
|
#
|
17
20
|
# All client instances default to the globally-defined
|
18
21
|
# Sidekiq.client_middleware but you can change as necessary.
|
19
22
|
#
|
20
23
|
def middleware(&block)
|
21
24
|
@chain ||= Sidekiq.client_middleware
|
22
|
-
if
|
25
|
+
if block
|
23
26
|
@chain = @chain.dup
|
24
27
|
yield @chain
|
25
28
|
end
|
@@ -38,7 +41,7 @@ module Sidekiq
|
|
38
41
|
# Generally this is only needed for very large Sidekiq installs processing
|
39
42
|
# thousands of jobs per second. I don't recommend sharding unless you
|
40
43
|
# cannot scale any other way (e.g. splitting your app into smaller apps).
|
41
|
-
def initialize(redis_pool=nil)
|
44
|
+
def initialize(redis_pool = nil)
|
42
45
|
@redis_pool = redis_pool || Thread.current[:sidekiq_via_pool] || Sidekiq.redis_pool
|
43
46
|
end
|
44
47
|
|
@@ -46,11 +49,17 @@ module Sidekiq
|
|
46
49
|
# The main method used to push a job to Redis. Accepts a number of options:
|
47
50
|
#
|
48
51
|
# queue - the named queue to use, default 'default'
|
49
|
-
# class - the
|
52
|
+
# class - the job class to call, required
|
50
53
|
# args - an array of simple arguments to the perform method, must be JSON-serializable
|
54
|
+
# at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f)
|
51
55
|
# retry - whether to retry this job if it fails, default true or an integer number of retries
|
52
56
|
# backtrace - whether to save any error backtrace, default false
|
53
57
|
#
|
58
|
+
# If class is set to the class name, the jobs' options will be based on Sidekiq's default
|
59
|
+
# job options. Otherwise, they will be based on the job class's options.
|
60
|
+
#
|
61
|
+
# Any options valid for a job class's sidekiq_options are also available here.
|
62
|
+
#
|
54
63
|
# All options must be strings, not symbols. NB: because we are serializing to JSON, all
|
55
64
|
# symbols in 'args' will be converted to strings. Note that +backtrace: true+ can take quite a bit of
|
56
65
|
# space in Redis; a large volume of failing jobs can start Redis swapping if you aren't careful.
|
@@ -58,22 +67,25 @@ module Sidekiq
|
|
58
67
|
# Returns a unique Job ID. If middleware stops the job, nil will be returned instead.
|
59
68
|
#
|
60
69
|
# Example:
|
61
|
-
# push('queue' => 'my_queue', 'class' =>
|
70
|
+
# push('queue' => 'my_queue', 'class' => MyJob, 'args' => ['foo', 1, :bat => 'bar'])
|
62
71
|
#
|
63
72
|
def push(item)
|
64
73
|
normed = normalize_item(item)
|
65
|
-
payload =
|
66
|
-
|
74
|
+
payload = middleware.invoke(item["class"], normed, normed["queue"], @redis_pool) do
|
75
|
+
normed
|
76
|
+
end
|
67
77
|
if payload
|
78
|
+
verify_json(payload)
|
68
79
|
raw_push([payload])
|
69
|
-
payload[
|
80
|
+
payload["jid"]
|
70
81
|
end
|
71
82
|
end
|
72
83
|
|
73
84
|
##
|
74
|
-
# Push a large number of jobs to Redis.
|
75
|
-
#
|
76
|
-
#
|
85
|
+
# Push a large number of jobs to Redis. This method cuts out the redis
|
86
|
+
# network round trip latency. I wouldn't recommend pushing more than
|
87
|
+
# 1000 per call but YMMV based on network quality, size of job args, etc.
|
88
|
+
# A large number of jobs can cause a bit of Redis command processing latency.
|
77
89
|
#
|
78
90
|
# Takes the same arguments as #push except that args is expected to be
|
79
91
|
# an Array of Arrays. All other keys are duplicated for each job. Each job
|
@@ -83,19 +95,30 @@ module Sidekiq
|
|
83
95
|
# Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
|
84
96
|
# than the number given if the middleware stopped processing for one or more jobs.
|
85
97
|
def push_bulk(items)
|
86
|
-
|
87
|
-
|
88
|
-
|
98
|
+
args = items["args"]
|
99
|
+
raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless args.is_a?(Array) && args.all?(Array)
|
100
|
+
return [] if args.empty? # no jobs to push
|
101
|
+
|
102
|
+
at = items.delete("at")
|
103
|
+
raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all? { |entry| entry.is_a?(Numeric) })
|
104
|
+
raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
|
105
|
+
|
106
|
+
jid = items.delete("jid")
|
107
|
+
raise ArgumentError, "Explicitly passing 'jid' when pushing more than one job is not supported" if jid && args.size > 1
|
89
108
|
|
90
109
|
normed = normalize_item(items)
|
91
|
-
payloads =
|
92
|
-
copy = normed.merge(
|
93
|
-
|
94
|
-
result
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
110
|
+
payloads = args.map.with_index { |job_args, index|
|
111
|
+
copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12))
|
112
|
+
copy["at"] = (at.is_a?(Array) ? at[index] : at) if at
|
113
|
+
result = middleware.invoke(items["class"], copy, copy["queue"], @redis_pool) do
|
114
|
+
verify_json(copy)
|
115
|
+
copy
|
116
|
+
end
|
117
|
+
result || nil
|
118
|
+
}.compact
|
119
|
+
|
120
|
+
raw_push(payloads) unless payloads.empty?
|
121
|
+
payloads.collect { |payload| payload["jid"] }
|
99
122
|
end
|
100
123
|
|
101
124
|
# Allows sharding of jobs across any number of Redis instances. All jobs
|
@@ -103,8 +126,8 @@ module Sidekiq
|
|
103
126
|
#
|
104
127
|
# pool = ConnectionPool.new { Redis.new }
|
105
128
|
# Sidekiq::Client.via(pool) do
|
106
|
-
#
|
107
|
-
#
|
129
|
+
# SomeJob.perform_async(1,2,3)
|
130
|
+
# SomeOtherJob.perform_async(1,2,3)
|
108
131
|
# end
|
109
132
|
#
|
110
133
|
# Generally this is only needed for very large Sidekiq installs processing
|
@@ -113,15 +136,13 @@ module Sidekiq
|
|
113
136
|
def self.via(pool)
|
114
137
|
raise ArgumentError, "No pool given" if pool.nil?
|
115
138
|
current_sidekiq_pool = Thread.current[:sidekiq_via_pool]
|
116
|
-
raise RuntimeError, "Sidekiq::Client.via is not re-entrant" if current_sidekiq_pool && current_sidekiq_pool != pool
|
117
139
|
Thread.current[:sidekiq_via_pool] = pool
|
118
140
|
yield
|
119
141
|
ensure
|
120
|
-
Thread.current[:sidekiq_via_pool] =
|
142
|
+
Thread.current[:sidekiq_via_pool] = current_sidekiq_pool
|
121
143
|
end
|
122
144
|
|
123
145
|
class << self
|
124
|
-
|
125
146
|
def push(item)
|
126
147
|
new.push(item)
|
127
148
|
end
|
@@ -131,40 +152,40 @@ module Sidekiq
|
|
131
152
|
end
|
132
153
|
|
133
154
|
# Resque compatibility helpers. Note all helpers
|
134
|
-
# should go through
|
155
|
+
# should go through Sidekiq::Job#client_push.
|
135
156
|
#
|
136
157
|
# Example usage:
|
137
|
-
# Sidekiq::Client.enqueue(
|
158
|
+
# Sidekiq::Client.enqueue(MyJob, 'foo', 1, :bat => 'bar')
|
138
159
|
#
|
139
160
|
# Messages are enqueued to the 'default' queue.
|
140
161
|
#
|
141
162
|
def enqueue(klass, *args)
|
142
|
-
klass.client_push(
|
163
|
+
klass.client_push("class" => klass, "args" => args)
|
143
164
|
end
|
144
165
|
|
145
166
|
# Example usage:
|
146
|
-
# Sidekiq::Client.enqueue_to(:queue_name,
|
167
|
+
# Sidekiq::Client.enqueue_to(:queue_name, MyJob, 'foo', 1, :bat => 'bar')
|
147
168
|
#
|
148
169
|
def enqueue_to(queue, klass, *args)
|
149
|
-
klass.client_push(
|
170
|
+
klass.client_push("queue" => queue, "class" => klass, "args" => args)
|
150
171
|
end
|
151
172
|
|
152
173
|
# Example usage:
|
153
|
-
# Sidekiq::Client.enqueue_to_in(:queue_name, 3.minutes,
|
174
|
+
# Sidekiq::Client.enqueue_to_in(:queue_name, 3.minutes, MyJob, 'foo', 1, :bat => 'bar')
|
154
175
|
#
|
155
176
|
def enqueue_to_in(queue, interval, klass, *args)
|
156
177
|
int = interval.to_f
|
157
178
|
now = Time.now.to_f
|
158
179
|
ts = (int < 1_000_000_000 ? now + int : int)
|
159
180
|
|
160
|
-
item = {
|
161
|
-
item.delete(
|
181
|
+
item = {"class" => klass, "args" => args, "at" => ts, "queue" => queue}
|
182
|
+
item.delete("at") if ts <= now
|
162
183
|
|
163
184
|
klass.client_push(item)
|
164
185
|
end
|
165
186
|
|
166
187
|
# Example usage:
|
167
|
-
# Sidekiq::Client.enqueue_in(3.minutes,
|
188
|
+
# Sidekiq::Client.enqueue_in(3.minutes, MyJob, 'foo', 1, :bat => 'bar')
|
168
189
|
#
|
169
190
|
def enqueue_in(interval, klass, *args)
|
170
191
|
klass.perform_in(interval, *args)
|
@@ -175,61 +196,43 @@ module Sidekiq
|
|
175
196
|
|
176
197
|
def raw_push(payloads)
|
177
198
|
@redis_pool.with do |conn|
|
178
|
-
|
179
|
-
|
199
|
+
retryable = true
|
200
|
+
begin
|
201
|
+
conn.pipelined do |pipeline|
|
202
|
+
atomic_push(pipeline, payloads)
|
203
|
+
end
|
204
|
+
rescue RedisConnection.adapter::BaseError => ex
|
205
|
+
# 2550 Failover can cause the server to become a replica, need
|
206
|
+
# to disconnect and reopen the socket to get back to the primary.
|
207
|
+
# 4495 Use the same logic if we have a "Not enough replicas" error from the primary
|
208
|
+
# 4985 Use the same logic when a blocking command is force-unblocked
|
209
|
+
# The retry logic is copied from sidekiq.rb
|
210
|
+
if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
|
211
|
+
conn.disconnect!
|
212
|
+
retryable = false
|
213
|
+
retry
|
214
|
+
end
|
215
|
+
raise
|
180
216
|
end
|
181
217
|
end
|
182
218
|
true
|
183
219
|
end
|
184
220
|
|
185
221
|
def atomic_push(conn, payloads)
|
186
|
-
if payloads.first
|
187
|
-
conn.zadd(
|
188
|
-
at = hash.delete(
|
222
|
+
if payloads.first.key?("at")
|
223
|
+
conn.zadd("schedule", payloads.flat_map { |hash|
|
224
|
+
at = hash.delete("at").to_s
|
189
225
|
[at, Sidekiq.dump_json(hash)]
|
190
|
-
|
226
|
+
})
|
191
227
|
else
|
192
|
-
|
228
|
+
queue = payloads.first["queue"]
|
193
229
|
now = Time.now.to_f
|
194
|
-
to_push = payloads.map
|
195
|
-
entry[
|
230
|
+
to_push = payloads.map { |entry|
|
231
|
+
entry["enqueued_at"] = now
|
196
232
|
Sidekiq.dump_json(entry)
|
197
|
-
|
198
|
-
conn.sadd(
|
199
|
-
conn.lpush("queue:#{
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
def process_single(worker_class, item)
|
204
|
-
queue = item['queue']
|
205
|
-
|
206
|
-
middleware.invoke(worker_class, item, queue, @redis_pool) do
|
207
|
-
item
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
def normalize_item(item)
|
212
|
-
raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.has_key?('class'.freeze) && item.has_key?('args'.freeze)
|
213
|
-
raise(ArgumentError, "Job args must be an Array") unless item['args'].is_a?(Array)
|
214
|
-
raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'.freeze].is_a?(Class) || item['class'.freeze].is_a?(String)
|
215
|
-
#raise(ArgumentError, "Arguments must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices") unless JSON.load(JSON.dump(item['args'])) == item['args']
|
216
|
-
|
217
|
-
normalized_hash(item['class'.freeze])
|
218
|
-
.each{ |key, value| item[key] = value if item[key].nil? }
|
219
|
-
|
220
|
-
item['class'.freeze] = item['class'.freeze].to_s
|
221
|
-
item['queue'.freeze] = item['queue'.freeze].to_s
|
222
|
-
item['jid'.freeze] ||= SecureRandom.hex(12)
|
223
|
-
item['created_at'.freeze] ||= Time.now.to_f
|
224
|
-
item
|
225
|
-
end
|
226
|
-
|
227
|
-
def normalized_hash(item_class)
|
228
|
-
if item_class.is_a?(Class)
|
229
|
-
raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options'.freeze)
|
230
|
-
item_class.get_sidekiq_options
|
231
|
-
else
|
232
|
-
Sidekiq.default_worker_options
|
233
|
+
}
|
234
|
+
conn.sadd("queues", [queue])
|
235
|
+
conn.lpush("queue:#{queue}", to_push)
|
233
236
|
end
|
234
237
|
end
|
235
238
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
##
|
3
|
+
# Sidekiq::Component assumes a config instance is available at @config
|
4
|
+
module Component # :nodoc:
|
5
|
+
attr_reader :config
|
6
|
+
|
7
|
+
def watchdog(last_words)
|
8
|
+
yield
|
9
|
+
rescue Exception => ex
|
10
|
+
handle_exception(ex, {context: last_words})
|
11
|
+
raise ex
|
12
|
+
end
|
13
|
+
|
14
|
+
def safe_thread(name, &block)
|
15
|
+
Thread.new do
|
16
|
+
Thread.current.name = name
|
17
|
+
watchdog(name, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def logger
|
22
|
+
config.logger
|
23
|
+
end
|
24
|
+
|
25
|
+
def redis(&block)
|
26
|
+
config.redis(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def tid
|
30
|
+
Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
|
31
|
+
end
|
32
|
+
|
33
|
+
def hostname
|
34
|
+
ENV["DYNO"] || Socket.gethostname
|
35
|
+
end
|
36
|
+
|
37
|
+
def process_nonce
|
38
|
+
@@process_nonce ||= SecureRandom.hex(6)
|
39
|
+
end
|
40
|
+
|
41
|
+
def identity
|
42
|
+
@@identity ||= "#{hostname}:#{::Process.pid}:#{process_nonce}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle_exception(ex, ctx = {})
|
46
|
+
config.handle_exception(ex, ctx)
|
47
|
+
end
|
48
|
+
|
49
|
+
def fire_event(event, options = {})
|
50
|
+
oneshot = options.fetch(:oneshot, true)
|
51
|
+
reverse = options[:reverse]
|
52
|
+
reraise = options[:reraise]
|
53
|
+
|
54
|
+
arr = config[:lifecycle_events][event]
|
55
|
+
arr.reverse! if reverse
|
56
|
+
arr.each do |block|
|
57
|
+
block.call
|
58
|
+
rescue => ex
|
59
|
+
handle_exception(ex, {context: "Exception during Sidekiq lifecycle event.", event: event})
|
60
|
+
raise ex if reraise
|
61
|
+
end
|
62
|
+
arr.clear if oneshot # once we've fired an event, we never fire it again
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sidekiq # :nodoc:
|
4
|
+
module Extensions
|
5
|
+
def self.enable_delay!
|
6
|
+
warn "Sidekiq's Delayed Extensions will be removed in Sidekiq 7.0", uplevel: 1
|
7
|
+
|
8
|
+
if defined?(::ActiveSupport)
|
9
|
+
require "sidekiq/extensions/active_record"
|
10
|
+
require "sidekiq/extensions/action_mailer"
|
11
|
+
|
12
|
+
# Need to patch Psych so it can autoload classes whose names are serialized
|
13
|
+
# in the delayed YAML.
|
14
|
+
Psych::Visitors::ToRuby.prepend(Sidekiq::Extensions::PsychAutoload)
|
15
|
+
|
16
|
+
ActiveSupport.on_load(:active_record) do
|
17
|
+
include Sidekiq::Extensions::ActiveRecord
|
18
|
+
end
|
19
|
+
ActiveSupport.on_load(:action_mailer) do
|
20
|
+
extend Sidekiq::Extensions::ActionMailer
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require "sidekiq/extensions/class_methods"
|
25
|
+
Module.__send__(:include, Sidekiq::Extensions::Klass)
|
26
|
+
end
|
27
|
+
|
28
|
+
module PsychAutoload
|
29
|
+
def resolve_class(klass_name)
|
30
|
+
return nil if !klass_name || klass_name.empty?
|
31
|
+
# constantize
|
32
|
+
names = klass_name.split("::")
|
33
|
+
names.shift if names.empty? || names.first.empty?
|
34
|
+
|
35
|
+
names.inject(Object) do |constant, name|
|
36
|
+
constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
37
|
+
end
|
38
|
+
rescue NameError
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "sidekiq/extensions/generic_proxy"
|
3
4
|
|
4
5
|
module Sidekiq
|
5
6
|
module Extensions
|
6
7
|
##
|
7
|
-
# Adds
|
8
|
-
# delivery to Sidekiq.
|
8
|
+
# Adds +delay+, +delay_for+ and +delay_until+ methods to ActionMailer to offload arbitrary email
|
9
|
+
# delivery to Sidekiq.
|
9
10
|
#
|
11
|
+
# @example
|
10
12
|
# UserMailer.delay.send_welcome_email(new_user)
|
11
13
|
# UserMailer.delay_for(5.days).send_welcome_email(new_user)
|
12
14
|
# UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user)
|
@@ -19,39 +21,28 @@ module Sidekiq
|
|
19
21
|
# The email method can return nil, which causes ActionMailer to return
|
20
22
|
# an undeliverable empty message.
|
21
23
|
if msg
|
22
|
-
deliver(msg)
|
23
|
-
else
|
24
|
-
raise "#{target.name}##{method_name} returned an undeliverable mail object"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def deliver(msg)
|
31
|
-
if msg.respond_to?(:deliver_now)
|
32
|
-
# Rails 4.2/5.0
|
33
24
|
msg.deliver_now
|
34
25
|
else
|
35
|
-
#
|
36
|
-
msg.deliver
|
26
|
+
raise "#{target.name}##{method_name} returned an undeliverable mail object"
|
37
27
|
end
|
38
28
|
end
|
39
29
|
end
|
40
30
|
|
41
31
|
module ActionMailer
|
42
|
-
def sidekiq_delay(options={})
|
32
|
+
def sidekiq_delay(options = {})
|
43
33
|
Proxy.new(DelayedMailer, self, options)
|
44
34
|
end
|
45
|
-
|
46
|
-
|
35
|
+
|
36
|
+
def sidekiq_delay_for(interval, options = {})
|
37
|
+
Proxy.new(DelayedMailer, self, options.merge("at" => Time.now.to_f + interval.to_f))
|
47
38
|
end
|
48
|
-
|
49
|
-
|
39
|
+
|
40
|
+
def sidekiq_delay_until(timestamp, options = {})
|
41
|
+
Proxy.new(DelayedMailer, self, options.merge("at" => timestamp.to_f))
|
50
42
|
end
|
51
43
|
alias_method :delay, :sidekiq_delay
|
52
44
|
alias_method :delay_for, :sidekiq_delay_for
|
53
45
|
alias_method :delay_until, :sidekiq_delay_until
|
54
46
|
end
|
55
|
-
|
56
47
|
end
|
57
48
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "sidekiq/extensions/generic_proxy"
|
3
4
|
|
4
5
|
module Sidekiq
|
5
6
|
module Extensions
|
6
7
|
##
|
7
|
-
# Adds
|
8
|
-
# execution to Sidekiq.
|
8
|
+
# Adds +delay+, +delay_for+ and +delay_until+ methods to ActiveRecord to offload instance method
|
9
|
+
# execution to Sidekiq.
|
9
10
|
#
|
10
|
-
#
|
11
|
+
# @example
|
12
|
+
# User.recent_signups.each { |user| user.delay.mark_as_awesome }
|
11
13
|
#
|
12
14
|
# Please note, this is not recommended as this will serialize the entire
|
13
15
|
# object to Redis. Your Sidekiq jobs should pass IDs, not entire instances.
|
@@ -22,19 +24,20 @@ module Sidekiq
|
|
22
24
|
end
|
23
25
|
|
24
26
|
module ActiveRecord
|
25
|
-
def sidekiq_delay(options={})
|
27
|
+
def sidekiq_delay(options = {})
|
26
28
|
Proxy.new(DelayedModel, self, options)
|
27
29
|
end
|
28
|
-
|
29
|
-
|
30
|
+
|
31
|
+
def sidekiq_delay_for(interval, options = {})
|
32
|
+
Proxy.new(DelayedModel, self, options.merge("at" => Time.now.to_f + interval.to_f))
|
30
33
|
end
|
31
|
-
|
32
|
-
|
34
|
+
|
35
|
+
def sidekiq_delay_until(timestamp, options = {})
|
36
|
+
Proxy.new(DelayedModel, self, options.merge("at" => timestamp.to_f))
|
33
37
|
end
|
34
38
|
alias_method :delay, :sidekiq_delay
|
35
39
|
alias_method :delay_for, :sidekiq_delay_for
|
36
40
|
alias_method :delay_until, :sidekiq_delay_until
|
37
41
|
end
|
38
|
-
|
39
42
|
end
|
40
43
|
end
|
@@ -1,14 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "sidekiq/extensions/generic_proxy"
|
3
4
|
|
4
5
|
module Sidekiq
|
5
6
|
module Extensions
|
6
7
|
##
|
7
|
-
# Adds
|
8
|
-
# execution to Sidekiq.
|
8
|
+
# Adds `delay`, `delay_for` and `delay_until` methods to all Classes to offload class method
|
9
|
+
# execution to Sidekiq.
|
9
10
|
#
|
10
|
-
#
|
11
|
-
#
|
11
|
+
# @example
|
12
|
+
# User.delay.delete_inactive
|
13
|
+
# Wikipedia.delay.download_changes_for(Date.today)
|
12
14
|
#
|
13
15
|
class DelayedClass
|
14
16
|
include Sidekiq::Worker
|
@@ -20,20 +22,21 @@ module Sidekiq
|
|
20
22
|
end
|
21
23
|
|
22
24
|
module Klass
|
23
|
-
def sidekiq_delay(options={})
|
25
|
+
def sidekiq_delay(options = {})
|
24
26
|
Proxy.new(DelayedClass, self, options)
|
25
27
|
end
|
26
|
-
|
27
|
-
|
28
|
+
|
29
|
+
def sidekiq_delay_for(interval, options = {})
|
30
|
+
Proxy.new(DelayedClass, self, options.merge("at" => Time.now.to_f + interval.to_f))
|
28
31
|
end
|
29
|
-
|
30
|
-
|
32
|
+
|
33
|
+
def sidekiq_delay_until(timestamp, options = {})
|
34
|
+
Proxy.new(DelayedClass, self, options.merge("at" => timestamp.to_f))
|
31
35
|
end
|
32
36
|
alias_method :delay, :sidekiq_delay
|
33
37
|
alias_method :delay_for, :sidekiq_delay_for
|
34
38
|
alias_method :delay_until, :sidekiq_delay_until
|
35
39
|
end
|
36
|
-
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
@@ -1,13 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "yaml"
|
3
4
|
|
4
5
|
module Sidekiq
|
5
6
|
module Extensions
|
7
|
+
SIZE_LIMIT = 8_192
|
8
|
+
|
6
9
|
class Proxy < BasicObject
|
7
|
-
def initialize(performable, target, options={})
|
10
|
+
def initialize(performable, target, options = {})
|
8
11
|
@performable = performable
|
9
12
|
@target = target
|
10
|
-
@opts = options
|
13
|
+
@opts = options.transform_keys(&:to_s)
|
11
14
|
end
|
12
15
|
|
13
16
|
def method_missing(name, *args)
|
@@ -17,9 +20,14 @@ module Sidekiq
|
|
17
20
|
# to JSON and then deserialized on the other side back into a
|
18
21
|
# Ruby object.
|
19
22
|
obj = [@target, name, args]
|
20
|
-
|
23
|
+
marshalled = ::YAML.dump(obj)
|
24
|
+
if marshalled.size > SIZE_LIMIT
|
25
|
+
::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" }
|
26
|
+
end
|
27
|
+
@performable.client_push({"class" => @performable,
|
28
|
+
"args" => [marshalled],
|
29
|
+
"display_class" => "#{@target}.#{name}"}.merge(@opts))
|
21
30
|
end
|
22
31
|
end
|
23
|
-
|
24
32
|
end
|
25
33
|
end
|