sidekiq 4.2.10 → 5.0.0.beta1
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/.github/issue_template.md +1 -6
- data/.gitignore +1 -0
- data/5.0-Upgrade.md +52 -0
- data/Changes.md +14 -6
- data/Ent-Changes.md +1 -2
- data/Pro-Changes.md +1 -19
- data/README.md +2 -2
- data/bin/sidekiqctl +1 -1
- data/bin/sidekiqload +14 -19
- data/lib/sidekiq.rb +3 -12
- data/lib/sidekiq/api.rb +30 -31
- data/lib/sidekiq/cli.rb +12 -5
- data/lib/sidekiq/delay.rb +21 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
- data/lib/sidekiq/job_logger.rb +36 -0
- data/lib/sidekiq/job_retry.rb +232 -0
- data/lib/sidekiq/launcher.rb +1 -7
- data/lib/sidekiq/middleware/server/active_record.rb +9 -0
- data/lib/sidekiq/processor.rb +63 -29
- data/lib/sidekiq/rails.rb +2 -65
- data/lib/sidekiq/testing.rb +0 -6
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +1 -1
- data/lib/sidekiq/web/helpers.rb +1 -2
- data/sidekiq.gemspec +2 -2
- data/test/config.yml +9 -0
- data/test/env_based_config.yml +11 -0
- data/test/fake_env.rb +1 -0
- data/test/fixtures/en.yml +2 -0
- data/test/helper.rb +98 -0
- data/test/test_actors.rb +138 -0
- data/test/test_api.rb +529 -0
- data/test/test_cli.rb +418 -0
- data/test/test_client.rb +266 -0
- data/test/test_exception_handler.rb +56 -0
- data/test/test_extensions.rb +115 -0
- data/test/test_fetch.rb +50 -0
- data/test/test_launcher.rb +92 -0
- data/test/test_logging.rb +35 -0
- data/test/test_manager.rb +50 -0
- data/test/test_middleware.rb +158 -0
- data/test/test_processor.rb +266 -0
- data/test/test_rails.rb +22 -0
- data/test/test_redis_connection.rb +132 -0
- data/test/test_retry.rb +335 -0
- data/test/test_retry_exhausted.rb +149 -0
- data/test/test_scheduled.rb +115 -0
- data/test/test_scheduling.rb +58 -0
- data/test/test_sidekiq.rb +107 -0
- data/test/test_testing.rb +135 -0
- data/test/test_testing_fake.rb +352 -0
- data/test/test_testing_inline.rb +93 -0
- data/test/test_util.rb +13 -0
- data/test/test_web.rb +638 -0
- data/test/test_web_auth.rb +54 -0
- data/test/test_web_helpers.rb +54 -0
- data/test/test_web_sessions.rb +67 -0
- data/web/assets/javascripts/dashboard.js +1 -1
- data/web/views/_job_info.erb +1 -1
- data/web/views/dashboard.erb +2 -2
- data/web/views/morgue.erb +0 -2
- data/web/views/queue.erb +1 -1
- data/web/views/retry.erb +1 -1
- metadata +73 -8
- data/lib/sidekiq/middleware/server/logging.rb +0 -31
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
- data/web/locales/fa.yml +0 -79
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
@@ -316,9 +316,3 @@ module Sidekiq
|
|
316
316
|
end
|
317
317
|
end
|
318
318
|
end
|
319
|
-
|
320
|
-
if defined?(::Rails) && !Rails.env.test?
|
321
|
-
puts("**************************************************")
|
322
|
-
puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
|
323
|
-
puts("**************************************************")
|
324
|
-
end
|
data/lib/sidekiq/version.rb
CHANGED
@@ -281,7 +281,7 @@ module Sidekiq
|
|
281
281
|
when :json
|
282
282
|
{ "Content-Type" => "application/json", "Cache-Control" => "no-cache" }
|
283
283
|
when String
|
284
|
-
{ "Content-Type" =>
|
284
|
+
{ "Content-Type" => action.type, "Cache-Control" => "no-cache" }
|
285
285
|
else
|
286
286
|
{ "Content-Type" => "text/html", "Cache-Control" => "no-cache" }
|
287
287
|
end
|
data/lib/sidekiq/web/helpers.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
require 'uri'
|
3
3
|
require 'set'
|
4
4
|
require 'yaml'
|
5
|
-
require 'cgi'
|
6
5
|
|
7
6
|
module Sidekiq
|
8
7
|
# This is not a public API
|
@@ -162,7 +161,7 @@ module Sidekiq
|
|
162
161
|
def qparams(options)
|
163
162
|
options = options.stringify_keys
|
164
163
|
params.merge(options).map do |key, value|
|
165
|
-
SAFE_QPARAMS.include?(key) ? "#{key}=#{
|
164
|
+
SAFE_QPARAMS.include?(key) ? "#{key}=#{value}" : next
|
166
165
|
end.compact.join("&")
|
167
166
|
end
|
168
167
|
|
data/sidekiq.gemspec
CHANGED
@@ -10,8 +10,8 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.license = "LGPL-3.0"
|
11
11
|
|
12
12
|
gem.executables = ['sidekiq', 'sidekiqctl']
|
13
|
-
gem.files = `git ls-files | grep -Ev '^(
|
14
|
-
gem.test_files =
|
13
|
+
gem.files = `git ls-files | grep -Ev '^(myapp|examples)'`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- test/*`.split("\n")
|
15
15
|
gem.name = "sidekiq"
|
16
16
|
gem.require_paths = ["lib"]
|
17
17
|
gem.version = Sidekiq::VERSION
|
data/test/config.yml
ADDED
data/test/fake_env.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# frozen_string_literal: true
|
data/test/helper.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
$TESTING = true
|
3
|
+
# disable minitest/parallel threads
|
4
|
+
ENV["N"] = "0"
|
5
|
+
|
6
|
+
require 'capybara'
|
7
|
+
require 'capybara/dsl'
|
8
|
+
require 'capybara/poltergeist'
|
9
|
+
|
10
|
+
Capybara.register_driver :poltergeist do |app|
|
11
|
+
Capybara::Poltergeist::Driver.new(app,
|
12
|
+
debug: false, js_errors: false, timeout: 180
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def percy_enabled?
|
17
|
+
!(ENV['PERCY_ENABLE'] == '0')
|
18
|
+
end
|
19
|
+
require 'percy/capybara' if percy_enabled?
|
20
|
+
|
21
|
+
if ENV["COVERAGE"]
|
22
|
+
require 'simplecov'
|
23
|
+
SimpleCov.start do
|
24
|
+
add_filter "/test/"
|
25
|
+
add_filter "/myapp/"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = 'test'
|
29
|
+
|
30
|
+
trap 'TSTP' do
|
31
|
+
threads = Thread.list
|
32
|
+
|
33
|
+
puts
|
34
|
+
puts "=" * 80
|
35
|
+
puts "Received TSTP signal; printing all #{threads.count} thread backtraces."
|
36
|
+
|
37
|
+
threads.each do |thr|
|
38
|
+
description = thr == Thread.main ? "Main thread" : thr.inspect
|
39
|
+
puts
|
40
|
+
puts "#{description} backtrace: "
|
41
|
+
puts thr.backtrace.join("\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
puts "=" * 80
|
45
|
+
end
|
46
|
+
|
47
|
+
begin
|
48
|
+
require 'pry-byebug'
|
49
|
+
rescue LoadError
|
50
|
+
end
|
51
|
+
|
52
|
+
require 'minitest/autorun'
|
53
|
+
|
54
|
+
require 'sidekiq'
|
55
|
+
require 'sidekiq/util'
|
56
|
+
Sidekiq.logger.level = Logger::ERROR
|
57
|
+
|
58
|
+
Sidekiq::Test = Minitest::Test
|
59
|
+
|
60
|
+
require 'sidekiq/redis_connection'
|
61
|
+
REDIS_URL = ENV['REDIS_URL'] || 'redis://localhost/15'
|
62
|
+
REDIS = Sidekiq::RedisConnection.create(:url => REDIS_URL, :namespace => 'testy')
|
63
|
+
|
64
|
+
Sidekiq.configure_client do |config|
|
65
|
+
config.redis = { :url => REDIS_URL, :namespace => 'testy' }
|
66
|
+
end
|
67
|
+
|
68
|
+
def capture_logging(lvl=Logger::INFO)
|
69
|
+
old = Sidekiq.logger
|
70
|
+
begin
|
71
|
+
out = StringIO.new
|
72
|
+
logger = Logger.new(out)
|
73
|
+
logger.level = lvl
|
74
|
+
Sidekiq.logger = logger
|
75
|
+
yield
|
76
|
+
out.string
|
77
|
+
ensure
|
78
|
+
Sidekiq.logger = old
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def with_logging(lvl=Logger::DEBUG)
|
83
|
+
old = Sidekiq.logger.level
|
84
|
+
begin
|
85
|
+
Sidekiq.logger.level = lvl
|
86
|
+
yield
|
87
|
+
ensure
|
88
|
+
Sidekiq.logger.level = old
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if percy_enabled?
|
93
|
+
# Initialize and finalize Percy.io
|
94
|
+
Percy::Capybara.initialize_build
|
95
|
+
MiniTest.after_run {
|
96
|
+
Percy::Capybara.finalize_build
|
97
|
+
}
|
98
|
+
end
|
data/test/test_actors.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'helper'
|
3
|
+
require 'sidekiq/cli'
|
4
|
+
require 'sidekiq/fetch'
|
5
|
+
require 'sidekiq/scheduled'
|
6
|
+
require 'sidekiq/processor'
|
7
|
+
|
8
|
+
class TestActors < Sidekiq::Test
|
9
|
+
class JoeWorker
|
10
|
+
include Sidekiq::Worker
|
11
|
+
def perform(slp)
|
12
|
+
raise "boom" if slp == "boom"
|
13
|
+
sleep(slp) if slp > 0
|
14
|
+
$count += 1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'threads' do
|
19
|
+
before do
|
20
|
+
Sidekiq.redis {|c| c.flushdb}
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'scheduler' do
|
24
|
+
it 'can start and stop' do
|
25
|
+
f = Sidekiq::Scheduled::Poller.new
|
26
|
+
f.start
|
27
|
+
f.terminate
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can schedule' do
|
31
|
+
ss = Sidekiq::ScheduledSet.new
|
32
|
+
q = Sidekiq::Queue.new
|
33
|
+
|
34
|
+
JoeWorker.perform_in(0.01, 0)
|
35
|
+
|
36
|
+
assert_equal 0, q.size
|
37
|
+
assert_equal 1, ss.size
|
38
|
+
|
39
|
+
sleep 0.015
|
40
|
+
s = Sidekiq::Scheduled::Poller.new
|
41
|
+
s.enqueue
|
42
|
+
assert_equal 1, q.size
|
43
|
+
assert_equal 0, ss.size
|
44
|
+
s.terminate
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'processor' do
|
49
|
+
before do
|
50
|
+
$count = 0
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'can start and stop' do
|
54
|
+
f = Sidekiq::Processor.new(Mgr.new)
|
55
|
+
f.terminate
|
56
|
+
end
|
57
|
+
|
58
|
+
class Mgr
|
59
|
+
attr_reader :latest_error
|
60
|
+
attr_reader :mutex
|
61
|
+
attr_reader :cond
|
62
|
+
def initialize
|
63
|
+
@mutex = ::Mutex.new
|
64
|
+
@cond = ::ConditionVariable.new
|
65
|
+
end
|
66
|
+
def processor_died(inst, err)
|
67
|
+
@latest_error = err
|
68
|
+
@mutex.synchronize do
|
69
|
+
@cond.signal
|
70
|
+
end
|
71
|
+
end
|
72
|
+
def processor_stopped(inst)
|
73
|
+
@mutex.synchronize do
|
74
|
+
@cond.signal
|
75
|
+
end
|
76
|
+
end
|
77
|
+
def options
|
78
|
+
{ :concurrency => 3, :queues => ['default'] }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'can process' do
|
83
|
+
mgr = Mgr.new
|
84
|
+
|
85
|
+
p = Sidekiq::Processor.new(mgr)
|
86
|
+
JoeWorker.perform_async(0)
|
87
|
+
|
88
|
+
a = $count
|
89
|
+
p.process_one
|
90
|
+
b = $count
|
91
|
+
assert_equal a + 1, b
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'deals with errors' do
|
95
|
+
mgr = Mgr.new
|
96
|
+
|
97
|
+
p = Sidekiq::Processor.new(mgr)
|
98
|
+
JoeWorker.perform_async("boom")
|
99
|
+
q = Sidekiq::Queue.new
|
100
|
+
assert_equal 1, q.size
|
101
|
+
|
102
|
+
a = $count
|
103
|
+
mgr.mutex.synchronize do
|
104
|
+
p.start
|
105
|
+
mgr.cond.wait(mgr.mutex)
|
106
|
+
end
|
107
|
+
b = $count
|
108
|
+
assert_equal a, b
|
109
|
+
|
110
|
+
sleep 0.001
|
111
|
+
assert_equal false, p.thread.status
|
112
|
+
p.terminate(true)
|
113
|
+
refute_nil mgr.latest_error
|
114
|
+
assert_equal RuntimeError, mgr.latest_error.class
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'gracefully kills' do
|
118
|
+
mgr = Mgr.new
|
119
|
+
|
120
|
+
p = Sidekiq::Processor.new(mgr)
|
121
|
+
JoeWorker.perform_async(1)
|
122
|
+
q = Sidekiq::Queue.new
|
123
|
+
assert_equal 1, q.size
|
124
|
+
|
125
|
+
a = $count
|
126
|
+
p.start
|
127
|
+
sleep(0.05)
|
128
|
+
p.terminate
|
129
|
+
p.kill(true)
|
130
|
+
|
131
|
+
b = $count
|
132
|
+
assert_equal a, b
|
133
|
+
assert_equal false, p.thread.status
|
134
|
+
refute mgr.latest_error, mgr.latest_error.to_s
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/test/test_api.rb
ADDED
@@ -0,0 +1,529 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'helper'
|
3
|
+
require 'sidekiq/api'
|
4
|
+
require 'active_job'
|
5
|
+
require 'action_mailer'
|
6
|
+
|
7
|
+
class TestApi < Sidekiq::Test
|
8
|
+
describe 'api' do
|
9
|
+
before do
|
10
|
+
Sidekiq.redis {|c| c.flushdb }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "stats" do
|
14
|
+
it "is initially zero" do
|
15
|
+
s = Sidekiq::Stats.new
|
16
|
+
assert_equal 0, s.processed
|
17
|
+
assert_equal 0, s.failed
|
18
|
+
assert_equal 0, s.enqueued
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "processed" do
|
22
|
+
it "returns number of processed jobs" do
|
23
|
+
Sidekiq.redis { |conn| conn.set("stat:processed", 5) }
|
24
|
+
s = Sidekiq::Stats.new
|
25
|
+
assert_equal 5, s.processed
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "failed" do
|
30
|
+
it "returns number of failed jobs" do
|
31
|
+
Sidekiq.redis { |conn| conn.set("stat:failed", 5) }
|
32
|
+
s = Sidekiq::Stats.new
|
33
|
+
assert_equal 5, s.failed
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "reset" do
|
38
|
+
before do
|
39
|
+
Sidekiq.redis do |conn|
|
40
|
+
conn.set('stat:processed', 5)
|
41
|
+
conn.set('stat:failed', 10)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'will reset all stats by default' do
|
46
|
+
Sidekiq::Stats.new.reset
|
47
|
+
s = Sidekiq::Stats.new
|
48
|
+
assert_equal 0, s.failed
|
49
|
+
assert_equal 0, s.processed
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can reset individual stats' do
|
53
|
+
Sidekiq::Stats.new.reset('failed')
|
54
|
+
s = Sidekiq::Stats.new
|
55
|
+
assert_equal 0, s.failed
|
56
|
+
assert_equal 5, s.processed
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'can accept anything that responds to #to_s' do
|
60
|
+
Sidekiq::Stats.new.reset(:failed)
|
61
|
+
s = Sidekiq::Stats.new
|
62
|
+
assert_equal 0, s.failed
|
63
|
+
assert_equal 5, s.processed
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'ignores anything other than "failed" or "processed"' do
|
67
|
+
Sidekiq::Stats.new.reset((1..10).to_a, ['failed'])
|
68
|
+
s = Sidekiq::Stats.new
|
69
|
+
assert_equal 0, s.failed
|
70
|
+
assert_equal 5, s.processed
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "queues" do
|
75
|
+
it "is initially empty" do
|
76
|
+
s = Sidekiq::Stats::Queues.new
|
77
|
+
assert_equal 0, s.lengths.size
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns a hash of queue and size in order" do
|
81
|
+
Sidekiq.redis do |conn|
|
82
|
+
conn.rpush 'queue:foo', '{}'
|
83
|
+
conn.sadd 'queues', 'foo'
|
84
|
+
|
85
|
+
3.times { conn.rpush 'queue:bar', '{}' }
|
86
|
+
conn.sadd 'queues', 'bar'
|
87
|
+
end
|
88
|
+
|
89
|
+
s = Sidekiq::Stats::Queues.new
|
90
|
+
assert_equal ({ "foo" => 1, "bar" => 3 }), s.lengths
|
91
|
+
assert_equal "bar", s.lengths.first.first
|
92
|
+
|
93
|
+
assert_equal Sidekiq::Stats.new.queues, Sidekiq::Stats::Queues.new.lengths
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "enqueued" do
|
98
|
+
it "returns total enqueued jobs" do
|
99
|
+
Sidekiq.redis do |conn|
|
100
|
+
conn.rpush 'queue:foo', '{}'
|
101
|
+
conn.sadd 'queues', 'foo'
|
102
|
+
|
103
|
+
3.times { conn.rpush 'queue:bar', '{}' }
|
104
|
+
conn.sadd 'queues', 'bar'
|
105
|
+
end
|
106
|
+
|
107
|
+
s = Sidekiq::Stats.new
|
108
|
+
assert_equal 4, s.enqueued
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "over time" do
|
113
|
+
before do
|
114
|
+
require 'active_support/core_ext/time/conversions'
|
115
|
+
@before = Time::DATE_FORMATS[:default]
|
116
|
+
Time::DATE_FORMATS[:default] = "%d/%m/%Y %H:%M:%S"
|
117
|
+
end
|
118
|
+
|
119
|
+
after do
|
120
|
+
Time::DATE_FORMATS[:default] = @before
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "processed" do
|
124
|
+
it 'retrieves hash of dates' do
|
125
|
+
Sidekiq.redis do |c|
|
126
|
+
c.incrby("stat:processed:2012-12-24", 4)
|
127
|
+
c.incrby("stat:processed:2012-12-25", 1)
|
128
|
+
c.incrby("stat:processed:2012-12-26", 6)
|
129
|
+
c.incrby("stat:processed:2012-12-27", 2)
|
130
|
+
end
|
131
|
+
Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do
|
132
|
+
s = Sidekiq::Stats::History.new(2)
|
133
|
+
assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1 }, s.processed)
|
134
|
+
|
135
|
+
s = Sidekiq::Stats::History.new(3)
|
136
|
+
assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed)
|
137
|
+
|
138
|
+
s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25"))
|
139
|
+
assert_equal({ "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "failed" do
|
145
|
+
it 'retrieves hash of dates' do
|
146
|
+
Sidekiq.redis do |c|
|
147
|
+
c.incrby("stat:failed:2012-12-24", 4)
|
148
|
+
c.incrby("stat:failed:2012-12-25", 1)
|
149
|
+
c.incrby("stat:failed:2012-12-26", 6)
|
150
|
+
c.incrby("stat:failed:2012-12-27", 2)
|
151
|
+
end
|
152
|
+
Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do
|
153
|
+
s = Sidekiq::Stats::History.new(2)
|
154
|
+
assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1 }), s.failed
|
155
|
+
|
156
|
+
s = Sidekiq::Stats::History.new(3)
|
157
|
+
assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed
|
158
|
+
|
159
|
+
s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25"))
|
160
|
+
assert_equal ({ "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe 'with an empty database' do
|
168
|
+
it 'shows queue as empty' do
|
169
|
+
q = Sidekiq::Queue.new
|
170
|
+
assert_equal 0, q.size
|
171
|
+
assert_equal 0, q.latency
|
172
|
+
end
|
173
|
+
|
174
|
+
before do
|
175
|
+
ActiveJob::Base.queue_adapter = :sidekiq
|
176
|
+
ActiveJob::Base.logger = nil
|
177
|
+
end
|
178
|
+
|
179
|
+
class ApiMailer < ActionMailer::Base
|
180
|
+
def test_email(*)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class ApiJob < ActiveJob::Base
|
185
|
+
def perform(*)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class ApiWorker
|
190
|
+
include Sidekiq::Worker
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'can enumerate jobs' do
|
194
|
+
q = Sidekiq::Queue.new
|
195
|
+
Time.stub(:now, Time.new(2012, 12, 26)) do
|
196
|
+
ApiWorker.perform_async(1, 'mike')
|
197
|
+
assert_equal ['TestApi::ApiWorker'], q.map(&:klass)
|
198
|
+
|
199
|
+
job = q.first
|
200
|
+
assert_equal 24, job.jid.size
|
201
|
+
assert_equal [1, 'mike'], job.args
|
202
|
+
assert_equal Time.new(2012, 12, 26), job.enqueued_at
|
203
|
+
end
|
204
|
+
assert q.latency > 10_000_000
|
205
|
+
|
206
|
+
q = Sidekiq::Queue.new('other')
|
207
|
+
assert_equal 0, q.size
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'has no enqueued_at time for jobs enqueued in the future' do
|
211
|
+
job_id = ApiWorker.perform_in(100, 1, 'foo')
|
212
|
+
job = Sidekiq::ScheduledSet.new.find_job(job_id)
|
213
|
+
assert_nil job.enqueued_at
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'unwraps delayed jobs' do
|
217
|
+
Sidekiq::Extensions.enable_delay!
|
218
|
+
Sidekiq::Queue.delay.foo(1,2,3)
|
219
|
+
q = Sidekiq::Queue.new
|
220
|
+
x = q.first
|
221
|
+
assert_equal "Sidekiq::Queue.foo", x.display_class
|
222
|
+
assert_equal [1,2,3], x.display_args
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'unwraps ActiveJob jobs' do
|
226
|
+
ApiJob.perform_later(1, 2, 3)
|
227
|
+
q = Sidekiq::Queue.new
|
228
|
+
x = q.first
|
229
|
+
assert_equal "TestApi::ApiJob", x.display_class
|
230
|
+
assert_equal [1,2,3], x.display_args
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'unwraps ActionMailer jobs' do
|
234
|
+
ApiMailer.test_email(1, 2, 3).deliver_later
|
235
|
+
q = Sidekiq::Queue.new('mailers')
|
236
|
+
x = q.first
|
237
|
+
assert_equal "TestApi::ApiMailer#test_email", x.display_class
|
238
|
+
assert_equal [1,2,3], x.display_args
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'has no enqueued_at time for jobs enqueued in the future' do
|
242
|
+
job_id = ApiWorker.perform_in(100, 1, 'foo')
|
243
|
+
job = Sidekiq::ScheduledSet.new.find_job(job_id)
|
244
|
+
assert_nil job.enqueued_at
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'can delete jobs' do
|
248
|
+
q = Sidekiq::Queue.new
|
249
|
+
ApiWorker.perform_async(1, 'mike')
|
250
|
+
assert_equal 1, q.size
|
251
|
+
|
252
|
+
x = q.first
|
253
|
+
assert_equal "TestApi::ApiWorker", x.display_class
|
254
|
+
assert_equal [1,'mike'], x.display_args
|
255
|
+
|
256
|
+
assert_equal [true], q.map(&:delete)
|
257
|
+
assert_equal 0, q.size
|
258
|
+
end
|
259
|
+
|
260
|
+
it "can move scheduled job to queue" do
|
261
|
+
remain_id = ApiWorker.perform_in(100, 1, 'jason')
|
262
|
+
job_id = ApiWorker.perform_in(100, 1, 'jason')
|
263
|
+
job = Sidekiq::ScheduledSet.new.find_job(job_id)
|
264
|
+
q = Sidekiq::Queue.new
|
265
|
+
job.add_to_queue
|
266
|
+
queued_job = q.find_job(job_id)
|
267
|
+
refute_nil queued_job
|
268
|
+
assert_equal queued_job.jid, job_id
|
269
|
+
assert_nil Sidekiq::ScheduledSet.new.find_job(job_id)
|
270
|
+
refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id)
|
271
|
+
end
|
272
|
+
|
273
|
+
it "handles multiple scheduled jobs when moving to queue" do
|
274
|
+
jids = Sidekiq::Client.push_bulk('class' => ApiWorker,
|
275
|
+
'args' => [[1, 'jason'], [2, 'jason']],
|
276
|
+
'at' => Time.now.to_f)
|
277
|
+
assert_equal 2, jids.size
|
278
|
+
(remain_id, job_id) = jids
|
279
|
+
job = Sidekiq::ScheduledSet.new.find_job(job_id)
|
280
|
+
q = Sidekiq::Queue.new
|
281
|
+
job.add_to_queue
|
282
|
+
queued_job = q.find_job(job_id)
|
283
|
+
refute_nil queued_job
|
284
|
+
assert_equal queued_job.jid, job_id
|
285
|
+
assert_nil Sidekiq::ScheduledSet.new.find_job(job_id)
|
286
|
+
refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id)
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'can find job by id in sorted sets' do
|
290
|
+
job_id = ApiWorker.perform_in(100, 1, 'jason')
|
291
|
+
job = Sidekiq::ScheduledSet.new.find_job(job_id)
|
292
|
+
refute_nil job
|
293
|
+
assert_equal job_id, job.jid
|
294
|
+
assert_in_delta job.latency, 0.0, 0.1
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'can remove jobs when iterating over a sorted set' do
|
298
|
+
# scheduled jobs must be greater than SortedSet#each underlying page size
|
299
|
+
51.times do
|
300
|
+
ApiWorker.perform_in(100, 'aaron')
|
301
|
+
end
|
302
|
+
set = Sidekiq::ScheduledSet.new
|
303
|
+
set.map(&:delete)
|
304
|
+
assert_equal set.size, 0
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'can remove jobs when iterating over a queue' do
|
308
|
+
# initial queue size must be greater than Queue#each underlying page size
|
309
|
+
51.times do
|
310
|
+
ApiWorker.perform_async(1, 'aaron')
|
311
|
+
end
|
312
|
+
q = Sidekiq::Queue.new
|
313
|
+
q.map(&:delete)
|
314
|
+
assert_equal q.size, 0
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'can find job by id in queues' do
|
318
|
+
q = Sidekiq::Queue.new
|
319
|
+
job_id = ApiWorker.perform_async(1, 'jason')
|
320
|
+
job = q.find_job(job_id)
|
321
|
+
refute_nil job
|
322
|
+
assert_equal job_id, job.jid
|
323
|
+
end
|
324
|
+
|
325
|
+
it 'can clear a queue' do
|
326
|
+
q = Sidekiq::Queue.new
|
327
|
+
2.times { ApiWorker.perform_async(1, 'mike') }
|
328
|
+
q.clear
|
329
|
+
|
330
|
+
Sidekiq.redis do |conn|
|
331
|
+
refute conn.smembers('queues').include?('foo')
|
332
|
+
refute conn.exists('queue:foo')
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'can fetch by score' do
|
337
|
+
same_time = Time.now.to_f
|
338
|
+
add_retry('bob1', same_time)
|
339
|
+
add_retry('bob2', same_time)
|
340
|
+
r = Sidekiq::RetrySet.new
|
341
|
+
assert_equal 2, r.fetch(same_time).size
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'can fetch by score and jid' do
|
345
|
+
same_time = Time.now.to_f
|
346
|
+
add_retry('bob1', same_time)
|
347
|
+
add_retry('bob2', same_time)
|
348
|
+
r = Sidekiq::RetrySet.new
|
349
|
+
assert_equal 1, r.fetch(same_time, 'bob1').size
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'shows empty retries' do
|
353
|
+
r = Sidekiq::RetrySet.new
|
354
|
+
assert_equal 0, r.size
|
355
|
+
end
|
356
|
+
|
357
|
+
it 'can enumerate retries' do
|
358
|
+
add_retry
|
359
|
+
|
360
|
+
r = Sidekiq::RetrySet.new
|
361
|
+
assert_equal 1, r.size
|
362
|
+
array = r.to_a
|
363
|
+
assert_equal 1, array.size
|
364
|
+
|
365
|
+
retri = array.first
|
366
|
+
assert_equal 'ApiWorker', retri.klass
|
367
|
+
assert_equal 'default', retri.queue
|
368
|
+
assert_equal 'bob', retri.jid
|
369
|
+
assert_in_delta Time.now.to_f, retri.at.to_f, 0.02
|
370
|
+
end
|
371
|
+
|
372
|
+
it 'requires a jid to delete an entry' do
|
373
|
+
start_time = Time.now.to_f
|
374
|
+
add_retry('bob2', Time.now.to_f)
|
375
|
+
assert_raises(ArgumentError) do
|
376
|
+
Sidekiq::RetrySet.new.delete(start_time)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'can delete a single retry from score and jid' do
|
381
|
+
same_time = Time.now.to_f
|
382
|
+
add_retry('bob1', same_time)
|
383
|
+
add_retry('bob2', same_time)
|
384
|
+
r = Sidekiq::RetrySet.new
|
385
|
+
assert_equal 2, r.size
|
386
|
+
Sidekiq::RetrySet.new.delete(same_time, 'bob1')
|
387
|
+
assert_equal 1, r.size
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'can retry a retry' do
|
391
|
+
add_retry
|
392
|
+
r = Sidekiq::RetrySet.new
|
393
|
+
assert_equal 1, r.size
|
394
|
+
r.first.retry
|
395
|
+
assert_equal 0, r.size
|
396
|
+
assert_equal 1, Sidekiq::Queue.new('default').size
|
397
|
+
job = Sidekiq::Queue.new('default').first
|
398
|
+
assert_equal 'bob', job.jid
|
399
|
+
assert_equal 1, job['retry_count']
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'can clear retries' do
|
403
|
+
add_retry
|
404
|
+
add_retry('test')
|
405
|
+
r = Sidekiq::RetrySet.new
|
406
|
+
assert_equal 2, r.size
|
407
|
+
r.clear
|
408
|
+
assert_equal 0, r.size
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'can enumerate processes' do
|
412
|
+
identity_string = "identity_string"
|
413
|
+
odata = {
|
414
|
+
'pid' => 123,
|
415
|
+
'hostname' => Socket.gethostname,
|
416
|
+
'key' => identity_string,
|
417
|
+
'identity' => identity_string,
|
418
|
+
'started_at' => Time.now.to_f - 15,
|
419
|
+
}
|
420
|
+
|
421
|
+
time = Time.now.to_f
|
422
|
+
Sidekiq.redis do |conn|
|
423
|
+
conn.multi do
|
424
|
+
conn.sadd('processes', odata['key'])
|
425
|
+
conn.hmset(odata['key'], 'info', Sidekiq.dump_json(odata), 'busy', 10, 'beat', time)
|
426
|
+
conn.sadd('processes', 'fake:pid')
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
ps = Sidekiq::ProcessSet.new.to_a
|
431
|
+
assert_equal 1, ps.size
|
432
|
+
data = ps.first
|
433
|
+
assert_equal 10, data['busy']
|
434
|
+
assert_equal time, data['beat']
|
435
|
+
assert_equal 123, data['pid']
|
436
|
+
data.quiet!
|
437
|
+
data.stop!
|
438
|
+
signals_string = "#{odata['key']}-signals"
|
439
|
+
assert_equal "TERM", Sidekiq.redis{|c| c.lpop(signals_string) }
|
440
|
+
assert_equal "TSTP", Sidekiq.redis{|c| c.lpop(signals_string) }
|
441
|
+
end
|
442
|
+
|
443
|
+
it 'can enumerate workers' do
|
444
|
+
w = Sidekiq::Workers.new
|
445
|
+
assert_equal 0, w.size
|
446
|
+
w.each do
|
447
|
+
assert false
|
448
|
+
end
|
449
|
+
|
450
|
+
hn = Socket.gethostname
|
451
|
+
key = "#{hn}:#{$$}"
|
452
|
+
pdata = { 'pid' => $$, 'hostname' => hn, 'started_at' => Time.now.to_i }
|
453
|
+
Sidekiq.redis do |conn|
|
454
|
+
conn.sadd('processes', key)
|
455
|
+
conn.hmset(key, 'info', Sidekiq.dump_json(pdata), 'busy', 0, 'beat', Time.now.to_f)
|
456
|
+
end
|
457
|
+
|
458
|
+
s = "#{key}:workers"
|
459
|
+
data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => Time.now.to_i })
|
460
|
+
Sidekiq.redis do |c|
|
461
|
+
c.hmset(s, '1234', data)
|
462
|
+
end
|
463
|
+
|
464
|
+
w.each do |p, x, y|
|
465
|
+
assert_equal key, p
|
466
|
+
assert_equal "1234", x
|
467
|
+
assert_equal 'default', y['queue']
|
468
|
+
assert_equal Time.now.year, Time.at(y['run_at']).year
|
469
|
+
end
|
470
|
+
|
471
|
+
s = "#{key}:workers"
|
472
|
+
data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => (Time.now.to_i - 2*60*60) })
|
473
|
+
Sidekiq.redis do |c|
|
474
|
+
c.multi do
|
475
|
+
c.hmset(s, '5678', data)
|
476
|
+
c.hmset("b#{s}", '5678', data)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
assert_equal ['1234', '5678'], w.map { |_, tid, _| tid }
|
481
|
+
end
|
482
|
+
|
483
|
+
it 'can reschedule jobs' do
|
484
|
+
add_retry('foo1')
|
485
|
+
add_retry('foo2')
|
486
|
+
|
487
|
+
retries = Sidekiq::RetrySet.new
|
488
|
+
assert_equal 2, retries.size
|
489
|
+
refute(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?)
|
490
|
+
|
491
|
+
retries.each do |retri|
|
492
|
+
retri.reschedule(Time.now.to_f + 10) if retri.jid == 'foo2'
|
493
|
+
end
|
494
|
+
|
495
|
+
assert_equal 2, retries.size
|
496
|
+
assert(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?)
|
497
|
+
end
|
498
|
+
|
499
|
+
it 'prunes processes which have died' do
|
500
|
+
data = { 'pid' => rand(10_000), 'hostname' => "app#{rand(1_000)}", 'started_at' => Time.now.to_f }
|
501
|
+
key = "#{data['hostname']}:#{data['pid']}"
|
502
|
+
Sidekiq.redis do |conn|
|
503
|
+
conn.sadd('processes', key)
|
504
|
+
conn.hmset(key, 'info', Sidekiq.dump_json(data), 'busy', 0, 'beat', Time.now.to_f)
|
505
|
+
end
|
506
|
+
|
507
|
+
ps = Sidekiq::ProcessSet.new
|
508
|
+
assert_equal 1, ps.size
|
509
|
+
assert_equal 1, ps.to_a.size
|
510
|
+
|
511
|
+
Sidekiq.redis do |conn|
|
512
|
+
conn.sadd('processes', "bar:987")
|
513
|
+
conn.sadd('processes', "bar:986")
|
514
|
+
end
|
515
|
+
|
516
|
+
ps = Sidekiq::ProcessSet.new
|
517
|
+
assert_equal 1, ps.size
|
518
|
+
assert_equal 1, ps.to_a.size
|
519
|
+
end
|
520
|
+
|
521
|
+
def add_retry(jid = 'bob', at = Time.now.to_f)
|
522
|
+
payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue' => 'default', 'jid' => jid, 'retry_count' => 2, 'failed_at' => Time.now.to_f)
|
523
|
+
Sidekiq.redis do |conn|
|
524
|
+
conn.zadd('retry', at.to_s, payload)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|