sidekiq 6.2.2 → 7.1.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +299 -11
- data/LICENSE.txt +9 -0
- data/README.md +45 -32
- data/bin/sidekiq +4 -9
- data/bin/sidekiqload +207 -117
- data/bin/sidekiqmon +4 -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 +334 -190
- data/lib/sidekiq/capsule.rb +127 -0
- data/lib/sidekiq/cli.rb +95 -81
- data/lib/sidekiq/client.rb +102 -96
- data/lib/sidekiq/{util.rb → component.rb} +14 -41
- data/lib/sidekiq/config.rb +278 -0
- data/lib/sidekiq/deploy.rb +62 -0
- data/lib/sidekiq/embedded.rb +61 -0
- data/lib/sidekiq/fetch.rb +26 -26
- data/lib/sidekiq/job.rb +371 -5
- data/lib/sidekiq/job_logger.rb +16 -28
- data/lib/sidekiq/job_retry.rb +85 -59
- data/lib/sidekiq/job_util.rb +105 -0
- data/lib/sidekiq/launcher.rb +106 -94
- data/lib/sidekiq/logger.rb +9 -44
- data/lib/sidekiq/manager.rb +40 -41
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +95 -0
- data/lib/sidekiq/metrics/tracking.rb +136 -0
- data/lib/sidekiq/middleware/chain.rb +96 -51
- data/lib/sidekiq/middleware/current_attributes.rb +95 -0
- data/lib/sidekiq/middleware/i18n.rb +6 -4
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +17 -4
- data/lib/sidekiq/paginator.rb +17 -9
- data/lib/sidekiq/processor.rb +60 -60
- data/lib/sidekiq/rails.rb +29 -6
- data/lib/sidekiq/redis_client_adapter.rb +96 -0
- data/lib/sidekiq/redis_connection.rb +17 -88
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +101 -44
- data/lib/sidekiq/testing/inline.rb +4 -4
- data/lib/sidekiq/testing.rb +41 -68
- data/lib/sidekiq/transaction_aware_client.rb +44 -0
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +3 -3
- data/lib/sidekiq/web/application.rb +47 -13
- data/lib/sidekiq/web/csrf_protection.rb +3 -3
- data/lib/sidekiq/web/helpers.rb +36 -33
- data/lib/sidekiq/web.rb +10 -17
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +86 -201
- data/sidekiq.gemspec +12 -10
- data/web/assets/javascripts/application.js +131 -60
- data/web/assets/javascripts/base-charts.js +106 -0
- 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-charts.js +166 -0
- data/web/assets/javascripts/dashboard.js +36 -273
- data/web/assets/javascripts/metrics.js +264 -0
- data/web/assets/stylesheets/application-dark.css +23 -23
- data/web/assets/stylesheets/application-rtl.css +2 -95
- data/web/assets/stylesheets/application.css +73 -402
- data/web/locales/ar.yml +70 -70
- data/web/locales/cs.yml +62 -62
- data/web/locales/da.yml +60 -53
- data/web/locales/de.yml +65 -65
- data/web/locales/el.yml +43 -24
- data/web/locales/en.yml +82 -69
- data/web/locales/es.yml +68 -68
- data/web/locales/fa.yml +65 -65
- data/web/locales/fr.yml +81 -67
- data/web/locales/gd.yml +99 -0
- data/web/locales/he.yml +65 -64
- data/web/locales/hi.yml +59 -59
- data/web/locales/it.yml +53 -53
- data/web/locales/ja.yml +73 -68
- data/web/locales/ko.yml +52 -52
- data/web/locales/lt.yml +66 -66
- data/web/locales/nb.yml +61 -61
- data/web/locales/nl.yml +52 -52
- data/web/locales/pl.yml +45 -45
- data/web/locales/pt-br.yml +63 -55
- data/web/locales/pt.yml +51 -51
- data/web/locales/ru.yml +67 -66
- data/web/locales/sv.yml +53 -53
- data/web/locales/ta.yml +60 -60
- data/web/locales/uk.yml +62 -61
- data/web/locales/ur.yml +64 -64
- data/web/locales/vi.yml +67 -67
- data/web/locales/zh-cn.yml +43 -16
- data/web/locales/zh-tw.yml +42 -8
- data/web/views/_footer.erb +6 -3
- data/web/views/_job_info.erb +18 -2
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_nav.erb +1 -1
- data/web/views/_paging.erb +2 -0
- data/web/views/_poll_link.erb +3 -6
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +44 -28
- data/web/views/dashboard.erb +44 -12
- data/web/views/layout.erb +1 -1
- data/web/views/metrics.erb +82 -0
- data/web/views/metrics_for_job.erb +68 -0
- data/web/views/morgue.erb +5 -9
- data/web/views/queue.erb +24 -24
- data/web/views/queues.erb +4 -2
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +62 -31
- data/LICENSE +0 -9
- data/lib/generators/sidekiq/worker_generator.rb +0 -57
- data/lib/sidekiq/delay.rb +0 -41
- data/lib/sidekiq/exception_handler.rb +0 -27
- data/lib/sidekiq/extensions/action_mailer.rb +0 -48
- data/lib/sidekiq/extensions/active_record.rb +0 -43
- data/lib/sidekiq/extensions/class_methods.rb +0 -43
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
- data/lib/sidekiq/worker.rb +0 -244
@@ -1,105 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "connection_pool"
|
4
|
-
require "redis"
|
5
4
|
require "uri"
|
5
|
+
require "sidekiq/redis_client_adapter"
|
6
6
|
|
7
7
|
module Sidekiq
|
8
|
-
|
8
|
+
module RedisConnection
|
9
9
|
class << self
|
10
10
|
def create(options = {})
|
11
11
|
symbolized_options = options.transform_keys(&:to_sym)
|
12
|
+
symbolized_options[:url] ||= determine_redis_provider
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
size = if symbolized_options[:size]
|
18
|
-
symbolized_options[:size]
|
19
|
-
elsif Sidekiq.server?
|
20
|
-
# Give ourselves plenty of connections. pool is lazy
|
21
|
-
# so we won't create them until we need them.
|
22
|
-
Sidekiq.options[:concurrency] + 5
|
23
|
-
elsif ENV["RAILS_MAX_THREADS"]
|
24
|
-
Integer(ENV["RAILS_MAX_THREADS"])
|
25
|
-
else
|
26
|
-
5
|
27
|
-
end
|
14
|
+
logger = symbolized_options.delete(:logger)
|
15
|
+
logger&.info { "Sidekiq #{Sidekiq::VERSION} connecting to Redis with options #{scrub(symbolized_options)}" }
|
28
16
|
|
29
|
-
|
17
|
+
size = symbolized_options.delete(:size) || 5
|
18
|
+
pool_timeout = symbolized_options.delete(:pool_timeout) || 1
|
19
|
+
pool_name = symbolized_options.delete(:pool_name)
|
30
20
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
35
|
-
build_client(symbolized_options)
|
21
|
+
redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
|
22
|
+
ConnectionPool.new(timeout: pool_timeout, size: size, name: pool_name) do
|
23
|
+
redis_config.new_client
|
36
24
|
end
|
37
25
|
end
|
38
26
|
|
39
27
|
private
|
40
28
|
|
41
|
-
|
42
|
-
#
|
43
|
-
# We need a connection for each Processor.
|
44
|
-
# We need a connection for Pro's real-time change listener
|
45
|
-
# We need a connection to various features to call Redis every few seconds:
|
46
|
-
# - the process heartbeat.
|
47
|
-
# - enterprise's leader election
|
48
|
-
# - enterprise's cron support
|
49
|
-
def verify_sizing(size, concurrency)
|
50
|
-
raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2)
|
51
|
-
end
|
52
|
-
|
53
|
-
def build_client(options)
|
54
|
-
namespace = options[:namespace]
|
55
|
-
|
56
|
-
client = Redis.new client_opts(options)
|
57
|
-
if namespace
|
58
|
-
begin
|
59
|
-
require "redis/namespace"
|
60
|
-
Redis::Namespace.new(namespace, redis: client)
|
61
|
-
rescue LoadError
|
62
|
-
Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \
|
63
|
-
"Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.")
|
64
|
-
exit(-127)
|
65
|
-
end
|
66
|
-
else
|
67
|
-
client
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def client_opts(options)
|
72
|
-
opts = options.dup
|
73
|
-
if opts[:namespace]
|
74
|
-
opts.delete(:namespace)
|
75
|
-
end
|
76
|
-
|
77
|
-
if opts[:network_timeout]
|
78
|
-
opts[:timeout] = opts[:network_timeout]
|
79
|
-
opts.delete(:network_timeout)
|
80
|
-
end
|
81
|
-
|
82
|
-
opts[:driver] ||= Redis::Connection.drivers.last || "ruby"
|
83
|
-
|
84
|
-
# Issue #3303, redis-rb will silently retry an operation.
|
85
|
-
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
86
|
-
# is performed twice but I believe this is much, much rarer
|
87
|
-
# than the reconnect silently fixing a problem; we keep it
|
88
|
-
# on by default.
|
89
|
-
opts[:reconnect_attempts] ||= 1
|
90
|
-
|
91
|
-
opts
|
92
|
-
end
|
93
|
-
|
94
|
-
def log_info(options)
|
29
|
+
def scrub(options)
|
95
30
|
redacted = "REDACTED"
|
96
31
|
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
|
101
|
-
keys = options.keys
|
102
|
-
keys.delete(:ssl_params)
|
32
|
+
# Deep clone so we can muck with these options all we want and exclude
|
33
|
+
# params from dump-and-load that may contain objects that Marshal is
|
34
|
+
# unable to safely dump.
|
35
|
+
keys = options.keys - [:logger, :ssl_params]
|
103
36
|
scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
|
104
37
|
if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
|
105
38
|
uri.password = redacted
|
@@ -111,11 +44,7 @@ module Sidekiq
|
|
111
44
|
scrubbed_options[:sentinels]&.each do |sentinel|
|
112
45
|
sentinel[:password] = redacted if sentinel[:password]
|
113
46
|
end
|
114
|
-
|
115
|
-
Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}")
|
116
|
-
else
|
117
|
-
Sidekiq.logger.debug("#{Sidekiq::NAME} client with redis options #{scrubbed_options}")
|
118
|
-
end
|
47
|
+
scrubbed_options
|
119
48
|
end
|
120
49
|
|
121
50
|
def determine_redis_provider
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
class RingBuffer
|
5
|
+
include Enumerable
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@buf, :[], :each, :size
|
8
|
+
|
9
|
+
def initialize(size, default = 0)
|
10
|
+
@size = size
|
11
|
+
@buf = Array.new(size, default)
|
12
|
+
@index = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(element)
|
16
|
+
@buf[@index % @size] = element
|
17
|
+
@index += 1
|
18
|
+
element
|
19
|
+
end
|
20
|
+
|
21
|
+
def buffer
|
22
|
+
@buf
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset(default = 0)
|
26
|
+
@buf.fill(default)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -1,37 +1,66 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "sidekiq"
|
4
|
-
require "sidekiq/
|
5
|
-
require "sidekiq/api"
|
4
|
+
require "sidekiq/component"
|
6
5
|
|
7
6
|
module Sidekiq
|
8
7
|
module Scheduled
|
9
8
|
SETS = %w[retry schedule]
|
10
9
|
|
11
10
|
class Enq
|
12
|
-
|
11
|
+
include Sidekiq::Component
|
12
|
+
|
13
|
+
LUA_ZPOPBYSCORE = <<~LUA
|
14
|
+
local key, now = KEYS[1], ARGV[1]
|
15
|
+
local jobs = redis.call("zrange", key, "-inf", now, "byscore", "limit", 0, 1)
|
16
|
+
if jobs[1] then
|
17
|
+
redis.call("zrem", key, jobs[1])
|
18
|
+
return jobs[1]
|
19
|
+
end
|
20
|
+
LUA
|
21
|
+
|
22
|
+
def initialize(container)
|
23
|
+
@config = container
|
24
|
+
@client = Sidekiq::Client.new(config: container)
|
25
|
+
@done = false
|
26
|
+
@lua_zpopbyscore_sha = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def enqueue_jobs(sorted_sets = SETS)
|
13
30
|
# A job's "score" in Redis is the time at which it should be processed.
|
14
31
|
# Just check Redis for the set of jobs with a timestamp before now.
|
15
|
-
|
32
|
+
redis do |conn|
|
16
33
|
sorted_sets.each do |sorted_set|
|
17
|
-
# Get next
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# the queue, it's because another process already popped it so we can move on to the
|
25
|
-
# next one.
|
26
|
-
if conn.zrem(sorted_set, job)
|
27
|
-
Sidekiq::Client.push(Sidekiq.load_json(job))
|
28
|
-
Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
|
29
|
-
end
|
30
|
-
end
|
34
|
+
# Get next item in the queue with score (time to execute) <= now.
|
35
|
+
# We need to go through the list one at a time to reduce the risk of something
|
36
|
+
# going wrong between the time jobs are popped from the scheduled queue and when
|
37
|
+
# they are pushed onto a work queue and losing the jobs.
|
38
|
+
while !@done && (job = zpopbyscore(conn, keys: [sorted_set], argv: [Time.now.to_f.to_s]))
|
39
|
+
@client.push(Sidekiq.load_json(job))
|
40
|
+
logger.debug { "enqueued #{sorted_set}: #{job}" }
|
31
41
|
end
|
32
42
|
end
|
33
43
|
end
|
34
44
|
end
|
45
|
+
|
46
|
+
def terminate
|
47
|
+
@done = true
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def zpopbyscore(conn, keys: nil, argv: nil)
|
53
|
+
if @lua_zpopbyscore_sha.nil?
|
54
|
+
@lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE)
|
55
|
+
end
|
56
|
+
|
57
|
+
conn.call("EVALSHA", @lua_zpopbyscore_sha, keys.size, *keys, *argv)
|
58
|
+
rescue RedisClient::CommandError => e
|
59
|
+
raise unless e.message.start_with?("NOSCRIPT")
|
60
|
+
|
61
|
+
@lua_zpopbyscore_sha = nil
|
62
|
+
retry
|
63
|
+
end
|
35
64
|
end
|
36
65
|
|
37
66
|
##
|
@@ -40,12 +69,13 @@ module Sidekiq
|
|
40
69
|
# just pops the job back onto its original queue so the
|
41
70
|
# workers can pick it up like any other job.
|
42
71
|
class Poller
|
43
|
-
include
|
72
|
+
include Sidekiq::Component
|
44
73
|
|
45
74
|
INITIAL_WAIT = 10
|
46
75
|
|
47
|
-
def initialize
|
48
|
-
@
|
76
|
+
def initialize(config)
|
77
|
+
@config = config
|
78
|
+
@enq = (config[:scheduled_enq] || Sidekiq::Scheduled::Enq).new(config)
|
49
79
|
@sleeper = ConnectionPool::TimedStack.new
|
50
80
|
@done = false
|
51
81
|
@thread = nil
|
@@ -55,12 +85,10 @@ module Sidekiq
|
|
55
85
|
# Shut down this instance, will pause until the thread is dead.
|
56
86
|
def terminate
|
57
87
|
@done = true
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
t.value
|
63
|
-
end
|
88
|
+
@enq.terminate
|
89
|
+
|
90
|
+
@sleeper << 0
|
91
|
+
@thread&.value
|
64
92
|
end
|
65
93
|
|
66
94
|
def start
|
@@ -71,7 +99,7 @@ module Sidekiq
|
|
71
99
|
enqueue
|
72
100
|
wait
|
73
101
|
end
|
74
|
-
|
102
|
+
logger.info("Scheduler exiting...")
|
75
103
|
}
|
76
104
|
end
|
77
105
|
|
@@ -118,13 +146,16 @@ module Sidekiq
|
|
118
146
|
# As we run more processes, the scheduling interval average will approach an even spread
|
119
147
|
# between 0 and poll interval so we don't need this artifical boost.
|
120
148
|
#
|
121
|
-
|
149
|
+
count = process_count
|
150
|
+
interval = poll_interval_average(count)
|
151
|
+
|
152
|
+
if count < 10
|
122
153
|
# For small clusters, calculate a random interval that is ±50% the desired average.
|
123
|
-
|
154
|
+
interval * rand + interval.to_f / 2
|
124
155
|
else
|
125
156
|
# With 10+ processes, we should have enough randomness to get decent polling
|
126
157
|
# across the entire timespan
|
127
|
-
|
158
|
+
interval * rand
|
128
159
|
end
|
129
160
|
end
|
130
161
|
|
@@ -141,38 +172,64 @@ module Sidekiq
|
|
141
172
|
# the same time: the thundering herd problem.
|
142
173
|
#
|
143
174
|
# We only do this if poll_interval_average is unset (the default).
|
144
|
-
def poll_interval_average
|
145
|
-
|
175
|
+
def poll_interval_average(count)
|
176
|
+
@config[:poll_interval_average] || scaled_poll_interval(count)
|
146
177
|
end
|
147
178
|
|
148
179
|
# Calculates an average poll interval based on the number of known Sidekiq processes.
|
149
180
|
# This minimizes a single point of failure by dispersing check-ins but without taxing
|
150
181
|
# Redis if you run many Sidekiq processes.
|
151
|
-
def scaled_poll_interval
|
152
|
-
process_count *
|
182
|
+
def scaled_poll_interval(process_count)
|
183
|
+
process_count * @config[:average_scheduled_poll_interval]
|
153
184
|
end
|
154
185
|
|
155
186
|
def process_count
|
156
|
-
|
157
|
-
# expensive at scale. Cut it down by 90% with this counter.
|
158
|
-
# NB: This method is only called by the scheduler thread so we
|
159
|
-
# don't need to worry about the thread safety of +=.
|
160
|
-
pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
|
187
|
+
pcount = Sidekiq.redis { |conn| conn.scard("processes") }
|
161
188
|
pcount = 1 if pcount == 0
|
162
|
-
@count_calls += 1
|
163
189
|
pcount
|
164
190
|
end
|
165
191
|
|
192
|
+
# A copy of Sidekiq::ProcessSet#cleanup because server
|
193
|
+
# should never depend on sidekiq/api.
|
194
|
+
def cleanup
|
195
|
+
# dont run cleanup more than once per minute
|
196
|
+
return 0 unless redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) }
|
197
|
+
|
198
|
+
count = 0
|
199
|
+
redis do |conn|
|
200
|
+
procs = conn.sscan("processes").to_a
|
201
|
+
heartbeats = conn.pipelined { |pipeline|
|
202
|
+
procs.each do |key|
|
203
|
+
pipeline.hget(key, "info")
|
204
|
+
end
|
205
|
+
}
|
206
|
+
|
207
|
+
# the hash named key has an expiry of 60 seconds.
|
208
|
+
# if it's not found, that means the process has not reported
|
209
|
+
# in to Redis and probably died.
|
210
|
+
to_prune = procs.select.with_index { |proc, i|
|
211
|
+
heartbeats[i].nil?
|
212
|
+
}
|
213
|
+
count = conn.srem("processes", to_prune) unless to_prune.empty?
|
214
|
+
end
|
215
|
+
count
|
216
|
+
end
|
217
|
+
|
166
218
|
def initial_wait
|
167
|
-
# Have all processes sleep between 5-15 seconds.
|
168
|
-
#
|
219
|
+
# Have all processes sleep between 5-15 seconds. 10 seconds to give time for
|
220
|
+
# the heartbeat to register (if the poll interval is going to be calculated by the number
|
169
221
|
# of workers), and 5 random seconds to ensure they don't all hit Redis at the same time.
|
170
222
|
total = 0
|
171
|
-
total += INITIAL_WAIT unless
|
223
|
+
total += INITIAL_WAIT unless @config[:poll_interval_average]
|
172
224
|
total += (5 * rand)
|
173
225
|
|
174
226
|
@sleeper.pop(total)
|
175
227
|
rescue Timeout::Error
|
228
|
+
ensure
|
229
|
+
# periodically clean out the `processes` set in Redis which can collect
|
230
|
+
# references to dead processes over time. The process count affects how
|
231
|
+
# often we scan for scheduled jobs.
|
232
|
+
cleanup
|
176
233
|
end
|
177
234
|
end
|
178
235
|
end
|
@@ -4,7 +4,7 @@ require "sidekiq/testing"
|
|
4
4
|
|
5
5
|
##
|
6
6
|
# The Sidekiq inline infrastructure overrides perform_async so that it
|
7
|
-
# actually calls perform instead. This allows
|
7
|
+
# actually calls perform instead. This allows jobs to be run inline in a
|
8
8
|
# testing environment.
|
9
9
|
#
|
10
10
|
# This is similar to `Resque.inline = true` functionality.
|
@@ -15,8 +15,8 @@ require "sidekiq/testing"
|
|
15
15
|
#
|
16
16
|
# $external_variable = 0
|
17
17
|
#
|
18
|
-
# class
|
19
|
-
# include Sidekiq::
|
18
|
+
# class ExternalJob
|
19
|
+
# include Sidekiq::Job
|
20
20
|
#
|
21
21
|
# def perform
|
22
22
|
# $external_variable = 1
|
@@ -24,7 +24,7 @@ require "sidekiq/testing"
|
|
24
24
|
# end
|
25
25
|
#
|
26
26
|
# assert_equal 0, $external_variable
|
27
|
-
#
|
27
|
+
# ExternalJob.perform_async
|
28
28
|
# assert_equal 1, $external_variable
|
29
29
|
#
|
30
30
|
Sidekiq::Testing.inline!
|
data/lib/sidekiq/testing.rb
CHANGED
@@ -51,19 +51,10 @@ module Sidekiq
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def server_middleware
|
54
|
-
@server_chain ||= Middleware::Chain.new
|
54
|
+
@server_chain ||= Middleware::Chain.new(Sidekiq.default_configuration)
|
55
55
|
yield @server_chain if block_given?
|
56
56
|
@server_chain
|
57
57
|
end
|
58
|
-
|
59
|
-
def constantize(str)
|
60
|
-
names = str.split("::")
|
61
|
-
names.shift if names.empty? || names.first.empty?
|
62
|
-
|
63
|
-
names.inject(Object) do |constant, name|
|
64
|
-
constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
65
|
-
end
|
66
|
-
end
|
67
58
|
end
|
68
59
|
end
|
69
60
|
|
@@ -83,7 +74,7 @@ module Sidekiq
|
|
83
74
|
true
|
84
75
|
elsif Sidekiq::Testing.inline?
|
85
76
|
payloads.each do |job|
|
86
|
-
klass =
|
77
|
+
klass = Object.const_get(job["class"])
|
87
78
|
job["id"] ||= SecureRandom.hex(12)
|
88
79
|
job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
|
89
80
|
klass.process_job(job_hash)
|
@@ -101,20 +92,20 @@ module Sidekiq
|
|
101
92
|
##
|
102
93
|
# The Queues class is only for testing the fake queue implementation.
|
103
94
|
# There are 2 data structures involved in tandem. This is due to the
|
104
|
-
# Rspec syntax of change(
|
95
|
+
# Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
|
105
96
|
# to the array. Because the array was dervied from a filter of the total
|
106
97
|
# jobs enqueued, it appeared as though the array didn't change.
|
107
98
|
#
|
108
99
|
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
109
|
-
# on the queue, and another with keys of the
|
110
|
-
#
|
100
|
+
# on the queue, and another with keys of the job type, so the array for
|
101
|
+
# HardJob.jobs is a straight reference to a real array.
|
111
102
|
#
|
112
103
|
# Queue-based hash:
|
113
104
|
#
|
114
105
|
# {
|
115
106
|
# "default"=>[
|
116
107
|
# {
|
117
|
-
# "class"=>"TestTesting::
|
108
|
+
# "class"=>"TestTesting::HardJob",
|
118
109
|
# "args"=>[1, 2],
|
119
110
|
# "retry"=>true,
|
120
111
|
# "queue"=>"default",
|
@@ -124,12 +115,12 @@ module Sidekiq
|
|
124
115
|
# ]
|
125
116
|
# }
|
126
117
|
#
|
127
|
-
#
|
118
|
+
# Job-based hash:
|
128
119
|
#
|
129
120
|
# {
|
130
|
-
# "TestTesting::
|
121
|
+
# "TestTesting::HardJob"=>[
|
131
122
|
# {
|
132
|
-
# "class"=>"TestTesting::
|
123
|
+
# "class"=>"TestTesting::HardJob",
|
133
124
|
# "args"=>[1, 2],
|
134
125
|
# "retry"=>true,
|
135
126
|
# "queue"=>"default",
|
@@ -144,14 +135,14 @@ module Sidekiq
|
|
144
135
|
# require 'sidekiq/testing'
|
145
136
|
#
|
146
137
|
# assert_equal 0, Sidekiq::Queues["default"].size
|
147
|
-
#
|
138
|
+
# HardJob.perform_async(:something)
|
148
139
|
# assert_equal 1, Sidekiq::Queues["default"].size
|
149
140
|
# assert_equal :something, Sidekiq::Queues["default"].first['args'][0]
|
150
141
|
#
|
151
|
-
# You can also clear all
|
142
|
+
# You can also clear all jobs:
|
152
143
|
#
|
153
144
|
# assert_equal 0, Sidekiq::Queues["default"].size
|
154
|
-
#
|
145
|
+
# HardJob.perform_async(:something)
|
155
146
|
# Sidekiq::Queues.clear_all
|
156
147
|
# assert_equal 0, Sidekiq::Queues["default"].size
|
157
148
|
#
|
@@ -170,35 +161,36 @@ module Sidekiq
|
|
170
161
|
|
171
162
|
def push(queue, klass, job)
|
172
163
|
jobs_by_queue[queue] << job
|
173
|
-
|
164
|
+
jobs_by_class[klass] << job
|
174
165
|
end
|
175
166
|
|
176
167
|
def jobs_by_queue
|
177
168
|
@jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
|
178
169
|
end
|
179
170
|
|
180
|
-
def
|
181
|
-
@
|
171
|
+
def jobs_by_class
|
172
|
+
@jobs_by_class ||= Hash.new { |hash, key| hash[key] = [] }
|
182
173
|
end
|
174
|
+
alias_method :jobs_by_worker, :jobs_by_class
|
183
175
|
|
184
176
|
def delete_for(jid, queue, klass)
|
185
177
|
jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
|
186
|
-
|
178
|
+
jobs_by_class[klass].delete_if { |job| job["jid"] == jid }
|
187
179
|
end
|
188
180
|
|
189
181
|
def clear_for(queue, klass)
|
190
|
-
jobs_by_queue[queue].clear
|
191
|
-
|
182
|
+
jobs_by_queue[queue.to_s].clear
|
183
|
+
jobs_by_class[klass].clear
|
192
184
|
end
|
193
185
|
|
194
186
|
def clear_all
|
195
187
|
jobs_by_queue.clear
|
196
|
-
|
188
|
+
jobs_by_class.clear
|
197
189
|
end
|
198
190
|
end
|
199
191
|
end
|
200
192
|
|
201
|
-
module
|
193
|
+
module Job
|
202
194
|
##
|
203
195
|
# The Sidekiq testing infrastructure overrides perform_async
|
204
196
|
# so that it does not actually touch the network. Instead it
|
@@ -212,43 +204,27 @@ module Sidekiq
|
|
212
204
|
#
|
213
205
|
# require 'sidekiq/testing'
|
214
206
|
#
|
215
|
-
# assert_equal 0,
|
216
|
-
#
|
217
|
-
# assert_equal 1,
|
218
|
-
# assert_equal :something,
|
219
|
-
#
|
220
|
-
# assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
|
221
|
-
# MyMailer.delay.send_welcome_email('foo@example.com')
|
222
|
-
# assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
|
207
|
+
# assert_equal 0, HardJob.jobs.size
|
208
|
+
# HardJob.perform_async(:something)
|
209
|
+
# assert_equal 1, HardJob.jobs.size
|
210
|
+
# assert_equal :something, HardJob.jobs[0]['args'][0]
|
223
211
|
#
|
224
|
-
# You can also clear and drain all
|
212
|
+
# You can also clear and drain all job types:
|
225
213
|
#
|
226
|
-
#
|
227
|
-
# assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
|
228
|
-
#
|
229
|
-
# MyMailer.delay.send_welcome_email('foo@example.com')
|
230
|
-
# MyModel.delay.do_something_hard
|
231
|
-
#
|
232
|
-
# assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size
|
233
|
-
# assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size
|
234
|
-
#
|
235
|
-
# Sidekiq::Worker.clear_all # or .drain_all
|
236
|
-
#
|
237
|
-
# assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size
|
238
|
-
# assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size
|
214
|
+
# Sidekiq::Job.clear_all # or .drain_all
|
239
215
|
#
|
240
216
|
# This can be useful to make sure jobs don't linger between tests:
|
241
217
|
#
|
242
218
|
# RSpec.configure do |config|
|
243
219
|
# config.before(:each) do
|
244
|
-
# Sidekiq::
|
220
|
+
# Sidekiq::Job.clear_all
|
245
221
|
# end
|
246
222
|
# end
|
247
223
|
#
|
248
224
|
# or for acceptance testing, i.e. with cucumber:
|
249
225
|
#
|
250
226
|
# AfterStep do
|
251
|
-
# Sidekiq::
|
227
|
+
# Sidekiq::Job.drain_all
|
252
228
|
# end
|
253
229
|
#
|
254
230
|
# When I sign up as "foo@example.com"
|
@@ -262,7 +238,7 @@ module Sidekiq
|
|
262
238
|
|
263
239
|
# Jobs queued for this worker
|
264
240
|
def jobs
|
265
|
-
Queues.
|
241
|
+
Queues.jobs_by_class[to_s]
|
266
242
|
end
|
267
243
|
|
268
244
|
# Clear all jobs for this worker
|
@@ -288,11 +264,11 @@ module Sidekiq
|
|
288
264
|
end
|
289
265
|
|
290
266
|
def process_job(job)
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
Sidekiq::Testing.server_middleware.invoke(
|
295
|
-
execute_job(
|
267
|
+
inst = new
|
268
|
+
inst.jid = job["jid"]
|
269
|
+
inst.bid = job["bid"] if inst.respond_to?(:bid=)
|
270
|
+
Sidekiq::Testing.server_middleware.invoke(inst, job, job["queue"]) do
|
271
|
+
execute_job(inst, job["args"])
|
296
272
|
end
|
297
273
|
end
|
298
274
|
|
@@ -306,18 +282,18 @@ module Sidekiq
|
|
306
282
|
Queues.jobs_by_queue.values.flatten
|
307
283
|
end
|
308
284
|
|
309
|
-
# Clear all queued jobs
|
285
|
+
# Clear all queued jobs
|
310
286
|
def clear_all
|
311
287
|
Queues.clear_all
|
312
288
|
end
|
313
289
|
|
314
|
-
# Drain all queued jobs
|
290
|
+
# Drain (execute) all queued jobs
|
315
291
|
def drain_all
|
316
292
|
while jobs.any?
|
317
|
-
|
293
|
+
job_classes = jobs.map { |job| job["class"] }.uniq
|
318
294
|
|
319
|
-
|
320
|
-
|
295
|
+
job_classes.each do |job_class|
|
296
|
+
Object.const_get(job_class).drain
|
321
297
|
end
|
322
298
|
end
|
323
299
|
end
|
@@ -328,13 +304,10 @@ module Sidekiq
|
|
328
304
|
def jobs_for(klass)
|
329
305
|
jobs.select do |job|
|
330
306
|
marshalled = job["args"][0]
|
331
|
-
marshalled.index(klass.to_s) && YAML.
|
307
|
+
marshalled.index(klass.to_s) && YAML.safe_load(marshalled)[0] == klass
|
332
308
|
end
|
333
309
|
end
|
334
310
|
end
|
335
|
-
|
336
|
-
Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
|
337
|
-
Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
|
338
311
|
end
|
339
312
|
|
340
313
|
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
|