sidekiq 2.1.0 → 2.1.1
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.
- data/Changes.md +9 -2
- data/README.md +11 -0
- data/lib/sidekiq/cli.rb +5 -5
- data/lib/sidekiq/exception_handler.rb +30 -0
- data/lib/sidekiq/fetch.rb +3 -1
- data/lib/sidekiq/manager.rb +1 -1
- data/lib/sidekiq/processor.rb +15 -3
- data/lib/sidekiq/scheduled.rb +21 -15
- data/lib/sidekiq/util.rb +4 -3
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web.rb +4 -0
- data/test/test_cli.rb +35 -2
- data/test/test_exception_handler.rb +109 -0
- data/test/test_fetch.rb +13 -0
- data/test/test_middleware.rb +0 -19
- data/test/test_processor.rb +41 -6
- data/test/test_web.rb +12 -4
- data/web/assets/javascripts/application.js +29 -0
- data/web/assets/stylesheets/layout.css +4 -0
- data/web/views/_summary.slim +8 -0
- data/web/views/_workers.slim +14 -0
- data/web/views/index.slim +8 -23
- data/web/views/layout.slim +1 -1
- data/web/views/poll.slim +3 -0
- metadata +10 -5
- data/lib/sidekiq/middleware/server/exception_handler.rb +0 -38
- data/test/test_manager.rb +0 -51
data/Changes.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
2.1.1
|
2
|
+
-----------
|
3
|
+
|
4
|
+
- Handle networking errors causing the scheduler thread to die [#309]
|
5
|
+
- Rework exception handling to log all Processor and actor death (#325, subelsky)
|
6
|
+
- Clone arguments when calling worker so modifications are discarded. (#265, hakanensari)
|
7
|
+
|
1
8
|
2.1.0
|
2
9
|
-----------
|
3
10
|
|
@@ -6,8 +13,8 @@
|
|
6
13
|
usage.
|
7
14
|
- Add pagination within the Web UI [#253]
|
8
15
|
- Specify which Redis driver to use: *hiredis* or *ruby* (default)
|
9
|
-
- Remove FailureJobs and UniqueJobs, which
|
10
|
-
|
16
|
+
- Remove FailureJobs and UniqueJobs, which were optional middleware
|
17
|
+
that I don't want to support in core. [#302]
|
11
18
|
|
12
19
|
2.0.3
|
13
20
|
-----------
|
data/README.md
CHANGED
@@ -27,6 +27,8 @@ Requirements
|
|
27
27
|
I test on Ruby 1.9.3 and JRuby 1.6.x in 1.9 mode. Other versions/VMs are
|
28
28
|
untested but I will do my best to support them. Ruby 1.8 is not supported.
|
29
29
|
|
30
|
+
Redis 2.0 or greater is required.
|
31
|
+
|
30
32
|
|
31
33
|
Installation
|
32
34
|
-----------------
|
@@ -38,6 +40,7 @@ Getting Started
|
|
38
40
|
-----------------
|
39
41
|
|
40
42
|
See the [sidekiq home page](http://mperham.github.com/sidekiq) for the simple 4-step process.
|
43
|
+
You can watch [Railscast #366](http://railscasts.com/episodes/366-sidekiq) to see Sidekiq in action.
|
41
44
|
|
42
45
|
|
43
46
|
More Information
|
@@ -52,6 +55,14 @@ and email to <sidekiq@librelist.org> with a greeting in the body. To unsubscribe
|
|
52
55
|
Once archiving begins, you'll be able to visit [the archives](http://librelist.com/browser/sidekiq/) to see past threads.
|
53
56
|
|
54
57
|
|
58
|
+
Problems?
|
59
|
+
-----------------
|
60
|
+
|
61
|
+
**Please do not directly email any Sidekiq committers with questions or problems.** A community is best served when discussions are held in public.
|
62
|
+
|
63
|
+
If you have a problem, please review the [FAQ](/mperham/sidekiq/wiki/FAQ) and [Troubleshooting](/mperham/sidekiq/wiki/Problems-and-Troubleshooting) wiki pages. Searching the issues for your problem is also a good idea. If that doesn't help, feel free to email the Sidekiq mailing list or open a new issue.
|
64
|
+
|
65
|
+
|
55
66
|
License
|
56
67
|
-----------------
|
57
68
|
|
data/lib/sidekiq/cli.rb
CHANGED
@@ -124,7 +124,6 @@ module Sidekiq
|
|
124
124
|
|
125
125
|
def validate!
|
126
126
|
options[:queues] << 'default' if options[:queues].empty?
|
127
|
-
options[:queues].shuffle!
|
128
127
|
|
129
128
|
if !File.exist?(options[:require]) ||
|
130
129
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
@@ -141,9 +140,10 @@ module Sidekiq
|
|
141
140
|
opts = {}
|
142
141
|
|
143
142
|
@parser = OptionParser.new do |o|
|
144
|
-
|
145
|
-
|
146
|
-
parse_queues(opts,
|
143
|
+
o.on "-q", "--queue QUEUE[,WEIGHT]...", "Queues to process with optional weights" do |arg|
|
144
|
+
queues_and_weights = arg.scan(/(\w+),?(\d*)/)
|
145
|
+
queues_and_weights.each {|queue_and_weight| parse_queues(opts, *queue_and_weight)}
|
146
|
+
opts[:strict] = queues_and_weights.collect(&:last).none? {|weight| weight != ''}
|
147
147
|
end
|
148
148
|
|
149
149
|
o.on "-v", "--verbose", "Print more verbose output" do
|
@@ -208,7 +208,7 @@ module Sidekiq
|
|
208
208
|
end
|
209
209
|
|
210
210
|
def parse_queues(opts, q, weight)
|
211
|
-
|
211
|
+
[weight.to_i, 1].max.times do
|
212
212
|
(opts[:queues] ||= []) << q
|
213
213
|
end
|
214
214
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module ExceptionHandler
|
3
|
+
|
4
|
+
def handle_exception(ex, msg)
|
5
|
+
Sidekiq.logger.warn msg
|
6
|
+
Sidekiq.logger.warn ex
|
7
|
+
Sidekiq.logger.warn ex.backtrace.join("\n")
|
8
|
+
send_to_airbrake(msg, ex) if defined?(::Airbrake)
|
9
|
+
send_to_exceptional(msg, ex) if defined?(::Exceptional)
|
10
|
+
send_to_exception_notifier(msg, ex) if defined?(::ExceptionNotifier)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def send_to_airbrake(msg, ex)
|
16
|
+
::Airbrake.notify(ex, :parameters => msg)
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_to_exceptional(msg, ex)
|
20
|
+
if ::Exceptional::Config.should_send_to_api?
|
21
|
+
::Exceptional.context(msg)
|
22
|
+
::Exceptional::Remote.error(::Exceptional::ExceptionData.new(ex))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_to_exception_notifier(msg, ex)
|
27
|
+
::ExceptionNotifier::Notifier.background_exception_notification(ex, :data => { :message => msg })
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/sidekiq/fetch.rb
CHANGED
@@ -12,8 +12,9 @@ module Sidekiq
|
|
12
12
|
|
13
13
|
TIMEOUT = 1
|
14
14
|
|
15
|
-
def initialize(mgr, queues)
|
15
|
+
def initialize(mgr, queues, strict)
|
16
16
|
@mgr = mgr
|
17
|
+
@strictly_ordered_queues = strict
|
17
18
|
@queues = queues.map { |q| "queue:#{q}" }
|
18
19
|
@unique_queues = @queues.uniq
|
19
20
|
end
|
@@ -68,6 +69,7 @@ module Sidekiq
|
|
68
69
|
# recreate the queue command each time we invoke Redis#blpop
|
69
70
|
# to honor weights and avoid queue starvation.
|
70
71
|
def queues_cmd
|
72
|
+
return @unique_queues.dup << TIMEOUT if @strictly_ordered_queues
|
71
73
|
queues = @queues.sample(@unique_queues.size).uniq
|
72
74
|
queues.concat(@unique_queues - queues)
|
73
75
|
queues << TIMEOUT
|
data/lib/sidekiq/manager.rb
CHANGED
@@ -27,7 +27,7 @@ module Sidekiq
|
|
27
27
|
@in_progress = {}
|
28
28
|
@done = false
|
29
29
|
@busy = []
|
30
|
-
@fetcher = Fetcher.new(current_actor, options[:queues])
|
30
|
+
@fetcher = Fetcher.new(current_actor, options[:queues], !!options[:strict])
|
31
31
|
@ready = @count.times.map { Processor.new_link(current_actor) }
|
32
32
|
procline
|
33
33
|
end
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -2,7 +2,6 @@ require 'celluloid'
|
|
2
2
|
require 'sidekiq/util'
|
3
3
|
|
4
4
|
require 'sidekiq/middleware/server/active_record'
|
5
|
-
require 'sidekiq/middleware/server/exception_handler'
|
6
5
|
require 'sidekiq/middleware/server/retry_jobs'
|
7
6
|
require 'sidekiq/middleware/server/logging'
|
8
7
|
require 'sidekiq/middleware/server/timeout'
|
@@ -20,7 +19,6 @@ module Sidekiq
|
|
20
19
|
|
21
20
|
def self.default_middleware
|
22
21
|
Middleware::Chain.new do |m|
|
23
|
-
m.add Middleware::Server::ExceptionHandler
|
24
22
|
m.add Middleware::Server::Logging
|
25
23
|
m.add Middleware::Server::RetryJobs
|
26
24
|
m.add Middleware::Server::ActiveRecord
|
@@ -39,10 +37,13 @@ module Sidekiq
|
|
39
37
|
|
40
38
|
stats(worker, msg, queue) do
|
41
39
|
Sidekiq.server_middleware.invoke(worker, msg, queue) do
|
42
|
-
worker.perform(*msg['args'])
|
40
|
+
worker.perform(*cloned(msg['args']))
|
43
41
|
end
|
44
42
|
end
|
45
43
|
@boss.processor_done!(current_actor)
|
44
|
+
rescue => ex
|
45
|
+
handle_exception(ex, msg || { :message => msgstr })
|
46
|
+
raise
|
46
47
|
end
|
47
48
|
|
48
49
|
# See http://github.com/tarcieri/celluloid/issues/22
|
@@ -87,7 +88,18 @@ module Sidekiq
|
|
87
88
|
end
|
88
89
|
end
|
89
90
|
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Singleton classes are not clonable.
|
94
|
+
SINGLETON_CLASSES = [ NilClass, TrueClass, FalseClass, Symbol, Fixnum, Float ].freeze
|
90
95
|
|
96
|
+
# Clone the arguments passed to the worker so that if
|
97
|
+
# the message fails, what is pushed back onto Redis hasn't
|
98
|
+
# been mutated by the worker.
|
99
|
+
def cloned(ary)
|
100
|
+
ary.map do |val|
|
101
|
+
SINGLETON_CLASSES.include?(val.class) ? val : val.clone
|
102
|
+
end
|
91
103
|
end
|
92
104
|
|
93
105
|
def hostname
|
data/lib/sidekiq/scheduled.rb
CHANGED
@@ -22,25 +22,31 @@ module Sidekiq
|
|
22
22
|
watchdog('scheduling poller thread died!') do
|
23
23
|
add_jitter if first_time
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
25
|
+
begin
|
26
|
+
# A message's "score" in Redis is the time at which it should be processed.
|
27
|
+
# Just check Redis for the set of messages with a timestamp before now.
|
28
|
+
now = Time.now.to_f.to_s
|
29
|
+
Sidekiq.redis do |conn|
|
30
|
+
SETS.each do |sorted_set|
|
31
|
+
(messages, _) = conn.multi do
|
32
|
+
conn.zrangebyscore(sorted_set, '-inf', now)
|
33
|
+
conn.zremrangebyscore(sorted_set, '-inf', now)
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
messages.each do |message|
|
37
|
+
logger.debug { "enqueued #{sorted_set}: #{message}" }
|
38
|
+
msg = Sidekiq.load_json(message)
|
39
|
+
conn.multi do
|
40
|
+
conn.sadd('queues', msg['queue'])
|
41
|
+
conn.rpush("queue:#{msg['queue']}", message)
|
42
|
+
end
|
41
43
|
end
|
42
44
|
end
|
43
45
|
end
|
46
|
+
rescue SystemCallError => ex
|
47
|
+
# ECONNREFUSED, etc. Most likely a problem with
|
48
|
+
# redis networking. Punt and try again at the next interval
|
49
|
+
logger.warn ex.message
|
44
50
|
end
|
45
51
|
|
46
52
|
after(poll_interval) { poll }
|
data/lib/sidekiq/util.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
require 'sidekiq/exception_handler'
|
2
|
+
|
1
3
|
module Sidekiq
|
2
4
|
##
|
3
5
|
# This module is part of Sidekiq core and not intended for extensions.
|
4
6
|
#
|
5
7
|
module Util
|
8
|
+
include ExceptionHandler
|
6
9
|
|
7
10
|
EXPIRY = 60 * 60
|
8
11
|
|
@@ -20,9 +23,7 @@ module Sidekiq
|
|
20
23
|
def watchdog(last_words)
|
21
24
|
yield
|
22
25
|
rescue => ex
|
23
|
-
|
24
|
-
logger.error ex
|
25
|
-
logger.error ex.backtrace.join("\n")
|
26
|
+
handle_exception(ex, { :context => last_words })
|
26
27
|
end
|
27
28
|
|
28
29
|
def logger
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web.rb
CHANGED
data/test/test_cli.rb
CHANGED
@@ -40,14 +40,29 @@ class TestCli < MiniTest::Unit::TestCase
|
|
40
40
|
assert_equal ['foo'], Sidekiq.options[:queues]
|
41
41
|
end
|
42
42
|
|
43
|
+
it 'sets strictly ordered queues if weights are not present' do
|
44
|
+
@cli.parse(['sidekiq', '-q', 'foo,bar', '-r', './test/fake_env.rb'])
|
45
|
+
assert_equal true, !!Sidekiq.options[:strict]
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'does not set strictly ordered queues if weights are present' do
|
49
|
+
@cli.parse(['sidekiq', '-q', 'foo,3', '-r', './test/fake_env.rb'])
|
50
|
+
assert_equal false, !!Sidekiq.options[:strict]
|
51
|
+
end
|
52
|
+
|
43
53
|
it 'changes timeout' do
|
44
54
|
@cli.parse(['sidekiq', '-t', '30', '-r', './test/fake_env.rb'])
|
45
55
|
assert_equal 30, Sidekiq.options[:timeout]
|
46
56
|
end
|
47
57
|
|
48
|
-
it 'handles multiple queues with weights' do
|
58
|
+
it 'handles multiple queues with weights with multiple switches' do
|
49
59
|
@cli.parse(['sidekiq', '-q', 'foo,3', '-q', 'bar', '-r', './test/fake_env.rb'])
|
50
|
-
assert_equal %w(
|
60
|
+
assert_equal %w(foo foo foo bar), Sidekiq.options[:queues]
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'handles multiple queues with weights with a single switch' do
|
64
|
+
@cli.parse(['sidekiq', '-q', 'bar,foo,3', '-r', './test/fake_env.rb'])
|
65
|
+
assert_equal %w(bar foo foo foo), Sidekiq.options[:queues]
|
51
66
|
end
|
52
67
|
|
53
68
|
it 'sets verbose' do
|
@@ -163,6 +178,24 @@ class TestCli < MiniTest::Unit::TestCase
|
|
163
178
|
assert_equal 3, Sidekiq.options[:queues].count { |q| q == 'seldom' }
|
164
179
|
end
|
165
180
|
end
|
181
|
+
|
182
|
+
describe 'Sidekiq::CLI#parse_queues' do
|
183
|
+
describe 'when weight is present' do
|
184
|
+
it 'concatenates queue to opts[:queues] weight number of times' do
|
185
|
+
opts = {}
|
186
|
+
@cli.send :parse_queues, opts, 'often', 7
|
187
|
+
assert_equal %w[often] * 7, opts[:queues]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe 'when weight is not present' do
|
192
|
+
it 'concatenates queue to opts[:queues] once' do
|
193
|
+
opts = {}
|
194
|
+
@cli.send :parse_queues, opts, 'once', nil
|
195
|
+
assert_equal %w[once], opts[:queues]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
166
199
|
end
|
167
200
|
|
168
201
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'sidekiq'
|
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::Util
|
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 < MiniTest::Unit::TestCase
|
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
|
+
end
|
41
|
+
|
42
|
+
describe "with fake Airbrake" do
|
43
|
+
before do
|
44
|
+
::Airbrake = MiniTest::Mock.new
|
45
|
+
end
|
46
|
+
|
47
|
+
after do
|
48
|
+
Object.send(:remove_const, "Airbrake") # HACK should probably inject Airbrake etc into this class in the future
|
49
|
+
end
|
50
|
+
|
51
|
+
it "notifies Airbrake" do
|
52
|
+
::Airbrake.expect(:notify,nil,[TEST_EXCEPTION,:parameters => { :a => 1 }])
|
53
|
+
Component.new.invoke_exception(:a => 1)
|
54
|
+
::Airbrake.verify
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "with fake ExceptionNotifier" do
|
59
|
+
before do
|
60
|
+
::ExceptionNotifier = Module.new
|
61
|
+
::ExceptionNotifier::Notifier = MiniTest::Mock.new
|
62
|
+
end
|
63
|
+
|
64
|
+
after do
|
65
|
+
Object.send(:remove_const, "ExceptionNotifier")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "notifies ExceptionNotifier" do
|
69
|
+
::ExceptionNotifier::Notifier.expect(:background_exception_notification,nil,[TEST_EXCEPTION, :data => { :message => { :b => 2 } }])
|
70
|
+
Component.new.invoke_exception(:b => 2)
|
71
|
+
::ExceptionNotifier::Notifier.verify
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "with fake Exceptional" do
|
76
|
+
before do
|
77
|
+
::Exceptional = Class.new do
|
78
|
+
|
79
|
+
def self.context(msg)
|
80
|
+
@msg = msg
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.check_context
|
84
|
+
@msg
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
::Exceptional::Config = MiniTest::Mock.new
|
89
|
+
::Exceptional::Remote = MiniTest::Mock.new
|
90
|
+
::Exceptional::ExceptionData = MiniTest::Mock.new
|
91
|
+
end
|
92
|
+
|
93
|
+
after do
|
94
|
+
Object.send(:remove_const, "Exceptional")
|
95
|
+
end
|
96
|
+
|
97
|
+
it "notifies Exceptional" do
|
98
|
+
::Exceptional::Config.expect(:should_send_to_api?,true)
|
99
|
+
exception_data = MiniTest::Mock.new
|
100
|
+
::Exceptional::Remote.expect(:error,nil,[exception_data])
|
101
|
+
::Exceptional::ExceptionData.expect(:new,exception_data,[TEST_EXCEPTION])
|
102
|
+
Component.new.invoke_exception(:c => 3)
|
103
|
+
assert_equal({:c => 3},::Exceptional.check_context,"did not record arguments properly")
|
104
|
+
::Exceptional::Config.verify
|
105
|
+
::Exceptional::Remote.verify
|
106
|
+
::Exceptional::ExceptionData.verify
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/test/test_fetch.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'sidekiq/fetch'
|
3
|
+
|
4
|
+
class TestFetcher < MiniTest::Unit::TestCase
|
5
|
+
describe 'Fetcher#queues_cmd' do
|
6
|
+
describe 'when queues are strictly ordered' do
|
7
|
+
it 'returns the unique ordered queues properly based on priority and order they were passed in' do
|
8
|
+
fetcher = Sidekiq::Fetcher.new nil, %w[high medium low default], true
|
9
|
+
assert_equal (%w[queue:high queue:medium queue:low queue:default] << 1), fetcher._send_(:queues_cmd)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/test/test_middleware.rb
CHANGED
@@ -9,18 +9,6 @@ class TestMiddleware < MiniTest::Unit::TestCase
|
|
9
9
|
Sidekiq.redis = REDIS
|
10
10
|
end
|
11
11
|
|
12
|
-
it 'handles errors' do
|
13
|
-
handler = Sidekiq::Middleware::Server::ExceptionHandler.new
|
14
|
-
|
15
|
-
assert_raises ArgumentError do
|
16
|
-
handler.call('', { :a => 1 }, 'default') do
|
17
|
-
raise ArgumentError
|
18
|
-
end
|
19
|
-
end
|
20
|
-
assert_equal 1, $errors.size
|
21
|
-
assert_equal({ :a => 1 }, $errors[0][:parameters])
|
22
|
-
end
|
23
|
-
|
24
12
|
class CustomMiddleware
|
25
13
|
def initialize(name, recorder)
|
26
14
|
@name = name
|
@@ -83,10 +71,3 @@ class TestMiddleware < MiniTest::Unit::TestCase
|
|
83
71
|
end
|
84
72
|
end
|
85
73
|
end
|
86
|
-
|
87
|
-
class FakeAirbrake
|
88
|
-
def self.notify(ex, hash)
|
89
|
-
$errors << hash
|
90
|
-
end
|
91
|
-
end
|
92
|
-
Airbrake = FakeAirbrake
|
data/test/test_processor.rb
CHANGED
@@ -2,11 +2,14 @@ require 'helper'
|
|
2
2
|
require 'sidekiq/processor'
|
3
3
|
|
4
4
|
class TestProcessor < MiniTest::Unit::TestCase
|
5
|
+
TestException = Class.new(StandardError)
|
6
|
+
TEST_EXCEPTION = TestException.new("kerboom!")
|
7
|
+
|
5
8
|
describe 'with mock setup' do
|
6
9
|
before do
|
7
10
|
$invokes = 0
|
8
|
-
$errors = []
|
9
11
|
@boss = MiniTest::Mock.new
|
12
|
+
@processor = ::Sidekiq::Processor.new(@boss)
|
10
13
|
Celluloid.logger = nil
|
11
14
|
Sidekiq.redis = REDIS
|
12
15
|
end
|
@@ -14,19 +17,51 @@ class TestProcessor < MiniTest::Unit::TestCase
|
|
14
17
|
class MockWorker
|
15
18
|
include Sidekiq::Worker
|
16
19
|
def perform(args)
|
17
|
-
raise
|
20
|
+
raise TEST_EXCEPTION if args == 'boom'
|
21
|
+
args.pop if args.is_a? Array
|
18
22
|
$invokes += 1
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
26
|
it 'processes as expected' do
|
23
27
|
msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] })
|
24
|
-
|
25
|
-
@
|
26
|
-
processor.process(msg, 'default')
|
28
|
+
@boss.expect(:processor_done!, nil, [@processor])
|
29
|
+
@processor.process(msg, 'default')
|
27
30
|
@boss.verify
|
28
31
|
assert_equal 1, $invokes
|
29
|
-
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'passes exceptions to ExceptionHandler' do
|
35
|
+
msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
|
36
|
+
begin
|
37
|
+
@processor.process(msg, 'default')
|
38
|
+
flunk "Expected #process to raise exception"
|
39
|
+
rescue TestException
|
40
|
+
end
|
41
|
+
|
42
|
+
assert_equal 0, $invokes
|
43
|
+
end
|
44
|
+
|
45
|
+
it 're-raises exceptions after handling' do
|
46
|
+
msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
|
47
|
+
re_raise = false
|
48
|
+
|
49
|
+
begin
|
50
|
+
@processor.process(msg, 'default')
|
51
|
+
rescue TestException
|
52
|
+
re_raise = true
|
53
|
+
end
|
54
|
+
|
55
|
+
assert re_raise, "does not re-raise exceptions after handling"
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'does not modify original arguments' do
|
59
|
+
msg = { 'class' => MockWorker.to_s, 'args' => [['myarg']] }
|
60
|
+
msgstr = Sidekiq.dump_json(msg)
|
61
|
+
processor = ::Sidekiq::Processor.new(@boss)
|
62
|
+
@boss.expect(:processor_done!, nil, [processor])
|
63
|
+
processor.process(msgstr, 'default')
|
64
|
+
assert_equal [['myarg']], msg['args']
|
30
65
|
end
|
31
66
|
end
|
32
67
|
end
|
data/test/test_web.rb
CHANGED
@@ -31,6 +31,14 @@ class TestWeb < MiniTest::Unit::TestCase
|
|
31
31
|
refute_match /default/, last_response.body
|
32
32
|
end
|
33
33
|
|
34
|
+
it 'can display poll' do
|
35
|
+
get '/poll'
|
36
|
+
assert_equal 200, last_response.status
|
37
|
+
assert_match /hero-unit/, last_response.body
|
38
|
+
assert_match /workers/, last_response.body
|
39
|
+
refute_match /navbar/, last_response.body
|
40
|
+
end
|
41
|
+
|
34
42
|
it 'can display queues' do
|
35
43
|
assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3])
|
36
44
|
|
@@ -115,26 +123,26 @@ class TestWeb < MiniTest::Unit::TestCase
|
|
115
123
|
assert_equal 200, last_response.status
|
116
124
|
assert_match /HardWorker/, last_response.body
|
117
125
|
end
|
118
|
-
|
126
|
+
|
119
127
|
it 'can delete a single retry' do
|
120
128
|
_, score = add_retry
|
121
129
|
|
122
130
|
post "/retries/#{score}", 'delete' => 'Delete'
|
123
131
|
assert_equal 302, last_response.status
|
124
132
|
assert_equal 'http://example.org/retries', last_response.header['Location']
|
125
|
-
|
133
|
+
|
126
134
|
get "/retries"
|
127
135
|
assert_equal 200, last_response.status
|
128
136
|
refute_match /#{score}/, last_response.body
|
129
137
|
end
|
130
|
-
|
138
|
+
|
131
139
|
it 'can retry a single retry now' do
|
132
140
|
msg, score = add_retry
|
133
141
|
|
134
142
|
post "/retries/#{score}", 'retry' => 'Retry'
|
135
143
|
assert_equal 302, last_response.status
|
136
144
|
assert_equal 'http://example.org/retries', last_response.header['Location']
|
137
|
-
|
145
|
+
|
138
146
|
get '/queues/default'
|
139
147
|
assert_equal 200, last_response.status
|
140
148
|
assert_match /#{msg['args'][2]}/, last_response.body
|
@@ -18,3 +18,32 @@ $(function() {
|
|
18
18
|
}
|
19
19
|
});
|
20
20
|
});
|
21
|
+
|
22
|
+
$(function() {
|
23
|
+
$('a[name=poll]').data('polling', false);
|
24
|
+
|
25
|
+
$('a[name=poll]').on('click', function(e) {
|
26
|
+
e.preventDefault();
|
27
|
+
var pollLink = $(this);
|
28
|
+
if (pollLink.data('polling')) {
|
29
|
+
clearInterval(pollLink.data('interval'));
|
30
|
+
pollLink.text('Live Poll');
|
31
|
+
$('.poll-status').text('');
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
var href = pollLink.attr('href');
|
35
|
+
pollLink.data('interval', setInterval(function() {
|
36
|
+
$.get(href, function(data) {
|
37
|
+
var responseHtml = $(data);
|
38
|
+
$('.hero-unit').replaceWith(responseHtml.find('.hero-unit'));
|
39
|
+
$('.workers').replaceWith(responseHtml.find('.workers'));
|
40
|
+
});
|
41
|
+
var currentTime = new Date();
|
42
|
+
$('.poll-status').text('Last polled at: ' + currentTime.getHours() + ':' + currentTime.getMinutes() + ':' + currentTime.getSeconds());
|
43
|
+
}, 2000));
|
44
|
+
$('.poll-status').text('Starting to poll...');
|
45
|
+
pollLink.text('Stop Polling');
|
46
|
+
}
|
47
|
+
pollLink.data('polling', !pollLink.data('polling'));
|
48
|
+
})
|
49
|
+
});
|
@@ -0,0 +1,14 @@
|
|
1
|
+
table class="table table-striped table-bordered workers"
|
2
|
+
tr
|
3
|
+
th Worker
|
4
|
+
th Queue
|
5
|
+
th Class
|
6
|
+
th Arguments
|
7
|
+
th Started
|
8
|
+
- workers.each do |(worker, msg)|
|
9
|
+
tr
|
10
|
+
td= worker
|
11
|
+
td= msg['queue']
|
12
|
+
td= msg['payload']['class']
|
13
|
+
td= msg['payload']['args'].inspect[0..100]
|
14
|
+
td== relative_time(Time.parse(msg['run_at']))
|
data/web/views/index.slim
CHANGED
@@ -1,25 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
p Queue Backlog: #{backlog}
|
1
|
+
== slim :_summary
|
2
|
+
|
3
|
+
.poll
|
4
|
+
a*{name: 'poll'} href='#{{root_path}}poll' Live Poll
|
5
|
+
span class="poll-status"
|
6
|
+
|
7
|
+
== slim :_workers
|
9
8
|
|
10
|
-
table class="table table-striped table-bordered"
|
11
|
-
tr
|
12
|
-
th Worker
|
13
|
-
th Queue
|
14
|
-
th Class
|
15
|
-
th Arguments
|
16
|
-
th Started
|
17
|
-
- workers.each do |(worker, msg)|
|
18
|
-
tr
|
19
|
-
td= worker
|
20
|
-
td= msg['queue']
|
21
|
-
td= msg['payload']['class']
|
22
|
-
td= msg['payload']['args'].inspect[0..100]
|
23
|
-
td== relative_time(Time.parse(msg['run_at']))
|
24
9
|
form action="#{root_path}reset" method="post"
|
25
|
-
|
10
|
+
button.btn type="submit" title="If you kill -9 Sidekiq, this table can fill up with old data." Clear worker list
|
data/web/views/layout.slim
CHANGED
data/web/views/poll.slim
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07
|
12
|
+
date: 2012-08-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -226,6 +226,7 @@ files:
|
|
226
226
|
- lib/sidekiq/cli.rb
|
227
227
|
- lib/sidekiq/client.rb
|
228
228
|
- lib/sidekiq/core_ext.rb
|
229
|
+
- lib/sidekiq/exception_handler.rb
|
229
230
|
- lib/sidekiq/extensions/action_mailer.rb
|
230
231
|
- lib/sidekiq/extensions/active_record.rb
|
231
232
|
- lib/sidekiq/extensions/generic_proxy.rb
|
@@ -234,7 +235,6 @@ files:
|
|
234
235
|
- lib/sidekiq/manager.rb
|
235
236
|
- lib/sidekiq/middleware/chain.rb
|
236
237
|
- lib/sidekiq/middleware/server/active_record.rb
|
237
|
-
- lib/sidekiq/middleware/server/exception_handler.rb
|
238
238
|
- lib/sidekiq/middleware/server/logging.rb
|
239
239
|
- lib/sidekiq/middleware/server/retry_jobs.rb
|
240
240
|
- lib/sidekiq/middleware/server/timeout.rb
|
@@ -295,8 +295,9 @@ files:
|
|
295
295
|
- test/helper.rb
|
296
296
|
- test/test_cli.rb
|
297
297
|
- test/test_client.rb
|
298
|
+
- test/test_exception_handler.rb
|
298
299
|
- test/test_extensions.rb
|
299
|
-
- test/
|
300
|
+
- test/test_fetch.rb
|
300
301
|
- test/test_middleware.rb
|
301
302
|
- test/test_processor.rb
|
302
303
|
- test/test_retry.rb
|
@@ -328,8 +329,11 @@ files:
|
|
328
329
|
- web/assets/stylesheets/vendor/bootstrap-responsive.css
|
329
330
|
- web/assets/stylesheets/vendor/bootstrap.css
|
330
331
|
- web/views/_paging.slim
|
332
|
+
- web/views/_summary.slim
|
333
|
+
- web/views/_workers.slim
|
331
334
|
- web/views/index.slim
|
332
335
|
- web/views/layout.slim
|
336
|
+
- web/views/poll.slim
|
333
337
|
- web/views/queue.slim
|
334
338
|
- web/views/queues.slim
|
335
339
|
- web/views/retries.slim
|
@@ -366,8 +370,9 @@ test_files:
|
|
366
370
|
- test/helper.rb
|
367
371
|
- test/test_cli.rb
|
368
372
|
- test/test_client.rb
|
373
|
+
- test/test_exception_handler.rb
|
369
374
|
- test/test_extensions.rb
|
370
|
-
- test/
|
375
|
+
- test/test_fetch.rb
|
371
376
|
- test/test_middleware.rb
|
372
377
|
- test/test_processor.rb
|
373
378
|
- test/test_retry.rb
|
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'sidekiq/util'
|
2
|
-
|
3
|
-
module Sidekiq
|
4
|
-
module Middleware
|
5
|
-
module Server
|
6
|
-
class ExceptionHandler
|
7
|
-
include Util
|
8
|
-
def call(*args)
|
9
|
-
yield
|
10
|
-
rescue => ex
|
11
|
-
logger.warn ex
|
12
|
-
logger.warn ex.backtrace.join("\n")
|
13
|
-
send_to_airbrake(args[1], ex) if defined?(::Airbrake)
|
14
|
-
send_to_exceptional(args[1], ex) if defined?(::Exceptional)
|
15
|
-
send_to_exception_notifier(args[1], ex) if defined?(::ExceptionNotifier)
|
16
|
-
raise
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def send_to_airbrake(msg, ex)
|
22
|
-
::Airbrake.notify(ex, :parameters => msg)
|
23
|
-
end
|
24
|
-
|
25
|
-
def send_to_exceptional(msg, ex)
|
26
|
-
if ::Exceptional::Config.should_send_to_api?
|
27
|
-
::Exceptional.context(msg)
|
28
|
-
::Exceptional::Remote.error(::Exceptional::ExceptionData.new(ex))
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def send_to_exception_notifier(msg, ex)
|
33
|
-
::ExceptionNotifier::Notifier.background_exception_notification(ex, :data => { :message => msg })
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
data/test/test_manager.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
require 'sidekiq'
|
3
|
-
require 'sidekiq/manager'
|
4
|
-
|
5
|
-
# for TimedQueue
|
6
|
-
require 'connection_pool'
|
7
|
-
|
8
|
-
class TestManager < MiniTest::Unit::TestCase
|
9
|
-
describe 'with redis' do
|
10
|
-
before do
|
11
|
-
Sidekiq.redis = REDIS
|
12
|
-
Sidekiq.redis {|c| c.flushdb }
|
13
|
-
$processed = 0
|
14
|
-
$mutex = Mutex.new
|
15
|
-
end
|
16
|
-
|
17
|
-
class IntegrationWorker
|
18
|
-
include Sidekiq::Worker
|
19
|
-
sidekiq_options :queue => 'foo'
|
20
|
-
|
21
|
-
def perform(a, b)
|
22
|
-
$mutex.synchronize do
|
23
|
-
$processed += 1
|
24
|
-
end
|
25
|
-
a + b
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'processes messages' do
|
30
|
-
IntegrationWorker.perform_async(1, 2)
|
31
|
-
IntegrationWorker.perform_async(1, 3)
|
32
|
-
|
33
|
-
q = TimedQueue.new
|
34
|
-
mgr = Sidekiq::Manager.new(:queues => [:foo], :concurrency => 2)
|
35
|
-
mgr.when_done do |_|
|
36
|
-
q << 'done' if $processed == 2
|
37
|
-
end
|
38
|
-
mgr.start!
|
39
|
-
result = q.timed_pop(1.0)
|
40
|
-
assert_equal 'done', result
|
41
|
-
mgr.stop
|
42
|
-
mgr.terminate
|
43
|
-
|
44
|
-
# Gross bloody hack because I can't get the actor threads
|
45
|
-
# to shut down cleanly in the test. Need @bascule's help here.
|
46
|
-
(Thread.list - [Thread.current]).each do |t|
|
47
|
-
t.raise Interrupt
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|