sidekiq 2.12.1 → 2.12.3

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,19 @@
1
+ 2.12.3
2
+ -----------
3
+
4
+ - Revert back to Celluloid's TaskFiber for job processing which has proven to be more
5
+ stable than TaskThread. [#985]
6
+ - Avoid possible lockup during hard shutdown [#997]
7
+
8
+ At this point, if you are experiencing stability issues with Sidekiq in
9
+ Ruby 1.9, please try Ruby 2.0. It seems to be more stable.
10
+
11
+ 2.12.2
12
+ -----------
13
+
14
+ - Relax slim version requirement to >= 1.1.0
15
+ - Refactor historical stats to use TTL, not explicit cleanup. [grosser, #971]
16
+
1
17
  2.12.1
2
18
  -----------
3
19
 
data/Gemfile CHANGED
@@ -1,7 +1,8 @@
1
1
  source 'http://rubygems.org'
2
2
  gemspec
3
3
 
4
- gem 'slim'
4
+ gem 'slim', '1.1.0' # earliest verson supported
5
+
5
6
  gem 'sqlite3', :platform => :mri
6
7
 
7
8
  group :test do
@@ -1,67 +1,7 @@
1
1
  module Sidekiq
2
- #
3
- # Celluloid has the nasty side effect of making objects
4
- # very hard to test because they are immediately async
5
- # upon creation. In testing we want to just treat
6
- # the actors as POROs.
7
- #
8
- # Instead of including Celluloid, we'll just stub
9
- # out the key methods we use so that everything works
10
- # synchronously. The alternative is no test coverage.
11
- #
12
2
  module Actor
13
- if $TESTING
14
-
15
- TaskThread = 0
16
-
17
- def sleep(amount=0)
18
- end
19
-
20
- def after(amount=0)
21
- end
22
-
23
- def defer
24
- yield
25
- end
26
-
27
- def current_actor
28
- self
29
- end
30
-
31
- def alive?
32
- !@dead
33
- end
34
-
35
- def terminate
36
- @dead = true
37
- end
38
-
39
- def async
40
- self
41
- end
42
-
43
- def signal(sym)
44
- end
45
-
46
- # we don't want to hide or catch failures in testing
47
- def watchdog(msg)
48
- yield
49
- end
50
-
51
- def self.included(klass)
52
- class << klass
53
- alias_method :new_link, :new
54
- def trap_exit(meth)
55
- end
56
- def task_class(konstant)
57
- end
58
- end
59
- end
60
-
61
- else
62
- def self.included(klass)
63
- klass.send(:include, Celluloid)
64
- end
3
+ def self.included(klass)
4
+ klass.send(:include, Celluloid)
65
5
  end
66
6
  end
67
7
  end
@@ -3,17 +3,11 @@ require 'sidekiq'
3
3
  module Sidekiq
4
4
  class Stats
5
5
  def processed
6
- count = Sidekiq.redis do |conn|
7
- conn.get("stat:processed")
8
- end
9
- count.nil? ? 0 : count.to_i
6
+ Sidekiq.redis { |conn| conn.get("stat:processed") }.to_i
10
7
  end
11
8
 
12
9
  def failed
13
- count = Sidekiq.redis do |conn|
14
- conn.get("stat:failed")
15
- end
16
- count.nil? ? 0 : count.to_i
10
+ Sidekiq.redis { |conn| conn.get("stat:failed") }.to_i
17
11
  end
18
12
 
19
13
  def reset
@@ -62,24 +56,6 @@ module Sidekiq
62
56
  date_stat_hash("failed")
63
57
  end
64
58
 
65
- def self.cleanup
66
- days_of_stats_to_keep = 180
67
- today = Time.now.utc.to_date
68
- delete_before_date = Time.now.utc.to_date - days_of_stats_to_keep
69
-
70
- Sidekiq.redis do |conn|
71
- processed_keys = conn.keys("stat:processed:*")
72
- earliest = "stat:processed:#{delete_before_date.to_s}"
73
- pkeys = processed_keys.select { |key| key < earliest }
74
- conn.del(pkeys) if pkeys.size > 0
75
-
76
- failed_keys = conn.keys("stat:failed:*")
77
- earliest = "stat:failed:#{delete_before_date.to_s}"
78
- fkeys = failed_keys.select { |key| key < earliest }
79
- conn.del(fkeys) if fkeys.size > 0
80
- end
81
- end
82
-
83
59
  private
84
60
 
85
61
  def date_stat_hash(stat)
@@ -117,6 +93,10 @@ module Sidekiq
117
93
  class Queue
118
94
  include Enumerable
119
95
 
96
+ def self.all
97
+ Sidekiq.redis {|c| c.smembers('queues') }.map {|q| Sidekiq::Queue.new(q) }
98
+ end
99
+
120
100
  attr_reader :name
121
101
 
122
102
  def initialize(name="default")
@@ -195,7 +175,7 @@ module Sidekiq
195
175
  end
196
176
 
197
177
  def enqueued_at
198
- Time.at(@item['enqueued_at'])
178
+ Time.at(@item['enqueued_at'] || 0)
199
179
  end
200
180
 
201
181
  def queue
@@ -1,9 +1,6 @@
1
1
  Capistrano::Configuration.instance.load do
2
- before "deploy:update_code", "sidekiq:quiet"
3
- after "deploy:stop", "sidekiq:stop"
4
- after "deploy:start", "sidekiq:start"
5
- before "deploy:restart", "sidekiq:restart"
6
2
 
3
+ _cset(:sidekiq_default_hooks) { true }
7
4
  _cset(:sidekiq_cmd) { "#{fetch(:bundle_cmd, "bundle")} exec sidekiq" }
8
5
  _cset(:sidekiqctl_cmd) { "#{fetch(:bundle_cmd, "bundle")} exec sidekiqctl" }
9
6
  _cset(:sidekiq_timeout) { 10 }
@@ -11,6 +8,13 @@ Capistrano::Configuration.instance.load do
11
8
  _cset(:sidekiq_pid) { "#{current_path}/tmp/pids/sidekiq.pid" }
12
9
  _cset(:sidekiq_processes) { 1 }
13
10
 
11
+ if fetch(:sidekiq_default_hooks)
12
+ before "deploy:update_code", "sidekiq:quiet"
13
+ after "deploy:stop", "sidekiq:stop"
14
+ after "deploy:start", "sidekiq:start"
15
+ before "deploy:restart", "sidekiq:restart"
16
+ end
17
+
14
18
  namespace :sidekiq do
15
19
  def for_each_process(&block)
16
20
  fetch(:sidekiq_processes).times do |idx|
@@ -52,8 +52,6 @@ module Sidekiq
52
52
  logger.info "Running in #{RUBY_DESCRIPTION}"
53
53
  logger.info Sidekiq::LICENSE
54
54
 
55
- Sidekiq::Stats::History.cleanup
56
-
57
55
  if !options[:daemon]
58
56
  logger.info 'Starting processing, hit Ctrl-C to stop'
59
57
  end
@@ -3,7 +3,7 @@ require 'sidekiq/extensions/generic_proxy'
3
3
  module Sidekiq
4
4
  module Extensions
5
5
  ##
6
- # Adds 'delay' and 'delay_for' to ActionMailer to offload arbitrary email
6
+ # Adds 'delay', 'delay_for' and `delay_until` methods to ActionMailer to offload arbitrary email
7
7
  # delivery to Sidekiq. Example:
8
8
  #
9
9
  # UserMailer.delay.send_welcome_email(new_user)
@@ -3,7 +3,7 @@ require 'sidekiq/extensions/generic_proxy'
3
3
  module Sidekiq
4
4
  module Extensions
5
5
  ##
6
- # Adds a 'delay' method to ActiveRecords to offload instance method
6
+ # Adds 'delay', 'delay_for' and `delay_until` methods to ActiveRecord to offload instance method
7
7
  # execution to Sidekiq. Examples:
8
8
  #
9
9
  # User.recent_signups.each { |user| user.delay.mark_as_awesome }
@@ -3,7 +3,7 @@ require 'sidekiq/extensions/generic_proxy'
3
3
  module Sidekiq
4
4
  module Extensions
5
5
  ##
6
- # Adds a 'delay' method to all Classes to offload class method
6
+ # Adds 'delay', 'delay_for' and `delay_until` methods to all Classes to offload class method
7
7
  # execution to Sidekiq. Examples:
8
8
  #
9
9
  # User.delay.delete_inactive
@@ -25,10 +25,11 @@ module Sidekiq
25
25
  @done_callback = nil
26
26
 
27
27
  @in_progress = {}
28
+ @threads = {}
28
29
  @done = false
29
30
  @busy = []
30
31
  @fetcher = Fetcher.new(current_actor, options)
31
- @ready = @count.times.map { Processor.new_link(current_actor) }
32
+ @ready = @count.times.map { Processor.new_link(current_actor).tap {|p| p.proxy_id = p.object_id} }
32
33
  end
33
34
 
34
35
  def stop(options={})
@@ -63,6 +64,7 @@ module Sidekiq
63
64
  watchdog('Manager#processor_done died') do
64
65
  @done_callback.call(processor) if @done_callback
65
66
  @in_progress.delete(processor.object_id)
67
+ @threads.delete(processor.object_id)
66
68
  @busy.delete(processor)
67
69
  if stopped?
68
70
  processor.terminate if processor.alive?
@@ -77,10 +79,13 @@ module Sidekiq
77
79
  def processor_died(processor, reason)
78
80
  watchdog("Manager#processor_died died") do
79
81
  @in_progress.delete(processor.object_id)
82
+ @threads.delete(processor.object_id)
80
83
  @busy.delete(processor)
81
84
 
82
85
  unless stopped?
83
- @ready << Processor.new_link(current_actor)
86
+ @ready << Processor.new_link(current_actor).tap do |p|
87
+ p.proxy_id = p.object_id
88
+ end
84
89
  dispatch
85
90
  else
86
91
  signal(:shutdown) if @busy.empty?
@@ -105,6 +110,14 @@ module Sidekiq
105
110
  end
106
111
  end
107
112
 
113
+ # A hack worthy of Rube Goldberg. We need to be able
114
+ # to hard stop a working thread. But there's no way for us to
115
+ # get handle to the underlying thread performing work for a processor
116
+ # so we have it call us and tell us.
117
+ def real_thread(proxy_id, thr)
118
+ @threads[proxy_id] = thr
119
+ end
120
+
108
121
  def procline(tag)
109
122
  "sidekiq #{Sidekiq::VERSION} #{tag}[#{@busy.size} of #{@count} busy]#{stopped? ? ' stopping' : ''}"
110
123
  end
@@ -145,10 +158,11 @@ module Sidekiq
145
158
  # it is worse to lose a job than to run it twice.
146
159
  Sidekiq::Fetcher.strategy.bulk_requeue(@in_progress.values)
147
160
 
148
- logger.debug { "Terminating worker threads" }
161
+ logger.debug { "Terminating #{@busy.size} busy worker threads" }
149
162
  @busy.each do |processor|
150
- t = processor.bare_object.actual_work_thread
151
- t.raise Shutdown if processor.alive?
163
+ if processor.alive? && t = @threads.delete(processor.object_id)
164
+ t.raise Shutdown
165
+ end
152
166
  end
153
167
 
154
168
  after(0) { signal(:shutdown) }
@@ -43,13 +43,12 @@ module Sidekiq
43
43
  class RetryJobs
44
44
  include Sidekiq::Util
45
45
 
46
- # delayed_job uses the same basic formula
47
46
  DEFAULT_MAX_RETRY_ATTEMPTS = 25
48
47
 
49
48
  def call(worker, msg, queue)
50
49
  yield
51
50
  rescue Sidekiq::Shutdown
52
- # ignore, will be pushed back onto queue
51
+ # ignore, will be pushed back onto queue during hard_shutdown
53
52
  raise
54
53
  rescue Exception => e
55
54
  raise e unless msg['retry']
@@ -110,6 +109,7 @@ module Sidekiq
110
109
  end
111
110
  end
112
111
 
112
+ # delayed_job uses the same basic formula
113
113
  def seconds_to_delay(count)
114
114
  (count ** 4) + 15 + (rand(30)*(count+1))
115
115
  end
@@ -11,11 +11,11 @@ module Sidekiq
11
11
  # processes it. It instantiates the worker, runs the middleware
12
12
  # chain and then calls Sidekiq::Worker#perform.
13
13
  class Processor
14
+ STATS_TIMEOUT = 180 * 24 * 60 * 60
15
+
14
16
  include Util
15
17
  include Actor
16
18
 
17
- task_class TaskThread
18
-
19
19
  def self.default_middleware
20
20
  Middleware::Chain.new do |m|
21
21
  m.add Middleware::Server::Logging
@@ -24,10 +24,7 @@ module Sidekiq
24
24
  end
25
25
  end
26
26
 
27
- # store the actual working thread so we
28
- # can later kill if it necessary during
29
- # hard shutdown.
30
- attr_accessor :actual_work_thread
27
+ attr_accessor :proxy_id
31
28
 
32
29
  def initialize(boss)
33
30
  @boss = boss
@@ -37,38 +34,56 @@ module Sidekiq
37
34
  msgstr = work.message
38
35
  queue = work.queue_name
39
36
 
40
- @actual_work_thread = Thread.current
41
- begin
42
- msg = Sidekiq.load_json(msgstr)
43
- klass = msg['class'].constantize
44
- worker = klass.new
45
- worker.jid = msg['jid']
46
-
47
- stats(worker, msg, queue) do
48
- Sidekiq.server_middleware.invoke(worker, msg, queue) do
49
- worker.perform(*cloned(msg['args']))
37
+ do_defer do
38
+ @boss.async.real_thread(proxy_id, Thread.current)
39
+
40
+ begin
41
+ msg = Sidekiq.load_json(msgstr)
42
+ klass = msg['class'].constantize
43
+ worker = klass.new
44
+ worker.jid = msg['jid']
45
+
46
+ stats(worker, msg, queue) do
47
+ Sidekiq.server_middleware.invoke(worker, msg, queue) do
48
+ worker.perform(*cloned(msg['args']))
49
+ end
50
50
  end
51
+ rescue Sidekiq::Shutdown
52
+ # Had to force kill this job because it didn't finish
53
+ # within the timeout.
54
+ rescue Exception => ex
55
+ handle_exception(ex, msg || { :message => msgstr })
56
+ raise
57
+ ensure
58
+ work.acknowledge
51
59
  end
52
- rescue Sidekiq::Shutdown
53
- # Had to force kill this job because it didn't finish
54
- # within the timeout.
55
- rescue Exception => ex
56
- handle_exception(ex, msg || { :message => msgstr })
57
- raise
58
- ensure
59
- work.acknowledge
60
60
  end
61
61
 
62
62
  @boss.async.processor_done(current_actor)
63
63
  end
64
64
 
65
- # See http://github.com/tarcieri/celluloid/issues/22
66
65
  def inspect
67
- "#<Processor #{to_s}>"
66
+ "<Processor##{object_id.to_s(16)}>"
68
67
  end
69
68
 
70
69
  private
71
70
 
71
+ # We use Celluloid's defer to workaround tiny little
72
+ # Fiber stacks (4kb!) in MRI 1.9.
73
+ #
74
+ # For some reason, Celluloid's thread dispatch, TaskThread,
75
+ # is unstable under heavy concurrency but TaskFiber has proven
76
+ # itself stable.
77
+ NEED_DEFER = (RUBY_ENGINE == 'ruby' && RUBY_VERSION < '2.0.0')
78
+
79
+ def do_defer(&block)
80
+ if NEED_DEFER
81
+ defer(&block)
82
+ else
83
+ yield
84
+ end
85
+ end
86
+
72
87
  def identity
73
88
  @str ||= "#{hostname}:#{process_id}-#{Thread.current.object_id}:default"
74
89
  end
@@ -87,21 +102,25 @@ module Sidekiq
87
102
  yield
88
103
  rescue Exception
89
104
  redis do |conn|
90
- conn.multi do
105
+ failed = "stat:failed:#{Time.now.utc.to_date}"
106
+ result = conn.multi do
91
107
  conn.incrby("stat:failed", 1)
92
- conn.incrby("stat:failed:#{Time.now.utc.to_date}", 1)
108
+ conn.incrby(failed, 1)
93
109
  end
110
+ conn.expire(failed, STATS_TIMEOUT) if result.last == 1
94
111
  end
95
112
  raise
96
113
  ensure
97
114
  redis do |conn|
98
- conn.multi do
115
+ processed = "stat:processed:#{Time.now.utc.to_date}"
116
+ result = conn.multi do
99
117
  conn.srem("workers", identity)
100
118
  conn.del("worker:#{identity}")
101
119
  conn.del("worker:#{identity}:started")
102
120
  conn.incrby("stat:processed", 1)
103
- conn.incrby("stat:processed:#{Time.now.utc.to_date}", 1)
121
+ conn.incrby(processed, 1)
104
122
  end
123
+ conn.expire(processed, STATS_TIMEOUT) if result.last == 1
105
124
  end
106
125
  end
107
126
  end
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "2.12.1"
2
+ VERSION = "2.12.3"
3
3
  end
@@ -2,7 +2,7 @@ require 'yaml'
2
2
  require 'sinatra/base'
3
3
  require 'slim'
4
4
 
5
- raise "The Sidekiq Web UI requires slim 1.3.8 or greater. You have slim v#{Slim::VERSION}" if Gem::Version.new(Slim::VERSION) < Gem::Version.new('1.3.8')
5
+ raise "The Sidekiq Web UI requires slim 1.1.0 or greater. You have slim v#{Slim::VERSION}" if Gem::Version.new(Slim::VERSION) < Gem::Version.new('1.1.0')
6
6
 
7
7
  require 'sidekiq'
8
8
  require 'sidekiq/api'
@@ -41,9 +41,7 @@ module Sidekiq
41
41
  def reset_worker_list
42
42
  Sidekiq.redis do |conn|
43
43
  workers = conn.smembers('workers')
44
- workers.each do |name|
45
- conn.srem('workers', name)
46
- end
44
+ conn.srem('workers', *workers)
47
45
  end
48
46
  end
49
47
 
@@ -104,13 +102,16 @@ module Sidekiq
104
102
  end
105
103
 
106
104
  def display_args(args, count=100)
107
- args.map { |arg| a = arg.inspect; a.size > count ? "#{a[0..count]}..." : a }.join(", ")
105
+ args.map do |arg|
106
+ a = arg.inspect
107
+ a.size > count ? "#{a[0..count]}..." : a
108
+ end.join(", ")
108
109
  end
109
110
 
110
111
  RETRY_JOB_KEYS = Set.new(%w(
111
112
  queue class args retry_count retried_at failed_at
112
113
  jid error_message error_class backtrace
113
- error_backtrace enqueued_at
114
+ error_backtrace enqueued_at retry
114
115
  ))
115
116
 
116
117
  def retry_extra_items(retry_job)
@@ -158,7 +159,7 @@ module Sidekiq
158
159
  end
159
160
 
160
161
  get "/queues" do
161
- @queues = Sidekiq::Stats.new.queues
162
+ @queues = Sidekiq::Queue.all
162
163
  slim :queues
163
164
  end
164
165
 
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.add_dependency 'celluloid', '>= 0.14.1'
21
21
  gem.add_dependency 'json'
22
22
  gem.add_development_dependency 'sinatra'
23
- gem.add_development_dependency 'slim'
23
+ gem.add_development_dependency 'slim', '>= 1.1.0'
24
24
  gem.add_development_dependency 'minitest', '~> 5'
25
25
  gem.add_development_dependency 'rake'
26
26
  gem.add_development_dependency 'actionmailer'
@@ -20,6 +20,7 @@ end
20
20
  require 'minitest/autorun'
21
21
  require 'minitest/pride'
22
22
 
23
+ require 'celluloid/autostart'
23
24
  require 'sidekiq'
24
25
  require 'sidekiq/util'
25
26
  Sidekiq.logger.level = Logger::ERROR
@@ -127,32 +127,6 @@ class TestApi < Minitest::Test
127
127
  end
128
128
  end
129
129
  end
130
-
131
- describe "cleanup" do
132
- it 'removes processed stats outside of keep window' do
133
- Sidekiq.redis do |c|
134
- c.incrby("stat:processed:2012-05-03", 4)
135
- c.incrby("stat:processed:2012-06-03", 4)
136
- c.incrby("stat:processed:2012-07-03", 1)
137
- end
138
- Time.stub(:now, Time.parse("2012-12-01 1:00:00 -0500")) do
139
- Sidekiq::Stats::History.cleanup
140
- assert_equal false, Sidekiq.redis { |c| c.exists("stat:processed:2012-05-03") }
141
- end
142
- end
143
-
144
- it 'removes failed stats outside of keep window' do
145
- Sidekiq.redis do |c|
146
- c.incrby("stat:failed:2012-05-03", 4)
147
- c.incrby("stat:failed:2012-06-03", 4)
148
- c.incrby("stat:failed:2012-07-03", 1)
149
- end
150
- Time.stub(:now, Time.parse("2012-12-01 1:00:00 -0500")) do
151
- Sidekiq::Stats::History.cleanup
152
- assert_equal false, Sidekiq.redis { |c| c.exists("stat:failed:2012-05-03") }
153
- end
154
- end
155
- end
156
130
  end
157
131
  end
158
132
 
@@ -82,6 +82,8 @@ class TestMiddleware < Minitest::Test
82
82
  processor = Sidekiq::Processor.new(boss)
83
83
  actor = Minitest::Mock.new
84
84
  actor.expect(:processor_done, nil, [processor])
85
+ actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
86
+ boss.expect(:async, actor, [])
85
87
  boss.expect(:async, actor, [])
86
88
  processor.process(Sidekiq::BasicFetch::UnitOfWork.new('queue:default', msg))
87
89
  assert_equal %w(2 before 3 before 0 before work_performed 0 after 3 after 2 after), $recorder.flatten
@@ -31,6 +31,8 @@ class TestProcessor < Minitest::Test
31
31
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] })
32
32
  actor = Minitest::Mock.new
33
33
  actor.expect(:processor_done, nil, [@processor])
34
+ actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
35
+ @boss.expect(:async, actor, [])
34
36
  @boss.expect(:async, actor, [])
35
37
  @processor.process(work(msg))
36
38
  @boss.verify
@@ -38,6 +40,9 @@ class TestProcessor < Minitest::Test
38
40
  end
39
41
 
40
42
  it 'passes exceptions to ExceptionHandler' do
43
+ actor = Minitest::Mock.new
44
+ actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
45
+ @boss.expect(:async, actor, [])
41
46
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
42
47
  begin
43
48
  @processor.process(work(msg))
@@ -51,6 +56,9 @@ class TestProcessor < Minitest::Test
51
56
  it 're-raises exceptions after handling' do
52
57
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
53
58
  re_raise = false
59
+ actor = Minitest::Mock.new
60
+ actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
61
+ @boss.expect(:async, actor, [])
54
62
 
55
63
  begin
56
64
  @processor.process(work(msg))
@@ -67,6 +75,8 @@ class TestProcessor < Minitest::Test
67
75
  processor = ::Sidekiq::Processor.new(@boss)
68
76
  actor = Minitest::Mock.new
69
77
  actor.expect(:processor_done, nil, [processor])
78
+ actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
79
+ @boss.expect(:async, actor, [])
70
80
  @boss.expect(:async, actor, [])
71
81
  processor.process(work(msgstr))
72
82
  assert_equal [['myarg']], msg['args']
@@ -77,12 +87,26 @@ class TestProcessor < Minitest::Test
77
87
  Sidekiq.redis {|c| c.flushdb }
78
88
  end
79
89
 
90
+ def with_expire(time)
91
+ begin
92
+ old = Sidekiq::Processor::STATS_TIMEOUT
93
+ silence_warnings { Sidekiq::Processor.const_set(:STATS_TIMEOUT, time) }
94
+ yield
95
+ ensure
96
+ silence_warnings { Sidekiq::Processor.const_set(:STATS_TIMEOUT, old) }
97
+ end
98
+ end
99
+
80
100
  describe 'when successful' do
101
+ let(:processed_today_key) { "stat:processed:#{Time.now.utc.to_date}" }
102
+
81
103
  def successful_job
82
104
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] })
83
105
  actor = Minitest::Mock.new
106
+ actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
84
107
  actor.expect(:processor_done, nil, [@processor])
85
108
  @boss.expect(:async, actor, [])
109
+ @boss.expect(:async, actor, [])
86
110
  @processor.process(work(msg))
87
111
  end
88
112
 
@@ -91,17 +115,24 @@ class TestProcessor < Minitest::Test
91
115
  assert_equal 1, Sidekiq::Stats.new.processed
92
116
  end
93
117
 
118
+ it 'expires processed stat' do
119
+ successful_job
120
+ assert_equal Sidekiq::Processor::STATS_TIMEOUT, Sidekiq.redis { |conn| conn.ttl(processed_today_key) }
121
+ end
122
+
94
123
  it 'increments date processed stat' do
95
- Time.stub(:now, Time.parse("2012-12-25 1:00:00 -0500")) do
96
- successful_job
97
- date_processed = Sidekiq.redis { |conn| conn.get("stat:processed:2012-12-25") }.to_i
98
- assert_equal 1, date_processed
99
- end
124
+ successful_job
125
+ assert_equal 1, Sidekiq.redis { |conn| conn.get(processed_today_key) }.to_i
100
126
  end
101
127
  end
102
128
 
103
129
  describe 'when failed' do
130
+ let(:failed_today_key) { "stat:failed:#{Time.now.utc.to_date}" }
131
+
104
132
  def failed_job
133
+ actor = Minitest::Mock.new
134
+ actor.expect(:real_thread, nil, [nil, Celluloid::Thread])
135
+ @boss.expect(:async, actor, [])
105
136
  msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] })
106
137
  begin
107
138
  @processor.process(work(msg))
@@ -115,14 +146,15 @@ class TestProcessor < Minitest::Test
115
146
  end
116
147
 
117
148
  it 'increments date failed stat' do
118
- Time.stub(:now, Time.parse("2012-12-25 1:00:00 -0500")) do
119
- failed_job
120
- date_failed = Sidekiq.redis { |conn| conn.get("stat:failed:2012-12-25") }.to_i
121
- assert_equal 1, date_failed
122
- end
149
+ failed_job
150
+ assert_equal 1, Sidekiq.redis { |conn| conn.get(failed_today_key) }.to_i
123
151
  end
124
- end
125
152
 
153
+ it 'expires failed stat' do
154
+ failed_job
155
+ assert_equal Sidekiq::Processor::STATS_TIMEOUT, Sidekiq.redis { |conn| conn.ttl(failed_today_key) }
156
+ end
157
+ end
126
158
  end
127
159
  end
128
160
  end
@@ -131,6 +131,7 @@ class TestTesting < Minitest::Test
131
131
  end
132
132
 
133
133
  it 'perform_one raise error upon empty queue' do
134
+ DirectWorker.clear
134
135
  assert_raises Sidekiq::EmptyQueueError do
135
136
  DirectWorker.perform_one
136
137
  end
@@ -5,11 +5,11 @@ table class="queues table table-hover table-bordered table-striped table-white"
5
5
  th = t('Queue')
6
6
  th = t('Size')
7
7
  th = t('Actions')
8
- - @queues.each do |queue, size|
8
+ - @queues.each do |queue|
9
9
  tr
10
10
  td
11
- a href="#{root_path}queues/#{queue}" #{queue}
12
- td= number_with_delimiter(size)
11
+ a href="#{root_path}queues/#{queue.name}" #{queue.name}
12
+ td= number_with_delimiter(queue.size)
13
13
  td width="20%"
14
- form action="#{root_path}queues/#{queue}" method="post"
15
- input.btn.btn-danger.btn-small type="submit" name="delete" value="#{t('Delete')}" data-confirm="#{t('AreYouSureDeleteQueue', :queue => queue)}"
14
+ form action="#{root_path}queues/#{queue.name}" method="post"
15
+ input.btn.btn-danger.btn-small type="submit" name="delete" value="#{t('Delete')}" data-confirm="#{t('AreYouSureDeleteQueue', :queue => queue.name)}"
@@ -19,11 +19,15 @@ table class="retry table table-bordered table-striped"
19
19
  th JID
20
20
  td
21
21
  code= @retry.jid
22
+ tr
23
+ th = t('Enqueued')
24
+ td== relative_time(@retry.enqueued_at)
22
25
  - unless retry_extra_items(@retry).empty?
23
26
  tr
24
27
  th = t('Extras')
25
28
  td
26
- code= display_args(retry_extra_items(@retry), 1000)
29
+ code
30
+ = retry_extra_items(@retry).inspect
27
31
  - if @retry['retry_count'] > 0
28
32
  tr
29
33
  th = t('RetryCount')
@@ -37,7 +41,7 @@ table class="retry table table-bordered table-striped"
37
41
  td== relative_time(@retry['failed_at'].is_a?(Numeric) ? Time.at(@retry['failed_at']) : Time.parse(@retry['failed_at']))
38
42
  tr
39
43
  th = t('NextRetry')
40
- td== relative_time(Time.at(@retry.score))
44
+ td== relative_time(@retry.at)
41
45
 
42
46
  h3 = t('Error')
43
47
  table class="error table table-bordered table-striped"
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.12.1
4
+ version: 2.12.3
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: 2013-05-31 00:00:00.000000000 Z
12
+ date: 2013-06-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -114,7 +114,7 @@ dependencies:
114
114
  requirements:
115
115
  - - ! '>='
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: 1.1.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
@@ -122,7 +122,7 @@ dependencies:
122
122
  requirements:
123
123
  - - ! '>='
124
124
  - !ruby/object:Gem::Version
125
- version: '0'
125
+ version: 1.1.0
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: minitest
128
128
  requirement: !ruby/object:Gem::Requirement