sidekiq 3.4.1 → 4.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -2
  3. data/4.0-Upgrade.md +50 -0
  4. data/COMM-LICENSE +55 -45
  5. data/Changes.md +85 -1
  6. data/Ent-Changes.md +79 -0
  7. data/Gemfile +7 -1
  8. data/Pro-2.0-Upgrade.md +2 -2
  9. data/Pro-3.0-Upgrade.md +46 -0
  10. data/Pro-Changes.md +60 -2
  11. data/README.md +20 -16
  12. data/bin/sidekiq +4 -0
  13. data/bin/sidekiqctl +8 -2
  14. data/bin/sidekiqload +167 -0
  15. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +2 -2
  16. data/lib/generators/sidekiq/templates/worker_test.rb.erb +5 -5
  17. data/lib/sidekiq/api.rb +43 -33
  18. data/lib/sidekiq/cli.rb +41 -42
  19. data/lib/sidekiq/client.rb +5 -10
  20. data/lib/sidekiq/fetch.rb +35 -111
  21. data/lib/sidekiq/launcher.rb +102 -42
  22. data/lib/sidekiq/manager.rb +80 -180
  23. data/lib/sidekiq/middleware/server/logging.rb +13 -8
  24. data/lib/sidekiq/middleware/server/retry_jobs.rb +6 -6
  25. data/lib/sidekiq/processor.rb +126 -97
  26. data/lib/sidekiq/redis_connection.rb +23 -5
  27. data/lib/sidekiq/scheduled.rb +47 -26
  28. data/lib/sidekiq/testing.rb +139 -17
  29. data/lib/sidekiq/util.rb +20 -0
  30. data/lib/sidekiq/version.rb +1 -1
  31. data/lib/sidekiq/web.rb +17 -1
  32. data/lib/sidekiq/web_helpers.rb +33 -5
  33. data/lib/sidekiq/worker.rb +16 -0
  34. data/lib/sidekiq.rb +37 -14
  35. data/sidekiq.gemspec +10 -11
  36. data/test/helper.rb +45 -10
  37. data/test/test_actors.rb +137 -0
  38. data/test/test_api.rb +417 -384
  39. data/test/test_cli.rb +29 -59
  40. data/test/test_client.rb +60 -135
  41. data/test/test_extensions.rb +29 -23
  42. data/test/test_fetch.rb +2 -57
  43. data/test/test_launcher.rb +80 -0
  44. data/test/test_logging.rb +1 -1
  45. data/test/test_manager.rb +16 -131
  46. data/test/test_middleware.rb +3 -5
  47. data/test/test_processor.rb +110 -76
  48. data/test/test_rails.rb +21 -0
  49. data/test/test_redis_connection.rb +0 -1
  50. data/test/test_retry.rb +114 -162
  51. data/test/test_scheduled.rb +11 -17
  52. data/test/test_scheduling.rb +20 -42
  53. data/test/test_sidekiq.rb +46 -16
  54. data/test/test_testing.rb +80 -20
  55. data/test/test_testing_fake.rb +83 -8
  56. data/test/test_testing_inline.rb +3 -3
  57. data/test/test_util.rb +16 -0
  58. data/test/test_web.rb +28 -9
  59. data/test/test_web_helpers.rb +3 -2
  60. data/web/assets/images/favicon.ico +0 -0
  61. data/web/assets/javascripts/application.js +6 -1
  62. data/web/assets/javascripts/dashboard.js +2 -8
  63. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +14 -14
  64. data/web/assets/stylesheets/application.css +33 -56
  65. data/web/locales/de.yml +1 -1
  66. data/web/locales/en.yml +2 -0
  67. data/web/locales/fr.yml +2 -2
  68. data/web/locales/ja.yml +10 -1
  69. data/web/locales/{no.yml → nb.yml} +10 -2
  70. data/web/locales/uk.yml +76 -0
  71. data/web/views/_footer.erb +2 -7
  72. data/web/views/_job_info.erb +5 -1
  73. data/web/views/_nav.erb +2 -2
  74. data/web/views/_poll_js.erb +5 -0
  75. data/web/views/{_poll.erb → _poll_link.erb} +0 -3
  76. data/web/views/busy.erb +2 -1
  77. data/web/views/dead.erb +1 -0
  78. data/web/views/layout.erb +2 -0
  79. data/web/views/morgue.erb +3 -0
  80. data/web/views/queue.erb +1 -0
  81. data/web/views/queues.erb +1 -0
  82. data/web/views/retries.erb +3 -0
  83. data/web/views/retry.erb +1 -0
  84. data/web/views/scheduled.erb +1 -0
  85. data/web/views/scheduled_job_info.erb +1 -0
  86. metadata +75 -55
  87. data/lib/sidekiq/actor.rb +0 -39
  88. data/test/test_worker_generator.rb +0 -17
data/test/test_manager.rb CHANGED
@@ -5,160 +5,45 @@ class TestManager < Sidekiq::Test
5
5
 
6
6
  describe 'manager' do
7
7
  before do
8
- Sidekiq.redis = REDIS
9
8
  Sidekiq.redis {|c| c.flushdb }
10
9
  end
11
10
 
12
11
  def new_manager(opts)
13
- condvar = Minitest::Mock.new
14
- condvar.expect(:signal, nil, [])
15
- Sidekiq::Manager.new(condvar, opts)
12
+ Sidekiq::Manager.new(opts)
16
13
  end
17
14
 
18
15
  it 'creates N processor instances' do
19
16
  mgr = new_manager(options)
20
- assert_equal options[:concurrency], mgr.ready.size
21
- assert_equal [], mgr.busy
22
- end
23
-
24
- it 'assigns work to a processor' do
25
- uow = Object.new
26
- processor = Minitest::Mock.new
27
- processor.expect(:async, processor, [])
28
- processor.expect(:process, nil, [uow])
29
-
30
- mgr = new_manager(options)
31
- mgr.ready << processor
32
- mgr.assign(uow)
33
- assert_equal 1, mgr.busy.size
34
-
35
- processor.verify
36
- end
37
-
38
- it 'requeues work if stopping' do
39
- uow = Minitest::Mock.new
40
- uow.expect(:requeue, nil, [])
41
-
42
- mgr = new_manager(options)
43
- mgr.fetcher = Sidekiq::BasicFetch.new({:queues => []})
44
- mgr.stop
45
- mgr.assign(uow)
46
- uow.verify
17
+ assert_equal options[:concurrency], mgr.workers.size
47
18
  end
48
19
 
49
20
  it 'shuts down the system' do
50
21
  mgr = new_manager(options)
51
- mgr.fetcher = Sidekiq::BasicFetch.new({:queues => []})
52
- mgr.stop
53
-
54
- assert mgr.busy.empty?
55
- assert mgr.ready.empty?
56
- end
57
-
58
- it 'returns finished processors to the ready pool' do
59
- fetcher = MiniTest::Mock.new
60
- fetcher.expect :async, fetcher, []
61
- fetcher.expect :fetch, nil, []
62
- mgr = new_manager(options)
63
- mgr.fetcher = fetcher
64
- init_size = mgr.ready.size
65
- processor = mgr.ready.pop
66
- mgr.busy << processor
67
- mgr.processor_done(processor)
68
-
69
- assert_equal 0, mgr.busy.size
70
- assert_equal init_size, mgr.ready.size
71
- fetcher.verify
22
+ mgr.stop(Time.now)
72
23
  end
73
24
 
74
25
  it 'throws away dead processors' do
75
- fetcher = MiniTest::Mock.new
76
- fetcher.expect :async, fetcher, []
77
- fetcher.expect :fetch, nil, []
78
26
  mgr = new_manager(options)
79
- mgr.fetcher = fetcher
80
- init_size = mgr.ready.size
81
- processor = mgr.ready.pop
82
- mgr.busy << processor
83
- mgr.processor_died(processor, 'ignored')
84
-
85
- assert_equal 0, mgr.busy.size
86
- assert_equal init_size, mgr.ready.size
87
- refute mgr.ready.include?(processor)
88
- fetcher.verify
89
- end
90
-
91
- describe 'heartbeat' do
92
- before do
93
- uow = Object.new
94
-
95
- @processor = Minitest::Mock.new
96
- @processor.expect(:async, @processor, [])
97
- @processor.expect(:process, nil, [uow])
98
-
99
- @mgr = new_manager(options)
100
- @mgr.ready << @processor
101
- @mgr.assign(uow)
102
-
103
- @processor.verify
104
- @proctitle = $0
105
- end
106
-
107
- after do
108
- $0 = @proctitle
109
- end
110
-
111
- describe 'when manager is active' do
112
- before do
113
- Sidekiq::Manager::PROCTITLES << proc { "xyz" }
114
- @mgr.heartbeat('identity', heartbeat_data, Sidekiq.dump_json(heartbeat_data))
115
- Sidekiq::Manager::PROCTITLES.pop
116
- end
117
-
118
- it 'sets useful info to proctitle' do
119
- assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] xyz", $0
120
- end
121
-
122
- it 'stores process info in redis' do
123
- info = Sidekiq.redis { |c| c.hmget('identity', 'busy') }
124
- assert_equal ["1"], info
125
- expires = Sidekiq.redis { |c| c.pttl('identity') }
126
- assert_in_delta 60000, expires, 500
127
- end
27
+ init_size = mgr.workers.size
28
+ processor = mgr.workers.first
29
+ begin
30
+ mgr.processor_died(processor, 'ignored')
31
+
32
+ assert_equal init_size, mgr.workers.size
33
+ refute mgr.workers.include?(processor)
34
+ ensure
35
+ mgr.workers.each {|p| p.terminate(true) }
128
36
  end
37
+ end
129
38
 
130
- describe 'when manager is stopped' do
131
- before do
132
- @processor.expect(:alive?, [])
133
- @processor.expect(:terminate, [])
134
-
135
- @mgr.stop
136
- @mgr.processor_done(@processor)
137
- @mgr.heartbeat('identity', heartbeat_data, Sidekiq.dump_json(heartbeat_data))
138
-
139
- @processor.verify
140
- end
141
-
142
- it 'indicates stopping status in proctitle' do
143
- assert_equal "sidekiq #{Sidekiq::VERSION} myapp [0 of 3 busy] stopping", $0
144
- end
145
-
146
- it 'stores process info in redis' do
147
- info = Sidekiq.redis { |c| c.hmget('identity', 'busy') }
148
- assert_equal ["0"], info
149
- expires = Sidekiq.redis { |c| c.pttl('identity') }
150
- assert_in_delta 60000, expires, 50
151
- end
152
- end
39
+ it 'does not support invalid concurrency' do
40
+ assert_raises(ArgumentError) { new_manager(concurrency: 0) }
41
+ assert_raises(ArgumentError) { new_manager(concurrency: -1) }
153
42
  end
154
43
 
155
44
  def options
156
45
  { :concurrency => 3, :queues => ['default'] }
157
46
  end
158
47
 
159
- def heartbeat_data
160
- { 'concurrency' => 3, 'tag' => 'myapp' }
161
- end
162
48
  end
163
-
164
49
  end
@@ -79,12 +79,10 @@ class TestMiddleware < Sidekiq::Test
79
79
  end
80
80
 
81
81
  boss = Minitest::Mock.new
82
+ boss.expect(:options, {:queues => ['default'] }, [])
83
+ boss.expect(:options, {:queues => ['default'] }, [])
82
84
  processor = Sidekiq::Processor.new(boss)
83
- actor = Minitest::Mock.new
84
- actor.expect(:processor_done, nil, [processor])
85
- actor.expect(:real_thread, nil, [nil, Thread])
86
- boss.expect(:async, actor, [])
87
- boss.expect(:async, actor, [])
85
+ boss.expect(:processor_done, nil, [processor])
88
86
  processor.process(Sidekiq::BasicFetch::UnitOfWork.new('queue:default', msg))
89
87
  assert_equal %w(2 before 3 before 1 before work_performed 1 after 3 after 2 after), $recorder.flatten
90
88
  end
@@ -1,17 +1,19 @@
1
1
  require_relative 'helper'
2
+ require 'sidekiq/fetch'
3
+ require 'sidekiq/cli'
2
4
  require 'sidekiq/processor'
3
5
 
4
6
  class TestProcessor < Sidekiq::Test
5
7
  TestException = Class.new(StandardError)
6
8
  TEST_EXCEPTION = TestException.new("kerboom!")
7
9
 
8
- describe 'with mock setup' do
10
+ describe 'processor' do
9
11
  before do
10
12
  $invokes = 0
11
- @boss = Minitest::Mock.new
12
- @processor = ::Sidekiq::Processor.new(@boss)
13
- Celluloid.logger = nil
14
- Sidekiq.redis = REDIS
13
+ @mgr = Minitest::Mock.new
14
+ @mgr.expect(:options, {:queues => ['default']})
15
+ @mgr.expect(:options, {:queues => ['default']})
16
+ @processor = ::Sidekiq::Processor.new(@mgr)
15
17
  end
16
18
 
17
19
  class MockWorker
@@ -29,13 +31,7 @@ class TestProcessor < Sidekiq::Test
29
31
 
30
32
  it 'processes as expected' do
31
33
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] })
32
- actor = Minitest::Mock.new
33
- actor.expect(:processor_done, nil, [@processor])
34
- actor.expect(:real_thread, nil, [nil, Thread])
35
- @boss.expect(:async, actor, [])
36
- @boss.expect(:async, actor, [])
37
34
  @processor.process(work(msg))
38
- @boss.verify
39
35
  assert_equal 1, $invokes
40
36
  end
41
37
 
@@ -45,100 +41,147 @@ class TestProcessor < Sidekiq::Test
45
41
  @processor.execute_job(worker, [1, 2, 3])
46
42
  end
47
43
 
48
- it 'passes exceptions to ExceptionHandler' do
49
- actor = Minitest::Mock.new
50
- actor.expect(:real_thread, nil, [nil, Thread])
51
- @boss.expect(:async, actor, [])
52
- msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
53
- begin
54
- @processor.process(work(msg))
55
- flunk "Expected #process to raise exception"
56
- rescue TestException
57
- end
58
-
59
- assert_equal 0, $invokes
60
- end
61
-
62
44
  it 're-raises exceptions after handling' do
63
45
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
64
46
  re_raise = false
65
- actor = Minitest::Mock.new
66
- actor.expect(:real_thread, nil, [nil, Thread])
67
- @boss.expect(:async, actor, [])
68
47
 
69
48
  begin
70
49
  @processor.process(work(msg))
50
+ flunk "Expected exception"
71
51
  rescue TestException
72
52
  re_raise = true
73
53
  end
74
54
 
55
+ assert_equal 0, $invokes
75
56
  assert re_raise, "does not re-raise exceptions after handling"
76
57
  end
77
58
 
78
59
  it 'does not modify original arguments' do
79
60
  msg = { 'class' => MockWorker.to_s, 'args' => [['myarg']] }
80
61
  msgstr = Sidekiq.dump_json(msg)
81
- processor = ::Sidekiq::Processor.new(@boss)
82
- actor = Minitest::Mock.new
83
- actor.expect(:processor_done, nil, [processor])
84
- actor.expect(:real_thread, nil, [nil, Thread])
85
- @boss.expect(:async, actor, [])
86
- @boss.expect(:async, actor, [])
87
- processor.process(work(msgstr))
62
+ @mgr.expect(:processor_done, nil, [@processor])
63
+ @processor.process(work(msgstr))
88
64
  assert_equal [['myarg']], msg['args']
89
65
  end
90
66
 
91
- describe 'stats' do
67
+ describe 'acknowledgement' do
68
+ class ExceptionRaisingMiddleware
69
+ def initialize(raise_before_yield, raise_after_yield, skip)
70
+ @raise_before_yield = raise_before_yield
71
+ @raise_after_yield = raise_after_yield
72
+ @skip = skip
73
+ end
74
+
75
+ def call(worker, item, queue)
76
+ raise TEST_EXCEPTION if @raise_before_yield
77
+ yield unless @skip
78
+ raise TEST_EXCEPTION if @raise_after_yield
79
+ end
80
+ end
81
+
82
+ let(:raise_before_yield) { false }
83
+ let(:raise_after_yield) { false }
84
+ let(:skip_job) { false }
85
+ let(:worker_args) { ['myarg'] }
86
+ let(:work) { MiniTest::Mock.new }
87
+
92
88
  before do
93
- Sidekiq.redis {|c| c.flushdb }
89
+ work.expect(:queue_name, 'queue:default')
90
+ work.expect(:job, Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => worker_args }))
91
+ Sidekiq.server_middleware do |chain|
92
+ chain.prepend ExceptionRaisingMiddleware, raise_before_yield, raise_after_yield, skip_job
93
+ end
94
+ end
95
+
96
+ after do
97
+ Sidekiq.server_middleware do |chain|
98
+ chain.remove ExceptionRaisingMiddleware
99
+ end
100
+ work.verify
101
+ end
102
+
103
+ describe 'middleware throws an exception before processing the work' do
104
+ let(:raise_before_yield) { true }
105
+
106
+ it 'does not ack' do
107
+ begin
108
+ @processor.process(work)
109
+ flunk "Expected #process to raise exception"
110
+ rescue TestException
111
+ end
112
+ end
94
113
  end
95
114
 
96
- def with_expire(time)
97
- begin
98
- old = Sidekiq::Processor::STATS_TIMEOUT
99
- silence_warnings { Sidekiq::Processor.const_set(:STATS_TIMEOUT, time) }
100
- yield
101
- ensure
102
- silence_warnings { Sidekiq::Processor.const_set(:STATS_TIMEOUT, old) }
115
+ describe 'middleware throws an exception after processing the work' do
116
+ let(:raise_after_yield) { true }
117
+
118
+ it 'acks the job' do
119
+ work.expect(:acknowledge, nil)
120
+ begin
121
+ @processor.process(work)
122
+ flunk "Expected #process to raise exception"
123
+ rescue TestException
124
+ end
103
125
  end
104
126
  end
105
127
 
128
+ describe 'middleware decides to skip work' do
129
+ let(:skip_job) { true }
130
+
131
+ it 'acks the job' do
132
+ work.expect(:acknowledge, nil)
133
+ @mgr.expect(:processor_done, nil, [@processor])
134
+ @processor.process(work)
135
+ end
136
+ end
137
+
138
+ describe 'worker raises an exception' do
139
+ let(:worker_args) { ['boom'] }
140
+
141
+ it 'acks the job' do
142
+ work.expect(:acknowledge, nil)
143
+ begin
144
+ @processor.process(work)
145
+ flunk "Expected #process to raise exception"
146
+ rescue TestException
147
+ end
148
+ end
149
+ end
150
+
151
+ describe 'everything goes well' do
152
+ it 'acks the job' do
153
+ work.expect(:acknowledge, nil)
154
+ @mgr.expect(:processor_done, nil, [@processor])
155
+ @processor.process(work)
156
+ end
157
+ end
158
+ end
159
+
160
+ describe 'stats' do
161
+ before do
162
+ Sidekiq.redis {|c| c.flushdb }
163
+ end
164
+
106
165
  describe 'when successful' do
107
- let(:processed_today_key) { "stat:processed:#{Time.now.utc.to_date}" }
166
+ let(:processed_today_key) { "stat:processed:#{Time.now.utc.strftime("%Y-%m-%d")}" }
108
167
 
109
168
  def successful_job
110
169
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] })
111
- actor = Minitest::Mock.new
112
- actor.expect(:real_thread, nil, [nil, Thread])
113
- actor.expect(:processor_done, nil, [@processor])
114
- @boss.expect(:async, actor, [])
115
- @boss.expect(:async, actor, [])
170
+ @mgr.expect(:processor_done, nil, [@processor])
116
171
  @processor.process(work(msg))
117
172
  end
118
173
 
119
174
  it 'increments processed stat' do
175
+ Sidekiq::Processor::PROCESSED.value = 0
120
176
  successful_job
121
- assert_equal 1, Sidekiq::Stats.new.processed
122
- end
123
-
124
- it 'expires processed stat' do
125
- successful_job
126
- assert_equal Sidekiq::Processor::STATS_TIMEOUT, Sidekiq.redis { |conn| conn.ttl(processed_today_key) }
127
- end
128
-
129
- it 'increments date processed stat' do
130
- successful_job
131
- assert_equal 1, Sidekiq.redis { |conn| conn.get(processed_today_key) }.to_i
177
+ assert_equal 1, Sidekiq::Processor::PROCESSED.value
132
178
  end
133
179
  end
134
180
 
135
181
  describe 'when failed' do
136
- let(:failed_today_key) { "stat:failed:#{Time.now.utc.to_date}" }
182
+ let(:failed_today_key) { "stat:failed:#{Time.now.utc.strftime("%Y-%m-%d")}" }
137
183
 
138
184
  def failed_job
139
- actor = Minitest::Mock.new
140
- actor.expect(:real_thread, nil, [nil, Thread])
141
- @boss.expect(:async, actor, [])
142
185
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
143
186
  begin
144
187
  @processor.process(work(msg))
@@ -147,18 +190,9 @@ class TestProcessor < Sidekiq::Test
147
190
  end
148
191
 
149
192
  it 'increments failed stat' do
193
+ Sidekiq::Processor::FAILURE.value = 0
150
194
  failed_job
151
- assert_equal 1, Sidekiq::Stats.new.failed
152
- end
153
-
154
- it 'increments date failed stat' do
155
- failed_job
156
- assert_equal 1, Sidekiq.redis { |conn| conn.get(failed_today_key) }.to_i
157
- end
158
-
159
- it 'expires failed stat' do
160
- failed_job
161
- assert_equal Sidekiq::Processor::STATS_TIMEOUT, Sidekiq.redis { |conn| conn.ttl(failed_today_key) }
195
+ assert_equal 1, Sidekiq::Processor::FAILURE.value
162
196
  end
163
197
  end
164
198
  end
@@ -0,0 +1,21 @@
1
+ require_relative 'helper'
2
+
3
+ $HAS_AJ = true
4
+ begin
5
+ require 'active_job'
6
+ rescue LoadError
7
+ $HAS_AJ = false
8
+ end
9
+
10
+ class TestRails < Sidekiq::Test
11
+
12
+ describe 'ActiveJob' do
13
+ it 'does not allow Sidekiq::Worker in AJ::Base classes' do
14
+ ex = assert_raises ArgumentError do
15
+ c = Class.new(ActiveJob::Base)
16
+ c.send(:include, Sidekiq::Worker)
17
+ end
18
+ assert_includes ex.message, "cannot include"
19
+ end if $HAS_AJ
20
+ end
21
+ end
@@ -1,5 +1,4 @@
1
1
  require_relative 'helper'
2
- require 'sidekiq/redis_connection'
3
2
 
4
3
  class TestRedisConnection < Sidekiq::Test
5
4