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
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'helper'
3
+ require 'sidekiq/exception_handler'
4
+ require 'stringio'
5
+ require 'logger'
6
+
7
+ ExceptionHandlerTestException = Class.new(StandardError)
8
+ TEST_EXCEPTION = ExceptionHandlerTestException.new("Something didn't work!")
9
+
10
+ class Component
11
+ include Sidekiq::ExceptionHandler
12
+
13
+ def invoke_exception(args)
14
+ raise TEST_EXCEPTION
15
+ rescue ExceptionHandlerTestException => e
16
+ handle_exception(e,args)
17
+ end
18
+ end
19
+
20
+ class TestExceptionHandler < Sidekiq::Test
21
+ describe "with mock logger" do
22
+ before do
23
+ @old_logger = Sidekiq.logger
24
+ @str_logger = StringIO.new
25
+ Sidekiq.logger = Logger.new(@str_logger)
26
+ end
27
+
28
+ after do
29
+ Sidekiq.logger = @old_logger
30
+ end
31
+
32
+ it "logs the exception to Sidekiq.logger" do
33
+ Component.new.invoke_exception(:a => 1)
34
+ @str_logger.rewind
35
+ log = @str_logger.readlines
36
+ assert_match(/"a":1/, log[0], "didn't include the context")
37
+ assert_match(/Something didn't work!/, log[1], "didn't include the exception message")
38
+ assert_match(/test\/test_exception_handler.rb/, log[2], "didn't include the backtrace")
39
+ end
40
+
41
+ describe "when the exception does not have a backtrace" do
42
+ it "does not fail" do
43
+ exception = ExceptionHandlerTestException.new
44
+ assert_nil exception.backtrace
45
+
46
+ begin
47
+ Component.new.handle_exception exception
48
+ pass
49
+ rescue StandardError
50
+ flunk "failed handling a nil backtrace"
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'helper'
3
+ require 'sidekiq/api'
4
+ require 'active_record'
5
+ require 'action_mailer'
6
+ Sidekiq::Extensions.enable_delay!
7
+
8
+ class TestExtensions < Sidekiq::Test
9
+ describe 'sidekiq extensions' do
10
+ before do
11
+ Sidekiq.redis = REDIS
12
+ Sidekiq.redis {|c| c.flushdb }
13
+ end
14
+
15
+ class MyModel < ActiveRecord::Base
16
+ def self.long_class_method
17
+ raise "Should not be called!"
18
+ end
19
+ end
20
+
21
+ it 'allows delayed execution of ActiveRecord class methods' do
22
+ assert_equal [], Sidekiq::Queue.all.map(&:name)
23
+ q = Sidekiq::Queue.new
24
+ assert_equal 0, q.size
25
+ MyModel.delay.long_class_method
26
+ assert_equal ['default'], Sidekiq::Queue.all.map(&:name)
27
+ assert_equal 1, q.size
28
+ end
29
+
30
+ it 'uses and stringifies specified options' do
31
+ assert_equal [], Sidekiq::Queue.all.map(&:name)
32
+ q = Sidekiq::Queue.new('notdefault')
33
+ assert_equal 0, q.size
34
+ MyModel.delay(queue: :notdefault).long_class_method
35
+ assert_equal ['notdefault'], Sidekiq::Queue.all.map(&:name)
36
+ assert_equal 1, q.size
37
+ end
38
+
39
+ it 'allows delayed scheduling of AR class methods' do
40
+ ss = Sidekiq::ScheduledSet.new
41
+ assert_equal 0, ss.size
42
+ MyModel.delay_for(5.days).long_class_method
43
+ assert_equal 1, ss.size
44
+ end
45
+
46
+ it 'allows until delayed scheduling of AR class methods' do
47
+ ss = Sidekiq::ScheduledSet.new
48
+ assert_equal 0, ss.size
49
+ MyModel.delay_until(1.day.from_now).long_class_method
50
+ assert_equal 1, ss.size
51
+ end
52
+
53
+ class UserMailer < ActionMailer::Base
54
+ def greetings(a, b)
55
+ raise "Should not be called!"
56
+ end
57
+ end
58
+
59
+ it 'allows delayed delivery of ActionMailer mails' do
60
+ assert_equal [], Sidekiq::Queue.all.map(&:name)
61
+ q = Sidekiq::Queue.new
62
+ assert_equal 0, q.size
63
+ UserMailer.delay.greetings(1, 2)
64
+ assert_equal ['default'], Sidekiq::Queue.all.map(&:name)
65
+ assert_equal 1, q.size
66
+ end
67
+
68
+ it 'allows delayed scheduling of AM mails' do
69
+ ss = Sidekiq::ScheduledSet.new
70
+ assert_equal 0, ss.size
71
+ UserMailer.delay_for(5.days).greetings(1, 2)
72
+ assert_equal 1, ss.size
73
+ end
74
+
75
+ it 'allows until delay scheduling of AM mails' do
76
+ ss = Sidekiq::ScheduledSet.new
77
+ assert_equal 0, ss.size
78
+ UserMailer.delay_until(5.days.from_now).greetings(1, 2)
79
+ assert_equal 1, ss.size
80
+ end
81
+
82
+ class SomeClass
83
+ def self.doit(arg)
84
+ end
85
+ end
86
+
87
+ it 'allows delay of any ole class method' do
88
+ q = Sidekiq::Queue.new
89
+ assert_equal 0, q.size
90
+ SomeClass.delay.doit(Date.today)
91
+ assert_equal 1, q.size
92
+ end
93
+
94
+ module SomeModule
95
+ def self.doit(arg)
96
+ end
97
+ end
98
+
99
+ it 'logs large payloads' do
100
+ output = capture_logging(Logger::WARN) do
101
+ SomeClass.delay.doit('a' * 8192)
102
+ end
103
+ assert_match(/#{SomeClass}.doit job argument is/, output)
104
+ end
105
+
106
+ it 'allows delay of any module class method' do
107
+ q = Sidekiq::Queue.new
108
+ assert_equal 0, q.size
109
+ SomeModule.delay.doit(Date.today)
110
+ assert_equal 1, q.size
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'helper'
3
+ require 'sidekiq/fetch'
4
+
5
+ class TestFetcher < Sidekiq::Test
6
+ describe 'fetcher' do
7
+ before do
8
+ Sidekiq.redis = { :url => REDIS_URL, :namespace => 'fuzzy' }
9
+ Sidekiq.redis do |conn|
10
+ conn.flushdb
11
+ conn.rpush('queue:basic', 'msg')
12
+ end
13
+ end
14
+
15
+ after do
16
+ Sidekiq.redis = REDIS
17
+ end
18
+
19
+ it 'retrieves' do
20
+ fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar'])
21
+ uow = fetch.retrieve_work
22
+ refute_nil uow
23
+ assert_equal 'basic', uow.queue_name
24
+ assert_equal 'msg', uow.job
25
+ q = Sidekiq::Queue.new('basic')
26
+ assert_equal 0, q.size
27
+ uow.requeue
28
+ assert_equal 1, q.size
29
+ assert_nil uow.acknowledge
30
+ end
31
+
32
+ it 'retrieves with strict setting' do
33
+ fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar', 'bar'], :strict => true)
34
+ cmd = fetch.queues_cmd
35
+ assert_equal cmd, ['queue:basic', 'queue:bar', Sidekiq::BasicFetch::TIMEOUT]
36
+ end
37
+
38
+ it 'bulk requeues' do
39
+ q1 = Sidekiq::Queue.new('foo')
40
+ q2 = Sidekiq::Queue.new('bar')
41
+ assert_equal 0, q1.size
42
+ assert_equal 0, q2.size
43
+ uow = Sidekiq::BasicFetch::UnitOfWork
44
+ Sidekiq::BasicFetch.bulk_requeue([uow.new('fuzzy:queue:foo', 'bob'), uow.new('fuzzy:queue:foo', 'bar'), uow.new('fuzzy:queue:bar', 'widget')], {:queues => []})
45
+ assert_equal 2, q1.size
46
+ assert_equal 1, q2.size
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'helper'
3
+ require 'sidekiq/launcher'
4
+
5
+ class TestLauncher < Sidekiq::Test
6
+
7
+ describe 'launcher' do
8
+ before do
9
+ Sidekiq.redis {|c| c.flushdb }
10
+ end
11
+
12
+ def new_manager(opts)
13
+ Sidekiq::Manager.new(opts)
14
+ end
15
+
16
+ describe 'heartbeat' do
17
+ before do
18
+ @mgr = new_manager(options)
19
+ @launcher = Sidekiq::Launcher.new(options)
20
+ @launcher.manager = @mgr
21
+ @id = @launcher.identity
22
+
23
+ Sidekiq::Processor::WORKER_STATE['a'] = {'b' => 1}
24
+
25
+ @proctitle = $0
26
+ end
27
+
28
+ after do
29
+ Sidekiq::Processor::WORKER_STATE.clear
30
+ $0 = @proctitle
31
+ end
32
+
33
+ it 'fires new heartbeat events' do
34
+ i = 0
35
+ Sidekiq.on(:heartbeat) do
36
+ i += 1
37
+ end
38
+ assert_equal 0, i
39
+ @launcher.heartbeat
40
+ assert_equal 1, i
41
+ @launcher.heartbeat
42
+ assert_equal 1, i
43
+ end
44
+
45
+ describe 'when manager is active' do
46
+ before do
47
+ Sidekiq::CLI::PROCTITLES << proc { "xyz" }
48
+ @launcher.heartbeat
49
+ Sidekiq::CLI::PROCTITLES.pop
50
+ end
51
+
52
+ it 'sets useful info to proctitle' do
53
+ assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] xyz", $0
54
+ end
55
+
56
+ it 'stores process info in redis' do
57
+ info = Sidekiq.redis { |c| c.hmget(@id, 'busy') }
58
+ assert_equal ["1"], info
59
+ expires = Sidekiq.redis { |c| c.pttl(@id) }
60
+ assert_in_delta 60000, expires, 500
61
+ end
62
+ end
63
+
64
+ describe 'when manager is stopped' do
65
+ before do
66
+ @launcher.quiet
67
+ @launcher.heartbeat
68
+ end
69
+
70
+ #after do
71
+ #puts system('redis-cli -n 15 keys "*" | while read LINE ; do TTL=`redis-cli -n 15 ttl "$LINE"`; if [ "$TTL" -eq -1 ]; then echo "$LINE"; fi; done;')
72
+ #end
73
+
74
+ it 'indicates stopping status in proctitle' do
75
+ assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] stopping", $0
76
+ end
77
+
78
+ it 'stores process info in redis' do
79
+ info = Sidekiq.redis { |c| c.hmget(@id, 'busy') }
80
+ assert_equal ["1"], info
81
+ expires = Sidekiq.redis { |c| c.pttl(@id) }
82
+ assert_in_delta 60000, expires, 50
83
+ end
84
+ end
85
+ end
86
+
87
+ def options
88
+ { :concurrency => 3, :queues => ['default'], :tag => 'myapp' }
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'helper'
3
+ require 'sidekiq/logging'
4
+
5
+ class TestLogging < Sidekiq::Test
6
+ describe Sidekiq::Logging do
7
+ describe "#with_context" do
8
+ def ctx
9
+ Sidekiq::Logging.logger.formatter.context
10
+ end
11
+
12
+ it "has no context by default" do
13
+ assert_nil ctx
14
+ end
15
+
16
+ it "can add a context" do
17
+ Sidekiq::Logging.with_context "xx" do
18
+ assert_equal " xx", ctx
19
+ end
20
+ assert_nil ctx
21
+ end
22
+
23
+ it "can use multiple contexts" do
24
+ Sidekiq::Logging.with_context "xx" do
25
+ assert_equal " xx", ctx
26
+ Sidekiq::Logging.with_context "yy" do
27
+ assert_equal " xx yy", ctx
28
+ end
29
+ assert_equal " xx", ctx
30
+ end
31
+ assert_nil ctx
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'helper'
3
+ require 'sidekiq/manager'
4
+
5
+ class TestManager < Sidekiq::Test
6
+
7
+ describe 'manager' do
8
+ before do
9
+ Sidekiq.redis {|c| c.flushdb }
10
+ end
11
+
12
+ def new_manager(opts)
13
+ Sidekiq::Manager.new(opts)
14
+ end
15
+
16
+ it 'creates N processor instances' do
17
+ mgr = new_manager(options)
18
+ assert_equal options[:concurrency], mgr.workers.size
19
+ end
20
+
21
+ it 'shuts down the system' do
22
+ mgr = new_manager(options)
23
+ mgr.stop(Time.now)
24
+ end
25
+
26
+ it 'throws away dead processors' do
27
+ mgr = new_manager(options)
28
+ init_size = mgr.workers.size
29
+ processor = mgr.workers.first
30
+ begin
31
+ mgr.processor_died(processor, 'ignored')
32
+
33
+ assert_equal init_size, mgr.workers.size
34
+ refute mgr.workers.include?(processor)
35
+ ensure
36
+ mgr.workers.each {|p| p.terminate(true) }
37
+ end
38
+ end
39
+
40
+ it 'does not support invalid concurrency' do
41
+ assert_raises(ArgumentError) { new_manager(concurrency: 0) }
42
+ assert_raises(ArgumentError) { new_manager(concurrency: -1) }
43
+ end
44
+
45
+ def options
46
+ { :concurrency => 3, :queues => ['default'] }
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'helper'
3
+ require 'sidekiq/middleware/chain'
4
+ require 'sidekiq/processor'
5
+
6
+ class TestMiddleware < Sidekiq::Test
7
+ describe 'middleware chain' do
8
+ before do
9
+ $errors = []
10
+ Sidekiq.redis = REDIS
11
+ end
12
+
13
+ class CustomMiddleware
14
+ def initialize(name, recorder)
15
+ @name = name
16
+ @recorder = recorder
17
+ end
18
+
19
+ def call(*args)
20
+ @recorder << [@name, 'before']
21
+ yield
22
+ @recorder << [@name, 'after']
23
+ end
24
+ end
25
+
26
+ it 'supports custom middleware' do
27
+ chain = Sidekiq::Middleware::Chain.new
28
+ chain.add CustomMiddleware, 1, []
29
+
30
+ assert_equal CustomMiddleware, chain.entries.last.klass
31
+ end
32
+
33
+ class CustomWorker
34
+ $recorder = []
35
+ include Sidekiq::Worker
36
+ def perform(recorder)
37
+ $recorder << ['work_performed']
38
+ end
39
+ end
40
+
41
+ class NonYieldingMiddleware
42
+ def call(*args)
43
+ end
44
+ end
45
+
46
+ class AnotherCustomMiddleware
47
+ def initialize(name, recorder)
48
+ @name = name
49
+ @recorder = recorder
50
+ end
51
+
52
+ def call(*args)
53
+ @recorder << [@name, 'before']
54
+ yield
55
+ @recorder << [@name, 'after']
56
+ end
57
+ end
58
+
59
+ class YetAnotherCustomMiddleware
60
+ def initialize(name, recorder)
61
+ @name = name
62
+ @recorder = recorder
63
+ end
64
+
65
+ def call(*args)
66
+ @recorder << [@name, 'before']
67
+ yield
68
+ @recorder << [@name, 'after']
69
+ end
70
+ end
71
+
72
+ it 'executes middleware in the proper order' do
73
+ msg = Sidekiq.dump_json({ 'class' => CustomWorker.to_s, 'args' => [$recorder] })
74
+
75
+ Sidekiq.server_middleware do |chain|
76
+ # should only add once, second should replace the first
77
+ 2.times { |i| chain.add CustomMiddleware, i.to_s, $recorder }
78
+ chain.insert_before CustomMiddleware, AnotherCustomMiddleware, '2', $recorder
79
+ chain.insert_after AnotherCustomMiddleware, YetAnotherCustomMiddleware, '3', $recorder
80
+ end
81
+
82
+ boss = Minitest::Mock.new
83
+ boss.expect(:options, {:queues => ['default'] }, [])
84
+ boss.expect(:options, {:queues => ['default'] }, [])
85
+ processor = Sidekiq::Processor.new(boss)
86
+ boss.expect(:processor_done, nil, [processor])
87
+ processor.process(Sidekiq::BasicFetch::UnitOfWork.new('queue:default', msg))
88
+ assert_equal %w(2 before 3 before 1 before work_performed 1 after 3 after 2 after), $recorder.flatten
89
+ end
90
+
91
+ it 'correctly replaces middleware when using middleware with options in the initializer' do
92
+ chain = Sidekiq::Middleware::Chain.new
93
+ chain.add NonYieldingMiddleware
94
+ chain.add NonYieldingMiddleware, {:foo => 5}
95
+ assert_equal 1, chain.count
96
+ end
97
+
98
+ it 'correctly prepends middleware' do
99
+ chain = Sidekiq::Middleware::Chain.new
100
+ chain_entries = chain.entries
101
+ chain.add CustomMiddleware
102
+ chain.prepend YetAnotherCustomMiddleware
103
+ assert_equal YetAnotherCustomMiddleware, chain_entries.first.klass
104
+ assert_equal CustomMiddleware, chain_entries.last.klass
105
+ end
106
+
107
+ it 'allows middleware to abruptly stop processing rest of chain' do
108
+ recorder = []
109
+ chain = Sidekiq::Middleware::Chain.new
110
+ chain.add NonYieldingMiddleware
111
+ chain.add CustomMiddleware, 1, recorder
112
+
113
+ final_action = nil
114
+ chain.invoke { final_action = true }
115
+ assert_nil final_action
116
+ assert_equal [], recorder
117
+ end
118
+ end
119
+
120
+ describe 'i18n' do
121
+ before do
122
+ require 'i18n'
123
+ I18n.enforce_available_locales = false
124
+ require 'sidekiq/middleware/i18n'
125
+ end
126
+
127
+ it 'saves and restores locale' do
128
+ I18n.locale = 'fr'
129
+ msg = {}
130
+ mw = Sidekiq::Middleware::I18n::Client.new
131
+ mw.call(nil, msg, nil, nil) { }
132
+ assert_equal :fr, msg['locale']
133
+
134
+ msg['locale'] = 'jp'
135
+ I18n.locale = I18n.default_locale
136
+ assert_equal :en, I18n.locale
137
+ mw = Sidekiq::Middleware::I18n::Server.new
138
+ mw.call(nil, msg, nil) do
139
+ assert_equal :jp, I18n.locale
140
+ end
141
+ assert_equal :en, I18n.locale
142
+ end
143
+
144
+ it 'supports I18n.enforce_available_locales = true' do
145
+ I18n.enforce_available_locales = true
146
+ I18n.available_locales = [:en, :jp]
147
+
148
+ msg = { 'locale' => 'jp' }
149
+ mw = Sidekiq::Middleware::I18n::Server.new
150
+ mw.call(nil, msg, nil) do
151
+ assert_equal :jp, I18n.locale
152
+ end
153
+
154
+ I18n.enforce_available_locales = false
155
+ I18n.available_locales = nil
156
+ end
157
+ end
158
+ end