sidekiq 7.3.9 → 8.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changes.md +225 -0
- data/README.md +34 -14
- data/bin/kiq +17 -0
- data/bin/lint-herb +13 -0
- data/bin/sidekiqload +12 -10
- data/bin/webload +69 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +107 -58
- data/lib/generators/sidekiq/job_generator.rb +15 -3
- data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
- data/lib/sidekiq/api.rb +310 -83
- data/lib/sidekiq/capsule.rb +10 -7
- data/lib/sidekiq/cli.rb +31 -24
- data/lib/sidekiq/client.rb +36 -18
- data/lib/sidekiq/component.rb +45 -3
- data/lib/sidekiq/config.rb +35 -30
- data/lib/sidekiq/embedded.rb +2 -1
- data/lib/sidekiq/fetch.rb +1 -0
- data/lib/sidekiq/iterable_job.rb +1 -0
- data/lib/sidekiq/job/iterable.rb +46 -18
- data/lib/sidekiq/job.rb +18 -3
- data/lib/sidekiq/job_logger.rb +8 -6
- data/lib/sidekiq/job_retry.rb +45 -18
- data/lib/sidekiq/job_util.rb +5 -1
- data/lib/sidekiq/launcher.rb +22 -12
- data/lib/sidekiq/loader.rb +57 -0
- data/lib/sidekiq/logger.rb +25 -69
- data/lib/sidekiq/manager.rb +2 -2
- data/lib/sidekiq/metrics/query.rb +77 -46
- data/lib/sidekiq/metrics/shared.rb +8 -5
- data/lib/sidekiq/metrics/tracking.rb +12 -7
- data/lib/sidekiq/middleware/current_attributes.rb +11 -19
- data/lib/sidekiq/middleware/i18n.rb +2 -0
- data/lib/sidekiq/monitor.rb +4 -8
- data/lib/sidekiq/paginator.rb +18 -2
- data/lib/sidekiq/processor.rb +26 -18
- data/lib/sidekiq/profiler.rb +73 -0
- data/lib/sidekiq/rails.rb +46 -67
- data/lib/sidekiq/redis_client_adapter.rb +6 -3
- data/lib/sidekiq/redis_connection.rb +16 -5
- data/lib/sidekiq/ring_buffer.rb +1 -0
- data/lib/sidekiq/scheduled.rb +6 -7
- data/lib/sidekiq/test_api.rb +331 -0
- data/lib/sidekiq/testing/inline.rb +2 -30
- data/lib/sidekiq/testing.rb +2 -333
- data/lib/sidekiq/transaction_aware_client.rb +13 -5
- data/lib/sidekiq/tui/controls.rb +53 -0
- data/lib/sidekiq/tui/filtering.rb +53 -0
- data/lib/sidekiq/tui/tabs/base_tab.rb +230 -0
- data/lib/sidekiq/tui/tabs/busy.rb +104 -0
- data/lib/sidekiq/tui/tabs/dead.rb +14 -0
- data/lib/sidekiq/tui/tabs/home.rb +115 -0
- data/lib/sidekiq/tui/tabs/metrics.rb +114 -0
- data/lib/sidekiq/tui/tabs/queues.rb +83 -0
- data/lib/sidekiq/tui/tabs/retries.rb +14 -0
- data/lib/sidekiq/tui/tabs/scheduled.rb +14 -0
- data/lib/sidekiq/tui/tabs/set_tab.rb +91 -0
- data/lib/sidekiq/tui/tabs.rb +15 -0
- data/lib/sidekiq/tui.rb +390 -0
- data/lib/sidekiq/version.rb +2 -2
- data/lib/sidekiq/web/action.rb +146 -83
- data/lib/sidekiq/web/application.rb +372 -328
- data/lib/sidekiq/web/config.rb +117 -0
- data/lib/sidekiq/web/helpers.rb +102 -44
- data/lib/sidekiq/web/router.rb +61 -77
- data/lib/sidekiq/web.rb +65 -151
- data/lib/sidekiq.rb +13 -1
- data/sidekiq.gemspec +9 -8
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status.png +0 -0
- data/web/assets/javascripts/application.js +43 -27
- data/web/assets/javascripts/base-charts.js +30 -16
- data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +1 -1
- data/web/assets/javascripts/metrics.js +16 -34
- data/web/assets/stylesheets/style.css +776 -0
- data/web/locales/ar.yml +3 -1
- data/web/locales/cs.yml +2 -0
- data/web/locales/da.yml +2 -0
- data/web/locales/de.yml +2 -0
- data/web/locales/el.yml +2 -0
- data/web/locales/en.yml +8 -0
- data/web/locales/es.yml +25 -2
- data/web/locales/fa.yml +3 -1
- data/web/locales/fr.yml +3 -1
- data/web/locales/gd.yml +13 -1
- data/web/locales/he.yml +3 -1
- data/web/locales/hi.yml +36 -0
- data/web/locales/it.yml +9 -0
- data/web/locales/ja.yml +2 -0
- data/web/locales/ko.yml +2 -0
- data/web/locales/lt.yml +2 -0
- data/web/locales/nb.yml +2 -0
- data/web/locales/nl.yml +2 -0
- data/web/locales/pl.yml +2 -0
- data/web/locales/{pt-br.yml → pt-BR.yml} +3 -1
- data/web/locales/pt.yml +2 -0
- data/web/locales/ru.yml +2 -0
- data/web/locales/sv.yml +2 -0
- data/web/locales/ta.yml +83 -47
- data/web/locales/tr.yml +2 -0
- data/web/locales/uk.yml +7 -5
- data/web/locales/ur.yml +3 -1
- data/web/locales/vi.yml +2 -0
- data/web/locales/{zh-cn.yml → zh-CN.yml} +86 -73
- data/web/locales/{zh-tw.yml → zh-TW.yml} +3 -1
- data/web/views/_footer.html.erb +32 -0
- data/web/views/_job_info.html.erb +115 -0
- data/web/views/_metrics_period_select.html.erb +15 -0
- data/web/views/_nav.html.erb +45 -0
- data/web/views/_paging.html.erb +26 -0
- data/web/views/_poll_link.html.erb +4 -0
- data/web/views/{_summary.erb → _summary.html.erb} +16 -16
- data/web/views/busy.html.erb +155 -0
- data/web/views/dashboard.html.erb +104 -0
- data/web/views/dead.html.erb +38 -0
- data/web/views/filtering.html.erb +6 -0
- data/web/views/layout.html.erb +26 -0
- data/web/views/metrics.html.erb +85 -0
- data/web/views/metrics_for_job.html.erb +58 -0
- data/web/views/morgue.html.erb +69 -0
- data/web/views/profiles.html.erb +43 -0
- data/web/views/queue.html.erb +57 -0
- data/web/views/queues.html.erb +46 -0
- data/web/views/retries.html.erb +77 -0
- data/web/views/retry.html.erb +39 -0
- data/web/views/scheduled.html.erb +64 -0
- data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
- metadata +65 -47
- data/lib/sidekiq/web/csrf_protection.rb +0 -183
- data/web/assets/stylesheets/application-dark.css +0 -147
- data/web/assets/stylesheets/application-rtl.css +0 -163
- data/web/assets/stylesheets/application.css +0 -759
- data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
- data/web/assets/stylesheets/bootstrap.css +0 -5
- data/web/views/_footer.erb +0 -34
- data/web/views/_job_info.erb +0 -105
- data/web/views/_metrics_period_select.erb +0 -12
- data/web/views/_nav.erb +0 -52
- data/web/views/_paging.erb +0 -25
- data/web/views/_poll_link.erb +0 -4
- data/web/views/_status.erb +0 -4
- data/web/views/busy.erb +0 -148
- data/web/views/dashboard.erb +0 -108
- data/web/views/dead.erb +0 -34
- data/web/views/filtering.erb +0 -6
- data/web/views/layout.erb +0 -42
- data/web/views/metrics.erb +0 -90
- data/web/views/metrics_for_job.erb +0 -59
- data/web/views/morgue.erb +0 -74
- data/web/views/queue.erb +0 -55
- data/web/views/queues.erb +0 -44
- data/web/views/retries.erb +0 -79
- data/web/views/retry.erb +0 -34
- data/web/views/scheduled.erb +0 -56
data/lib/sidekiq/rails.rb
CHANGED
|
@@ -1,84 +1,63 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sidekiq/job"
|
|
4
|
-
require "rails"
|
|
5
|
-
|
|
6
3
|
module Sidekiq
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Rails < ::Rails::Engine
|
|
19
|
-
class Reloader
|
|
20
|
-
def initialize(app = ::Rails.application)
|
|
21
|
-
@app = app
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def call
|
|
25
|
-
params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
|
|
26
|
-
@app.reloader.wrap(**params) do
|
|
27
|
-
yield
|
|
4
|
+
begin
|
|
5
|
+
gem "railties", ">= 7.0"
|
|
6
|
+
require "rails"
|
|
7
|
+
require "sidekiq/job"
|
|
8
|
+
require_relative "../active_job/queue_adapters/sidekiq_adapter"
|
|
9
|
+
|
|
10
|
+
class Rails < ::Rails::Engine
|
|
11
|
+
class Reloader
|
|
12
|
+
def initialize(app = ::Rails.application)
|
|
13
|
+
@app = app
|
|
28
14
|
end
|
|
29
|
-
end
|
|
30
15
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
def call
|
|
17
|
+
params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
|
|
18
|
+
@app.reloader.wrap(**params) do
|
|
19
|
+
yield
|
|
20
|
+
end
|
|
21
|
+
end
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
23
|
+
def inspect
|
|
24
|
+
"#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
|
|
25
|
+
end
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# manually retried, don't automatically die, etc.
|
|
44
|
-
#
|
|
45
|
-
# class SomeJob < ActiveJob::Base
|
|
46
|
-
# queue_as :default
|
|
47
|
-
# sidekiq_options retry: 3, backtrace: 10
|
|
48
|
-
# def perform
|
|
49
|
-
# end
|
|
50
|
-
# end
|
|
51
|
-
initializer "sidekiq.active_job_integration" do
|
|
52
|
-
ActiveSupport.on_load(:active_job) do
|
|
53
|
-
require_relative "../active_job/queue_adapters/sidekiq_adapter"
|
|
54
|
-
include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
|
|
27
|
+
def to_hash
|
|
28
|
+
{app: @app.class.name}
|
|
29
|
+
end
|
|
55
30
|
end
|
|
56
|
-
end
|
|
57
31
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
32
|
+
initializer "sidekiq.backtrace_cleaner" do
|
|
33
|
+
Sidekiq.configure_server do |config|
|
|
34
|
+
config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
|
|
35
|
+
end
|
|
61
36
|
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# This hook happens after all initializers are run, just before returning
|
|
65
|
-
# from config/environment.rb back to sidekiq/cli.rb.
|
|
66
|
-
#
|
|
67
|
-
# None of this matters on the client-side, only within the Sidekiq process itself.
|
|
68
|
-
config.after_initialize do
|
|
69
|
-
Sidekiq.configure_server do |config|
|
|
70
|
-
config[:reloader] = Sidekiq::Rails::Reloader.new
|
|
71
37
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
38
|
+
# This hook happens after all initializers are run, just before returning
|
|
39
|
+
# from config/environment.rb back to sidekiq/cli.rb.
|
|
40
|
+
#
|
|
41
|
+
# None of this matters on the client-side, only within the Sidekiq process itself.
|
|
42
|
+
config.after_initialize do
|
|
43
|
+
Sidekiq.configure_server do |config|
|
|
44
|
+
config[:reloader] = Sidekiq::Rails::Reloader.new
|
|
45
|
+
|
|
46
|
+
# This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
|
|
47
|
+
# it will appear in the Sidekiq console with all of the job context.
|
|
48
|
+
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
|
49
|
+
if ::Rails.logger.respond_to?(:broadcast_to)
|
|
50
|
+
::Rails.logger.broadcast_to(config.logger)
|
|
51
|
+
elsif ::ActiveSupport::Logger.respond_to?(:broadcast)
|
|
52
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
|
53
|
+
else
|
|
54
|
+
::Rails.logger = ::ActiveSupport::BroadcastLogger.new(::Rails.logger, config.logger)
|
|
55
|
+
end
|
|
79
56
|
end
|
|
80
57
|
end
|
|
81
58
|
end
|
|
82
59
|
end
|
|
60
|
+
rescue Gem::LoadError
|
|
61
|
+
# Rails not available or version requirement not met
|
|
83
62
|
end
|
|
84
63
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
3
|
require "redis_client"
|
|
5
4
|
require "redis_client/decorator"
|
|
6
5
|
|
|
@@ -42,8 +41,8 @@ module Sidekiq
|
|
|
42
41
|
# this allows us to use methods like `conn.hmset(...)` instead of having to use
|
|
43
42
|
# redis-client's native `conn.call("hmset", ...)`
|
|
44
43
|
def method_missing(*args, &block)
|
|
45
|
-
warn("[sidekiq#5788] Redis has deprecated the `#{args.first}`command, called at #{caller(1..1)}") if DEPRECATED_COMMANDS.include?(args.first)
|
|
46
|
-
@client.call(*args,
|
|
44
|
+
warn("[sidekiq#5788] Redis has deprecated the `#{args.first}` command, called at #{caller(1..1)}") if DEPRECATED_COMMANDS.include?(args.first)
|
|
45
|
+
@client.call(*args, &block)
|
|
47
46
|
end
|
|
48
47
|
ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
|
|
49
48
|
|
|
@@ -108,6 +107,10 @@ module Sidekiq
|
|
|
108
107
|
# on by default.
|
|
109
108
|
opts[:reconnect_attempts] ||= 1
|
|
110
109
|
|
|
110
|
+
# Identify ourselves to Redis via CLIENT SETINFO so connections
|
|
111
|
+
# are distinguishable in CLIENT LIST / CLIENT INFO output.
|
|
112
|
+
opts[:driver_info] ||= "sidekiq_v#{Sidekiq::VERSION}"
|
|
113
|
+
|
|
111
114
|
opts
|
|
112
115
|
end
|
|
113
116
|
end
|
|
@@ -10,6 +10,8 @@ module Sidekiq
|
|
|
10
10
|
def create(options = {})
|
|
11
11
|
symbolized_options = deep_symbolize_keys(options)
|
|
12
12
|
symbolized_options[:url] ||= determine_redis_provider
|
|
13
|
+
symbolized_options[:password] = wrap(symbolized_options[:password]) if symbolized_options.key?(:password)
|
|
14
|
+
symbolized_options[:sentinel_password] = wrap(symbolized_options[:sentinel_password]) if symbolized_options.key?(:sentinel_password)
|
|
13
15
|
|
|
14
16
|
logger = symbolized_options.delete(:logger)
|
|
15
17
|
logger&.info { "Sidekiq #{Sidekiq::VERSION} connecting to Redis with options #{scrub(symbolized_options)}" }
|
|
@@ -21,7 +23,7 @@ module Sidekiq
|
|
|
21
23
|
|
|
22
24
|
size = symbolized_options.delete(:size) || 5
|
|
23
25
|
pool_timeout = symbolized_options.delete(:pool_timeout) || 1
|
|
24
|
-
|
|
26
|
+
symbolized_options.delete(:pool_name)
|
|
25
27
|
|
|
26
28
|
# Default timeout in redis-client is 1 second, which can be too aggressive
|
|
27
29
|
# if the Sidekiq process is CPU-bound. With 10-15 threads and a thread quantum of 100ms,
|
|
@@ -31,13 +33,22 @@ module Sidekiq
|
|
|
31
33
|
symbolized_options[:timeout] ||= 3
|
|
32
34
|
|
|
33
35
|
redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
|
|
34
|
-
ConnectionPool.new(timeout: pool_timeout, size: size
|
|
36
|
+
ConnectionPool.new(timeout: pool_timeout, size: size) do
|
|
35
37
|
redis_config.new_client
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
private
|
|
40
42
|
|
|
43
|
+
# Wrap hard-coded passwords in a Proc to avoid logging the value
|
|
44
|
+
def wrap(pwd)
|
|
45
|
+
if pwd.is_a?(String)
|
|
46
|
+
->(username) { pwd }
|
|
47
|
+
else
|
|
48
|
+
pwd
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
41
52
|
def deep_symbolize_keys(object)
|
|
42
53
|
case object
|
|
43
54
|
when Hash
|
|
@@ -57,14 +68,14 @@ module Sidekiq
|
|
|
57
68
|
# Deep clone so we can muck with these options all we want and exclude
|
|
58
69
|
# params from dump-and-load that may contain objects that Marshal is
|
|
59
70
|
# unable to safely dump.
|
|
60
|
-
keys = options.keys - [:logger, :ssl_params]
|
|
71
|
+
keys = options.keys - [:logger, :ssl_params, :password, :sentinel_password]
|
|
61
72
|
scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys)))
|
|
62
73
|
if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password
|
|
63
74
|
uri.password = redacted
|
|
64
75
|
scrubbed_options[:url] = uri.to_s
|
|
65
76
|
end
|
|
66
|
-
scrubbed_options[:password] = redacted if
|
|
67
|
-
scrubbed_options[:sentinel_password] = redacted if
|
|
77
|
+
scrubbed_options[:password] = redacted if options.key?(:password)
|
|
78
|
+
scrubbed_options[:sentinel_password] = redacted if options.key?(:sentinel_password)
|
|
68
79
|
scrubbed_options[:sentinels]&.each do |sentinel|
|
|
69
80
|
if sentinel.is_a?(String)
|
|
70
81
|
if (uri = URI(sentinel)) && uri.password
|
data/lib/sidekiq/ring_buffer.rb
CHANGED
data/lib/sidekiq/scheduled.rb
CHANGED
|
@@ -72,6 +72,7 @@ module Sidekiq
|
|
|
72
72
|
include Sidekiq::Component
|
|
73
73
|
|
|
74
74
|
INITIAL_WAIT = 10
|
|
75
|
+
attr_accessor :rnd
|
|
75
76
|
|
|
76
77
|
def initialize(config)
|
|
77
78
|
@config = config
|
|
@@ -80,6 +81,7 @@ module Sidekiq
|
|
|
80
81
|
@done = false
|
|
81
82
|
@thread = nil
|
|
82
83
|
@count_calls = 0
|
|
84
|
+
@rnd = Random.new
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
# Shut down this instance, will pause until the thread is dead.
|
|
@@ -115,9 +117,7 @@ module Sidekiq
|
|
|
115
117
|
private
|
|
116
118
|
|
|
117
119
|
def wait
|
|
118
|
-
@sleeper.pop(random_poll_interval)
|
|
119
|
-
rescue Timeout::Error
|
|
120
|
-
# expected
|
|
120
|
+
@sleeper.pop(timeout: random_poll_interval, exception: false)
|
|
121
121
|
rescue => ex
|
|
122
122
|
# if poll_interval_average hasn't been calculated yet, we can
|
|
123
123
|
# raise an error trying to reach Redis.
|
|
@@ -151,11 +151,11 @@ module Sidekiq
|
|
|
151
151
|
|
|
152
152
|
if count < 10
|
|
153
153
|
# For small clusters, calculate a random interval that is ±50% the desired average.
|
|
154
|
-
interval * rand + interval.to_f / 2
|
|
154
|
+
interval * @rnd.rand + interval.to_f / 2
|
|
155
155
|
else
|
|
156
156
|
# With 10+ processes, we should have enough randomness to get decent polling
|
|
157
157
|
# across the entire timespan
|
|
158
|
-
interval * rand
|
|
158
|
+
interval * @rnd.rand * 2
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
|
|
@@ -223,8 +223,7 @@ module Sidekiq
|
|
|
223
223
|
total += INITIAL_WAIT unless @config[:poll_interval_average]
|
|
224
224
|
total += (5 * rand)
|
|
225
225
|
|
|
226
|
-
@sleeper.pop(total)
|
|
227
|
-
rescue Timeout::Error
|
|
226
|
+
@sleeper.pop(timeout: total, exception: false)
|
|
228
227
|
ensure
|
|
229
228
|
# periodically clean out the `processes` set in Redis which can collect
|
|
230
229
|
# references to dead processes over time. The process count affects how
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "sidekiq"
|
|
5
|
+
|
|
6
|
+
module Sidekiq
|
|
7
|
+
class Testing
|
|
8
|
+
class TestModeAlreadySetError < RuntimeError; end
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :__global_test_mode
|
|
11
|
+
|
|
12
|
+
# Calling without a block sets the global test mode, affecting
|
|
13
|
+
# all threads. Calling with a block only affects the current Thread.
|
|
14
|
+
def __set_test_mode(mode)
|
|
15
|
+
if block_given?
|
|
16
|
+
# Reentrant testing modes will lead to a rat's nest of code which is
|
|
17
|
+
# hard to reason about. You can set the testing mode once globally and
|
|
18
|
+
# you can override that global setting once per-thread.
|
|
19
|
+
raise TestModeAlreadySetError, "Nesting test modes is not supported" if __local_test_mode
|
|
20
|
+
|
|
21
|
+
self.__local_test_mode = mode
|
|
22
|
+
begin
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
self.__local_test_mode = nil
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
self.__global_test_mode = mode
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def __test_mode
|
|
33
|
+
__local_test_mode || __global_test_mode
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def __local_test_mode
|
|
37
|
+
Thread.current[:__sidekiq_test_mode]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def __local_test_mode=(value)
|
|
41
|
+
Thread.current[:__sidekiq_test_mode] = value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def disable!(&block)
|
|
45
|
+
__set_test_mode(:disable, &block)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fake!(&block)
|
|
49
|
+
__set_test_mode(:fake, &block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def inline!(&block)
|
|
53
|
+
__set_test_mode(:inline, &block)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def enabled?
|
|
57
|
+
__test_mode != :disable
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def disabled?
|
|
61
|
+
__test_mode == :disable
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def fake?
|
|
65
|
+
__test_mode == :fake
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def inline?
|
|
69
|
+
__test_mode == :inline
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def server_middleware
|
|
73
|
+
@server_chain ||= Middleware::Chain.new(Sidekiq.default_configuration)
|
|
74
|
+
yield @server_chain if block_given?
|
|
75
|
+
@server_chain
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class EmptyQueueError < RuntimeError; end
|
|
81
|
+
|
|
82
|
+
module TestingClient
|
|
83
|
+
private def atomic_push(conn, payloads)
|
|
84
|
+
if Sidekiq::Testing.fake?
|
|
85
|
+
payloads.each do |job|
|
|
86
|
+
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
87
|
+
job["enqueued_at"] = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) unless job["at"]
|
|
88
|
+
Queues.push(job["queue"], job["class"], job)
|
|
89
|
+
end
|
|
90
|
+
true
|
|
91
|
+
elsif Sidekiq::Testing.inline?
|
|
92
|
+
payloads.each do |job|
|
|
93
|
+
klass = Object.const_get(job["class"])
|
|
94
|
+
job["id"] ||= SecureRandom.hex(12)
|
|
95
|
+
job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
96
|
+
klass.process_job(job_hash)
|
|
97
|
+
end
|
|
98
|
+
true
|
|
99
|
+
else
|
|
100
|
+
super
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Sidekiq::Client.prepend TestingClient
|
|
106
|
+
|
|
107
|
+
module Queues
|
|
108
|
+
##
|
|
109
|
+
# The Queues class is only for testing the fake queue implementation.
|
|
110
|
+
# There are 2 data structures involved in tandem. This is due to the
|
|
111
|
+
# Rspec syntax of change(HardJob.jobs, :size). It keeps a reference
|
|
112
|
+
# to the array. Because the array was derived from a filter of the total
|
|
113
|
+
# jobs enqueued, it appeared as though the array didn't change.
|
|
114
|
+
#
|
|
115
|
+
# To solve this, we'll keep 2 hashes containing the jobs. One with keys based
|
|
116
|
+
# on the queue, and another with keys of the job type, so the array for
|
|
117
|
+
# HardJob.jobs is a straight reference to a real array.
|
|
118
|
+
#
|
|
119
|
+
# Queue-based hash:
|
|
120
|
+
#
|
|
121
|
+
# {
|
|
122
|
+
# "default"=>[
|
|
123
|
+
# {
|
|
124
|
+
# "class"=>"TestTesting::HardJob",
|
|
125
|
+
# "args"=>[1, 2],
|
|
126
|
+
# "retry"=>true,
|
|
127
|
+
# "queue"=>"default",
|
|
128
|
+
# "jid"=>"abc5b065c5c4b27fc1102833",
|
|
129
|
+
# "created_at"=>1447445554.419934
|
|
130
|
+
# }
|
|
131
|
+
# ]
|
|
132
|
+
# }
|
|
133
|
+
#
|
|
134
|
+
# Job-based hash:
|
|
135
|
+
#
|
|
136
|
+
# {
|
|
137
|
+
# "TestTesting::HardJob"=>[
|
|
138
|
+
# {
|
|
139
|
+
# "class"=>"TestTesting::HardJob",
|
|
140
|
+
# "args"=>[1, 2],
|
|
141
|
+
# "retry"=>true,
|
|
142
|
+
# "queue"=>"default",
|
|
143
|
+
# "jid"=>"abc5b065c5c4b27fc1102833",
|
|
144
|
+
# "created_at"=>1447445554.419934
|
|
145
|
+
# }
|
|
146
|
+
# ]
|
|
147
|
+
# }
|
|
148
|
+
#
|
|
149
|
+
# Example:
|
|
150
|
+
#
|
|
151
|
+
# require 'sidekiq/testing'
|
|
152
|
+
#
|
|
153
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
154
|
+
# HardJob.perform_async(:something)
|
|
155
|
+
# assert_equal 1, Sidekiq::Queues["default"].size
|
|
156
|
+
# assert_equal :something, Sidekiq::Queues["default"].first['args'][0]
|
|
157
|
+
#
|
|
158
|
+
# You can also clear all jobs:
|
|
159
|
+
#
|
|
160
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
161
|
+
# HardJob.perform_async(:something)
|
|
162
|
+
# Sidekiq::Queues.clear_all
|
|
163
|
+
# assert_equal 0, Sidekiq::Queues["default"].size
|
|
164
|
+
#
|
|
165
|
+
# This can be useful to make sure jobs don't linger between tests:
|
|
166
|
+
#
|
|
167
|
+
# RSpec.configure do |config|
|
|
168
|
+
# config.before(:each) do
|
|
169
|
+
# Sidekiq::Queues.clear_all
|
|
170
|
+
# end
|
|
171
|
+
# end
|
|
172
|
+
#
|
|
173
|
+
class << self
|
|
174
|
+
def [](queue)
|
|
175
|
+
jobs_by_queue[queue]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def push(queue, klass, job)
|
|
179
|
+
jobs_by_queue[queue] << job
|
|
180
|
+
jobs_by_class[klass] << job
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def jobs_by_queue
|
|
184
|
+
@jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def jobs_by_class
|
|
188
|
+
@jobs_by_class ||= Hash.new { |hash, key| hash[key] = [] }
|
|
189
|
+
end
|
|
190
|
+
alias_method :jobs_by_worker, :jobs_by_class
|
|
191
|
+
|
|
192
|
+
def delete_for(jid, queue, klass)
|
|
193
|
+
jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid }
|
|
194
|
+
jobs_by_class[klass].delete_if { |job| job["jid"] == jid }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def clear_for(queue, klass)
|
|
198
|
+
jobs_by_queue[queue.to_s].clear
|
|
199
|
+
jobs_by_class[klass].clear
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def clear_all
|
|
203
|
+
jobs_by_queue.clear
|
|
204
|
+
jobs_by_class.clear
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
module Job
|
|
210
|
+
##
|
|
211
|
+
# The Sidekiq testing infrastructure overrides perform_async
|
|
212
|
+
# so that it does not actually touch the network. Instead it
|
|
213
|
+
# stores the asynchronous jobs in a per-class array so that
|
|
214
|
+
# their presence/absence can be asserted by your tests.
|
|
215
|
+
#
|
|
216
|
+
# This is similar to ActionMailer's :test delivery_method and its
|
|
217
|
+
# ActionMailer::Base.deliveries array.
|
|
218
|
+
#
|
|
219
|
+
# Example:
|
|
220
|
+
#
|
|
221
|
+
# require 'sidekiq/testing'
|
|
222
|
+
#
|
|
223
|
+
# assert_equal 0, HardJob.jobs.size
|
|
224
|
+
# HardJob.perform_async(:something)
|
|
225
|
+
# assert_equal 1, HardJob.jobs.size
|
|
226
|
+
# assert_equal :something, HardJob.jobs[0]['args'][0]
|
|
227
|
+
#
|
|
228
|
+
# You can also clear and drain all job types:
|
|
229
|
+
#
|
|
230
|
+
# Sidekiq::Job.clear_all # or .drain_all
|
|
231
|
+
#
|
|
232
|
+
# This can be useful to make sure jobs don't linger between tests:
|
|
233
|
+
#
|
|
234
|
+
# RSpec.configure do |config|
|
|
235
|
+
# config.before(:each) do
|
|
236
|
+
# Sidekiq::Job.clear_all
|
|
237
|
+
# end
|
|
238
|
+
# end
|
|
239
|
+
#
|
|
240
|
+
# or for acceptance testing, i.e. with cucumber:
|
|
241
|
+
#
|
|
242
|
+
# AfterStep do
|
|
243
|
+
# Sidekiq::Job.drain_all
|
|
244
|
+
# end
|
|
245
|
+
#
|
|
246
|
+
# When I sign up as "foo@example.com"
|
|
247
|
+
# Then I should receive a welcome email to "foo@example.com"
|
|
248
|
+
#
|
|
249
|
+
module ClassMethods
|
|
250
|
+
# Queue for this worker
|
|
251
|
+
def queue
|
|
252
|
+
get_sidekiq_options["queue"]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Jobs queued for this worker
|
|
256
|
+
def jobs
|
|
257
|
+
Queues.jobs_by_class[to_s]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Clear all jobs for this worker
|
|
261
|
+
def clear
|
|
262
|
+
Queues.clear_for(queue, to_s)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Drain and run all jobs for this worker
|
|
266
|
+
def drain
|
|
267
|
+
while jobs.any?
|
|
268
|
+
next_job = jobs.first
|
|
269
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
|
|
270
|
+
process_job(next_job)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Pop out a single job and perform it
|
|
275
|
+
def perform_one
|
|
276
|
+
raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
|
|
277
|
+
next_job = jobs.first
|
|
278
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
|
|
279
|
+
process_job(next_job)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def process_job(job)
|
|
283
|
+
instance = new
|
|
284
|
+
instance.jid = job["jid"]
|
|
285
|
+
instance.bid = job["bid"] if instance.respond_to?(:bid=)
|
|
286
|
+
Sidekiq::Testing.server_middleware.invoke(instance, job, job["queue"]) do
|
|
287
|
+
execute_job(instance, job["args"])
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def execute_job(worker, args)
|
|
292
|
+
worker.perform(*args)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
class << self
|
|
297
|
+
def jobs # :nodoc:
|
|
298
|
+
Queues.jobs_by_queue.values.flatten
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Clear all queued jobs
|
|
302
|
+
def clear_all
|
|
303
|
+
Queues.clear_all
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Drain (execute) all queued jobs
|
|
307
|
+
def drain_all
|
|
308
|
+
while jobs.any?
|
|
309
|
+
job_classes = jobs.map { |job| job["class"] }.uniq
|
|
310
|
+
|
|
311
|
+
job_classes.each do |job_class|
|
|
312
|
+
Object.const_get(job_class).drain
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
module TestingExtensions
|
|
320
|
+
def jobs_for(klass)
|
|
321
|
+
jobs.select do |job|
|
|
322
|
+
marshalled = job["args"][0]
|
|
323
|
+
marshalled.index(klass.to_s) && YAML.safe_load(marshalled)[0] == klass
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING # rubocop:disable Style/GlobalVars
|
|
330
|
+
warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
|
|
331
|
+
end
|
|
@@ -1,30 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require "sidekiq/testing"
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
# The Sidekiq inline infrastructure overrides perform_async so that it
|
|
7
|
-
# actually calls perform instead. This allows jobs to be run inline in a
|
|
8
|
-
# testing environment.
|
|
9
|
-
#
|
|
10
|
-
# This is similar to `Resque.inline = true` functionality.
|
|
11
|
-
#
|
|
12
|
-
# Example:
|
|
13
|
-
#
|
|
14
|
-
# require 'sidekiq/testing/inline'
|
|
15
|
-
#
|
|
16
|
-
# $external_variable = 0
|
|
17
|
-
#
|
|
18
|
-
# class ExternalJob
|
|
19
|
-
# include Sidekiq::Job
|
|
20
|
-
#
|
|
21
|
-
# def perform
|
|
22
|
-
# $external_variable = 1
|
|
23
|
-
# end
|
|
24
|
-
# end
|
|
25
|
-
#
|
|
26
|
-
# assert_equal 0, $external_variable
|
|
27
|
-
# ExternalJob.perform_async
|
|
28
|
-
# assert_equal 1, $external_variable
|
|
29
|
-
#
|
|
30
|
-
Sidekiq::Testing.inline!
|
|
1
|
+
Sidekiq.testing!(:inline)
|
|
2
|
+
warn('⛔️ `require "sidekiq/testing/inline"` is deprecated and will be removed in Sidekiq 9.0. See https://sidekiq.org/wiki/Testing#new-api')
|