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.

Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/issue_template.md +1 -6
  3. data/.gitignore +1 -0
  4. data/5.0-Upgrade.md +52 -0
  5. data/Changes.md +14 -6
  6. data/Ent-Changes.md +1 -2
  7. data/Pro-Changes.md +1 -19
  8. data/README.md +2 -2
  9. data/bin/sidekiqctl +1 -1
  10. data/bin/sidekiqload +14 -19
  11. data/lib/sidekiq.rb +3 -12
  12. data/lib/sidekiq/api.rb +30 -31
  13. data/lib/sidekiq/cli.rb +12 -5
  14. data/lib/sidekiq/delay.rb +21 -0
  15. data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
  16. data/lib/sidekiq/job_logger.rb +36 -0
  17. data/lib/sidekiq/job_retry.rb +232 -0
  18. data/lib/sidekiq/launcher.rb +1 -7
  19. data/lib/sidekiq/middleware/server/active_record.rb +9 -0
  20. data/lib/sidekiq/processor.rb +63 -29
  21. data/lib/sidekiq/rails.rb +2 -65
  22. data/lib/sidekiq/testing.rb +0 -6
  23. data/lib/sidekiq/version.rb +1 -1
  24. data/lib/sidekiq/web/application.rb +1 -1
  25. data/lib/sidekiq/web/helpers.rb +1 -2
  26. data/sidekiq.gemspec +2 -2
  27. data/test/config.yml +9 -0
  28. data/test/env_based_config.yml +11 -0
  29. data/test/fake_env.rb +1 -0
  30. data/test/fixtures/en.yml +2 -0
  31. data/test/helper.rb +98 -0
  32. data/test/test_actors.rb +138 -0
  33. data/test/test_api.rb +529 -0
  34. data/test/test_cli.rb +418 -0
  35. data/test/test_client.rb +266 -0
  36. data/test/test_exception_handler.rb +56 -0
  37. data/test/test_extensions.rb +115 -0
  38. data/test/test_fetch.rb +50 -0
  39. data/test/test_launcher.rb +92 -0
  40. data/test/test_logging.rb +35 -0
  41. data/test/test_manager.rb +50 -0
  42. data/test/test_middleware.rb +158 -0
  43. data/test/test_processor.rb +266 -0
  44. data/test/test_rails.rb +22 -0
  45. data/test/test_redis_connection.rb +132 -0
  46. data/test/test_retry.rb +335 -0
  47. data/test/test_retry_exhausted.rb +149 -0
  48. data/test/test_scheduled.rb +115 -0
  49. data/test/test_scheduling.rb +58 -0
  50. data/test/test_sidekiq.rb +107 -0
  51. data/test/test_testing.rb +135 -0
  52. data/test/test_testing_fake.rb +352 -0
  53. data/test/test_testing_inline.rb +93 -0
  54. data/test/test_util.rb +13 -0
  55. data/test/test_web.rb +638 -0
  56. data/test/test_web_auth.rb +54 -0
  57. data/test/test_web_helpers.rb +54 -0
  58. data/test/test_web_sessions.rb +67 -0
  59. data/web/assets/javascripts/dashboard.js +1 -1
  60. data/web/views/_job_info.erb +1 -1
  61. data/web/views/dashboard.erb +2 -2
  62. data/web/views/morgue.erb +0 -2
  63. data/web/views/queue.erb +1 -1
  64. data/web/views/retry.erb +1 -1
  65. metadata +73 -8
  66. data/lib/sidekiq/middleware/server/logging.rb +0 -31
  67. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
  68. 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
- # The reloader also takes care of ActiveRecord but is incompatible with
66
- # the ActiveRecord middleware so make sure it's not in the chain already.
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
@@ -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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sidekiq
3
- VERSION = "4.2.10"
3
+ VERSION = "5.0.0.beta1"
4
4
  end
@@ -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" => (action.type || "text/html"), "Cache-Control" => "no-cache" }
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
@@ -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}=#{CGI.escape(value.to_s)}" : next
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 '^(test|myapp|examples)'`.split("\n")
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
@@ -0,0 +1,9 @@
1
+ ---
2
+ :verbose: false
3
+ :require: ./test/fake_env.rb
4
+ :pidfile: /tmp/sidekiq-config-test.pid
5
+ :logfile: /tmp/sidekiq.log
6
+ :concurrency: 50
7
+ :queues:
8
+ - [<%="very_"%>often, 2]
9
+ - [seldom, 1]
@@ -0,0 +1,11 @@
1
+ ---
2
+ :pidfile: /tmp/sidekiq-config-test.pid
3
+ :concurrency: 50
4
+ staging:
5
+ :verbose: false
6
+ :require: ./test/fake_env.rb
7
+ :logfile: /tmp/sidekiq.log
8
+ :concurrency: 5
9
+ :queues:
10
+ - [<%="very_"%>often, 2]
11
+ - [seldom, 1]
data/test/fake_env.rb ADDED
@@ -0,0 +1 @@
1
+ # frozen_string_literal: true
@@ -0,0 +1,2 @@
1
+ en:
2
+ translated_text: 'Changed text from add locals'
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
@@ -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