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 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 have been optional server
10
- middleware. [#302]
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
 
@@ -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
- o.on "-q", "--queue QUEUE,WEIGHT", "Queue to process, with optional weight" do |arg|
145
- q, weight = arg.split(",")
146
- parse_queues(opts, q, weight)
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
- (weight || 1).to_i.times do
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
@@ -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
@@ -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
@@ -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
@@ -22,25 +22,31 @@ module Sidekiq
22
22
  watchdog('scheduling poller thread died!') do
23
23
  add_jitter if first_time
24
24
 
25
- # A message's "score" in Redis is the time at which it should be processed.
26
- # Just check Redis for the set of messages with a timestamp before now.
27
- now = Time.now.to_f.to_s
28
- Sidekiq.redis do |conn|
29
- SETS.each do |sorted_set|
30
- (messages, _) = conn.multi do
31
- conn.zrangebyscore(sorted_set, '-inf', now)
32
- conn.zremrangebyscore(sorted_set, '-inf', now)
33
- end
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
- messages.each do |message|
36
- logger.debug { "enqueued #{sorted_set}: #{message}" }
37
- msg = Sidekiq.load_json(message)
38
- conn.multi do
39
- conn.sadd('queues', msg['queue'])
40
- conn.rpush("queue:#{msg['queue']}", message)
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 }
@@ -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
- logger.error last_words
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
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "2.1.0"
2
+ VERSION = "2.1.1"
3
3
  end
@@ -117,6 +117,10 @@ module Sidekiq
117
117
  slim :index
118
118
  end
119
119
 
120
+ get "/poll" do
121
+ slim :poll, layout: false
122
+ end
123
+
120
124
  get "/queues" do
121
125
  @queues = queues
122
126
  slim :queues
@@ -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(bar foo foo foo), Sidekiq.options[:queues].sort
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
@@ -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
@@ -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
@@ -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 "kerboom!" if args == 'boom'
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
- processor = ::Sidekiq::Processor.new(@boss)
25
- @boss.expect(:processor_done!, nil, [processor])
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
- assert_equal 0, $errors.size
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
@@ -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
+ });
@@ -20,3 +20,7 @@ code {
20
20
  .hero-unit {
21
21
  padding: 30px;
22
22
  }
23
+
24
+ .poll-status {
25
+ padding-left: 10px;
26
+ }
@@ -0,0 +1,8 @@
1
+ .hero-unit
2
+ h1 Sidekiq is #{current_status}
3
+ p Processed: #{processed}
4
+ p Failed: #{failed}
5
+ p Busy Workers: #{workers.size}
6
+ p Scheduled: #{zcard('schedule')}
7
+ p Retries Pending: #{zcard('retry')}
8
+ p Queue Backlog: #{backlog}
@@ -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']))
@@ -1,25 +1,10 @@
1
- .hero-unit
2
- h1 Sidekiq is #{current_status}
3
- p Processed: #{processed}
4
- p Failed: #{failed}
5
- p Busy Workers: #{workers.size}
6
- p Scheduled: #{zcard('schedule')}
7
- p Retries Pending: #{zcard('retry')}
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
- button.btn type="submit" title="If you kill -9 Sidekiq, this table can fill up with old data." Clear worker list
10
+ button.btn type="submit" title="If you kill -9 Sidekiq, this table can fill up with old data." Clear worker list
@@ -13,7 +13,7 @@ html
13
13
  span.icon-bar
14
14
  a.brand href='#{{root_path}}'
15
15
  | Sidekiq
16
- div.nav-collapse
16
+ div.nav-collapse
17
17
  ul.nav
18
18
  li
19
19
  a href='#{{root_path}}' Home
@@ -0,0 +1,3 @@
1
+ div
2
+ == slim :_summary
3
+ == slim :_workers
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.0
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-21 00:00:00.000000000 Z
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/test_manager.rb
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/test_manager.rb
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
@@ -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