sidekiq 2.17.8 → 3.0.0

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/3.0-Upgrade.md +63 -0
  3. data/Changes.md +66 -3
  4. data/Contributing.md +1 -3
  5. data/Pro-Changes.md +18 -0
  6. data/README.md +2 -2
  7. data/bin/sidekiqctl +19 -6
  8. data/lib/sidekiq.rb +53 -11
  9. data/lib/sidekiq/actor.rb +1 -0
  10. data/lib/sidekiq/api.rb +145 -58
  11. data/lib/sidekiq/cli.rb +22 -18
  12. data/lib/sidekiq/client.rb +44 -14
  13. data/lib/sidekiq/core_ext.rb +5 -8
  14. data/lib/sidekiq/exception_handler.rb +19 -28
  15. data/lib/sidekiq/fetch.rb +3 -3
  16. data/lib/sidekiq/launcher.rb +30 -3
  17. data/lib/sidekiq/logging.rb +2 -2
  18. data/lib/sidekiq/manager.rb +19 -16
  19. data/lib/sidekiq/middleware/chain.rb +1 -1
  20. data/lib/sidekiq/middleware/i18n.rb +1 -1
  21. data/lib/sidekiq/middleware/server/retry_jobs.rb +23 -7
  22. data/lib/sidekiq/processor.rb +36 -54
  23. data/lib/sidekiq/redis_connection.rb +1 -3
  24. data/lib/sidekiq/util.rb +4 -4
  25. data/lib/sidekiq/version.rb +1 -1
  26. data/lib/sidekiq/web.rb +57 -8
  27. data/lib/sidekiq/web_helpers.rb +6 -15
  28. data/lib/sidekiq/worker.rb +3 -1
  29. data/sidekiq.gemspec +5 -5
  30. data/test/test_api.rb +59 -19
  31. data/test/test_cli.rb +1 -1
  32. data/test/test_client.rb +44 -5
  33. data/test/test_exception_handler.rb +4 -87
  34. data/test/test_middleware.rb +3 -2
  35. data/test/test_redis_connection.rb +0 -6
  36. data/test/test_retry.rb +13 -68
  37. data/test/test_scheduled.rb +1 -1
  38. data/test/test_scheduling.rb +5 -0
  39. data/test/test_sidekiq.rb +18 -0
  40. data/test/test_web.rb +98 -58
  41. data/web/assets/stylesheets/application.css +5 -0
  42. data/web/locales/cs.yml +68 -0
  43. data/web/locales/da.yml +9 -1
  44. data/web/locales/de.yml +15 -7
  45. data/web/locales/el.yml +68 -0
  46. data/web/locales/en.yml +8 -3
  47. data/web/locales/es.yml +9 -1
  48. data/web/locales/fr.yml +34 -26
  49. data/web/locales/it.yml +26 -18
  50. data/web/locales/ja.yml +8 -2
  51. data/web/locales/ko.yml +0 -2
  52. data/web/locales/nl.yml +8 -3
  53. data/web/locales/no.yml +9 -3
  54. data/web/locales/pl.yml +0 -1
  55. data/web/locales/pt-br.yml +11 -4
  56. data/web/locales/pt.yml +8 -1
  57. data/web/locales/ru.yml +29 -22
  58. data/web/locales/sv.yml +68 -0
  59. data/web/locales/zh-tw.yml +68 -0
  60. data/web/views/_job_info.erb +8 -2
  61. data/web/views/_summary.erb +13 -7
  62. data/web/views/busy.erb +55 -0
  63. data/web/views/dead.erb +30 -0
  64. data/web/views/layout.erb +1 -0
  65. data/web/views/morgue.erb +66 -0
  66. metadata +29 -30
  67. data/config.ru +0 -18
  68. data/lib/sidekiq/capistrano.rb +0 -5
  69. data/lib/sidekiq/capistrano2.rb +0 -54
  70. data/lib/sidekiq/tasks/sidekiq.rake +0 -119
  71. data/lib/sidekiq/yaml_patch.rb +0 -21
  72. data/test/test_util.rb +0 -18
  73. data/web/views/_workers.erb +0 -22
  74. data/web/views/workers.erb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01d6264503e20644482c431b9381fb01b3a37733
4
- data.tar.gz: a61f928dd7d5f0994959fcddaa0f04e837d685b2
3
+ metadata.gz: 6924f9ac5a08e3bfb8b20204262b281082a590c9
4
+ data.tar.gz: db0c1c65e0f8ca499771c8f68174cc889a977e79
5
5
  SHA512:
6
- metadata.gz: f92ff4829fb64f95480838fdab6f23d297a67a4acb6efe4da61684824d4f745c01969b371c96c63d050b618fb11c690293e50807b7e90b972f750eb4080abb29
7
- data.tar.gz: 7544d61d9eb35f0983beaaf3e99b20dff42a2b1fbafdee572eb717da81bfb5bdb2e9be54e7359456c8d7f64b1412b9d9a2d15d07cda7edbbf5fdb2c893ae37ab
6
+ metadata.gz: 541f6b4546da89be32045f56d4e5dd41aa9d97d03860ee4fdde696cfb88d9b6ca68d4fb5f83377696d4ee1a9fe572d129afe8c5ddfebb61809302ed0cd54f88c
7
+ data.tar.gz: d396cad4b53238a174c722a92e485fb8657664d1965ea826c264c2aab1e5e25991023edce4556e3ceff05250bccf02892190b9fa69a1bc1c40944a71cb20d445
@@ -0,0 +1,63 @@
1
+ # Upgrading to Sidekiq 3.0
2
+
3
+ Sidekiq 3.0 brings several new features but also removes old APIs and
4
+ changes a few data elements in Redis. To upgrade cleanly:
5
+
6
+ * Upgrade to the latest Sidekiq 2.x and run it for a few weeks.
7
+ `gem 'sidekiq', '< 3'`
8
+ This is only needed if you have retries pending.
9
+ * 3rd party gems which use **client-side middleware** will need to update
10
+ due to an API change. The Redis connection for a particular job is
11
+ passed thru the middleware to handle sharding where jobs can
12
+ be pushed to different redis server instances.
13
+
14
+ `def call(worker_class, msg, queue, redis_pool)`
15
+
16
+ Client-side middleware should use `redis_pool.with { |conn| ... }` to
17
+ perform Redis operations and **not** `Sidekiq.redis`.
18
+ * If you used the capistrano integration, you'll need to pull in the
19
+ new [capistrano-sidekiq](https://github.com/seuros/capistrano-sidekiq)
20
+ gem and use it in your deploy.rb.
21
+ * API changes:
22
+ - `Sidekiq::Client.registered_workers` replaced by `Sidekiq::Workers.new`
23
+ - `Sidekiq::Client.registered_queues` replaced by `Sidekiq::Queue.all`
24
+ - `Sidekiq::Worker#retries_exhausted` replaced by `Sidekiq::Worker.sidekiq_retries_exhausted`
25
+ - `Sidekiq::Workers#each` has changed significantly with a reworking
26
+ of Sidekiq's internal process/thread data model.
27
+ * Redis-to-Go is no longer transparently activated on Heroku so as to not play
28
+ favorites with any particular Redis service. You need to set a config option
29
+ for your app:
30
+ `heroku config:set REDIS_PROVIDER=REDISTOGO_URL`
31
+ * Anyone using Airbrake, Honeybadger, Exceptional or ExceptionNotifier
32
+ will need to update their error gem version to the latest to pull in
33
+ Sidekiq support. Sidekiq will not provide explicit support for these
34
+ services so as to not play favorites with any particular error service.
35
+ * MRI 1.9 is no longer officially supported. Sidekiq's official
36
+ support policy is to support the current and previous major releases
37
+ of MRI and Rails. As of February 2014, that's MRI 2.1, MRI 2.0, JRuby 1.7, Rails 4.0
38
+ and Rails 3.2. I will consider PRs to fix issues found by users for
39
+ other platforms/versions.
40
+
41
+ ## Error Service Providers
42
+
43
+ If you previously provided a middleware to capture job errors, you
44
+ should instead provide a global error handler with Sidekiq 3.0. This
45
+ ensures **any** error within Sidekiq will be logged appropriately, not
46
+ just during job execution.
47
+
48
+ ```ruby
49
+ if Sidekiq::VERSION < '3'
50
+ # old behavior
51
+ Sidekiq.configure_server do |config|
52
+ config.server_middleware do |chain|
53
+ chain.add MyErrorService::Middleware
54
+ end
55
+ end
56
+ else
57
+ Sidekiq.configure_server do |config|
58
+ config.error_handlers << Proc.new {|ex,context| MyErrorService.notify(ex, context) }
59
+ end
60
+ end
61
+ ```
62
+
63
+ Your error handler must respond to `call(exception, context_hash)`.
data/Changes.md CHANGED
@@ -1,8 +1,71 @@
1
- 2.17.8
1
+ 3.0.0
2
2
  -----------
3
3
 
4
- - Tighten allowed gem versions to prevent future incompatibility. Fixes
5
- breakage with Celluloid 0.16.
4
+ **Not yet released**
5
+
6
+ Please see [3.0-Upgrade.md](3.0-Upgrade.md) for more comprehensive upgrade notes.
7
+
8
+ - **Dead Job Queue** - jobs which run out of retries are now moved to a dead
9
+ job queue. These jobs must be retried manually or they will expire
10
+ after 6 months or 10,000 jobs. The Web UI contains a "Dead" tab
11
+ exposing these jobs. Use `sidekiq_options :retry => false` if you
12
+ don't wish jobs to be retried or put in the DJQ. Use
13
+ `sidekiq_options :retry => 0` if you don't want jobs to retry but go
14
+ straight to the DJQ.
15
+ - **Process Lifecycle Events** - you can now register blocks to run at
16
+ certain points during the Sidekiq process lifecycle: startup, quiet and
17
+ shutdown.
18
+ ```ruby
19
+ Sidekiq.configure_server do |config|
20
+ config.on(:startup) do
21
+ # do something
22
+ end
23
+ end
24
+ ```
25
+ - **Global Error Handlers** - blocks of code which handle errors that
26
+ occur anywhere within Sidekiq, not just within middleware.
27
+ ```ruby
28
+ Sidekiq.configure_server do |config|
29
+ config.error_handlers << Proc.new {|ex,ctx| ... }
30
+ end
31
+ ```
32
+ - **Process Heartbeat** - each Sidekiq process will ping Redis every 5
33
+ seconds to give a summary of the Sidekiq population at work.
34
+ - The Workers tab is now renamed to Busy and contains a list of live
35
+ Sidekiq processes and jobs in progress based on the heartbeat.
36
+ - **Shardable Client** - Sidekiq::Client instances can use a custom
37
+ Redis connection pool, allowing very large Sidekiq installations to scale by
38
+ sharding: sending different jobs to different Redis instances.
39
+ ```ruby
40
+ client = Sidekiq::Client.new(ConnectionPool.new { Redis.new })
41
+ client.push(...)
42
+ ```
43
+ ```ruby
44
+ Sidekiq::Client.via(ConnectionPool.new { Redis.new }) do
45
+ FooWorker.perform_async
46
+ BarWorker.perform_async
47
+ end
48
+ ```
49
+ **Sharding support does require a breaking change to client-side
50
+ middleware, see 3.0-Upgrade.md.**
51
+ - New Chinese, Greek, Swedish and Czech translations for the Web UI.
52
+ - Updated most languages translations for the new UI features.
53
+ - **Remove official Capistrano integration** - this integration has been
54
+ moved into the [capistrano-sidekiq](https://github.com/seuros/capistrano-sidekiq) gem.
55
+ - **Remove official support for MRI 1.9** - Things still might work but
56
+ I no longer actively test on it.
57
+ - **Remove built-in support for Redis-to-Go**.
58
+ Heroku users: `heroku config:set REDIS_PROVIDER=REDISTOGO_URL`
59
+ - **Remove built-in error integration for Airbrake, Honeybadger, ExceptionNotifier and Exceptional**.
60
+ Each error gem should provide its own Sidekiq integration. Update your error gem to the latest
61
+ version to pick up Sidekiq support.
62
+ - Upgrade to connection\_pool 2.0 which now creates connections lazily.
63
+ - Remove deprecated Sidekiq::Client.registered\_\* APIs
64
+ - Remove deprecated support for the old Sidekiq::Worker#retries\_exhausted method.
65
+ - Removed 'sidekiq/yaml\_patch', this was never documented or recommended.
66
+ - Removed --profile option, #1592
67
+ - Remove usage of the term 'Worker' in the UI for clarity. Users would call both threads and
68
+ processes 'workers'. Instead, use "Thread", "Process" or "Job".
6
69
 
7
70
  2.17.7
8
71
  -----------
@@ -1,7 +1,6 @@
1
1
  # Contributing
2
2
 
3
- First of all, thank you for even opening this file up! I hope you find
4
- it worthwhile to help out with Sidekiq.
3
+ First of all, thank you! I hope you find it worthwhile to help out with Sidekiq.
5
4
 
6
5
  ## Issues
7
6
 
@@ -21,7 +20,6 @@ fix or new functionality. Functionality must meet my design goals and
21
20
  vision for the project to be accepted; I would be happy to discuss how
22
21
  your idea can best fit into Sidekiq.
23
22
 
24
-
25
23
  ## Sponsorship
26
24
 
27
25
  If you've got more money than time and want to sponsor Sidekiq's continued support, your company can buy [Sidekiq Pro](http://sidekiq.org/pro). You get great functionality, I continue to fix bugs and enhance Sidekiq for years to come.
@@ -3,6 +3,24 @@ Sidekiq Pro Changelog
3
3
 
4
4
  Please see [http://sidekiq.org/pro](http://sidekiq.org/pro) for more details and how to buy.
5
5
 
6
+ HEAD
7
+ -----------
8
+
9
+ - Compatible with Sidekiq 3.
10
+
11
+ 1.5.1
12
+ -----------
13
+
14
+ - Due to a breaking API change in Sidekiq 3.0, this version is limited
15
+ to Sidekiq 2.x.
16
+
17
+ 1.5.0
18
+ -----------
19
+
20
+ - Compatible with upcoming Sidekiq 3.0 release
21
+ - Fix issue on Heroku where reliable fetch could orphan jobs [#1573]
22
+
23
+
6
24
  1.4.3
7
25
  -----------
8
26
 
data/README.md CHANGED
@@ -17,14 +17,14 @@ use the Resque client to enqueue jobs in Redis to be processed by Sidekiq.
17
17
 
18
18
  At the same time, Sidekiq uses multithreading so it is much more memory efficient than
19
19
  Resque (which forks a new process for every job). You'll find that you might need
20
- 50 200MB resque processes to peg your CPU whereas one 300MB Sidekiq process will peg
20
+ 10 200MB resque processes to peg your CPU whereas one 300MB Sidekiq process will peg
21
21
  the same CPU and perform the same amount of work.
22
22
 
23
23
 
24
24
  Requirements
25
25
  -----------------
26
26
 
27
- I test with the latest Ruby (2.0) and JRuby versions (1.7). Other versions/VMs
27
+ I test with the latest MRI (2.1, 2.0) and JRuby versions (1.7). Other versions/VMs
28
28
  are untested but might work fine.
29
29
 
30
30
  The last two major Rails releases (3.2 and 4.0) are officially supported, other
@@ -3,9 +3,19 @@
3
3
  require 'fileutils'
4
4
 
5
5
  class Sidekiqctl
6
+ DEFAULT_TIMEOUT = 10
6
7
 
7
8
  attr_reader :stage, :pidfile, :timeout
8
9
 
10
+ def self.print_usage
11
+ puts
12
+ puts "Usage: #{File.basename($0)} <command> <pidfile> <timeout>"
13
+ puts " where <command> is either 'quiet', 'stop' or 'shutdown'"
14
+ puts " <pidfile> is path to a pidfile"
15
+ puts " <timeout> is number of seconds to wait till Sidekiq exits (default: #{Sidekiqctl::DEFAULT_TIMEOUT})"
16
+ puts
17
+ end
18
+
9
19
  def initialize(stage, pidfile, timeout)
10
20
  @stage = stage
11
21
  @pidfile = pidfile
@@ -67,12 +77,15 @@ class Sidekiqctl
67
77
  quiet
68
78
  stop
69
79
  end
70
-
71
80
  end
72
81
 
73
- stage = ARGV[0]
74
- pidfile = ARGV[1]
75
- timeout = ARGV[2].to_i
76
- timeout = 10 if timeout == 0
82
+ if ARGV.length < 2
83
+ Sidekiqctl.print_usage
84
+ else
85
+ stage = ARGV[0]
86
+ pidfile = ARGV[1]
87
+ timeout = ARGV[2].to_i
88
+ timeout = Sidekiqctl::DEFAULT_TIMEOUT if timeout == 0
77
89
 
78
- Sidekiqctl.new(stage, pidfile, timeout)
90
+ Sidekiqctl.new(stage, pidfile, timeout)
91
+ end
@@ -4,8 +4,6 @@ require 'sidekiq/logging'
4
4
  require 'sidekiq/client'
5
5
  require 'sidekiq/worker'
6
6
  require 'sidekiq/redis_connection'
7
- require 'sidekiq/util'
8
- require 'sidekiq/api'
9
7
 
10
8
  require 'json'
11
9
 
@@ -19,7 +17,12 @@ module Sidekiq
19
17
  :require => '.',
20
18
  :environment => nil,
21
19
  :timeout => 8,
22
- :profile => false,
20
+ :error_handlers => [],
21
+ :lifecycle_events => {
22
+ :startup => [],
23
+ :quiet => [],
24
+ :shutdown => [],
25
+ },
23
26
  }
24
27
 
25
28
  def self.❨╯°□°❩╯︵┻━┻
@@ -63,17 +66,18 @@ module Sidekiq
63
66
 
64
67
  def self.redis(&block)
65
68
  raise ArgumentError, "requires a block" if !block
66
- @redis ||= Sidekiq::RedisConnection.create(@hash || {})
67
- @redis.with(&block)
69
+ redis_pool.with(&block)
68
70
  end
69
71
 
70
- def self.redis=(hash)
71
- return @redis = hash if hash.is_a?(ConnectionPool)
72
+ def self.redis_pool
73
+ @redis ||= Sidekiq::RedisConnection.create
74
+ end
72
75
 
73
- if hash.is_a?(Hash)
74
- @hash = hash
76
+ def self.redis=(hash)
77
+ @redis = if hash.is_a?(ConnectionPool)
78
+ hash
75
79
  else
76
- raise ArgumentError, "redis= requires a Hash or ConnectionPool"
80
+ Sidekiq::RedisConnection.create(hash)
77
81
  end
78
82
  end
79
83
 
@@ -94,7 +98,7 @@ module Sidekiq
94
98
  end
95
99
 
96
100
  def self.default_worker_options
97
- @default_worker_options || { 'retry' => true, 'queue' => 'default' }
101
+ defined?(@default_worker_options) ? @default_worker_options : { 'retry' => true, 'queue' => 'default' }
98
102
  end
99
103
 
100
104
  def self.load_json(string)
@@ -117,6 +121,44 @@ module Sidekiq
117
121
  self.options[:poll_interval] = interval
118
122
  end
119
123
 
124
+ # Register a proc to handle any error which occurs within the Sidekiq process.
125
+ #
126
+ # Sidekiq.configure_server do |config|
127
+ # config.error_handlers << Proc.new {|ex,ctx_hash| MyErrorService.notify(ex, ctx_hash) }
128
+ # end
129
+ #
130
+ # The default error handler logs errors to Sidekiq.logger.
131
+ def self.error_handlers
132
+ self.options[:error_handlers]
133
+ end
134
+
135
+ # Register a block to run at a point in the Sidekiq lifecycle.
136
+ # :startup, :quiet or :shutdown are valid events.
137
+ #
138
+ # Sidekiq.configure_server do |config|
139
+ # config.on(:shutdown) do
140
+ # puts "Goodbye cruel world!"
141
+ # end
142
+ # end
143
+ def self.on(event, &block)
144
+ raise ArgumentError, "Symbols only please: #{event}" if !event.is_a?(Symbol)
145
+ raise ArgumentError, "Invalid event name: #{event}" if !options[:lifecycle_events].keys.include?(event)
146
+ options[:lifecycle_events][event] << block
147
+ end
148
+
149
+ BANNER = %q{ s
150
+ ss
151
+ sss sss ss
152
+ s sss s ssss sss ____ _ _ _ _
153
+ s sssss ssss / ___|(_) __| | ___| | _(_) __ _
154
+ s sss \___ \| |/ _` |/ _ \ |/ / |/ _` |
155
+ s sssss s ___) | | (_| | __/ <| | (_| |
156
+ ss s s |____/|_|\__,_|\___|_|\_\_|\__, |
157
+ s s s |_|
158
+ s s
159
+ sss
160
+ sss }
161
+
120
162
  end
121
163
 
122
164
  require 'sidekiq/extensions/class_methods'
@@ -16,6 +16,7 @@ module Sidekiq
16
16
  def after(interval)
17
17
  end
18
18
  def alive?
19
+ @dead = false unless defined?(@dead)
19
20
  !@dead
20
21
  end
21
22
  def terminate
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require 'sidekiq'
2
3
 
3
4
  module Sidekiq
@@ -14,8 +15,13 @@ module Sidekiq
14
15
  all = %w(failed processed)
15
16
  stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s)
16
17
 
18
+ mset_args = []
19
+ stats.each do |stat|
20
+ mset_args << "stat:#{stat}"
21
+ mset_args << 0
22
+ end
17
23
  Sidekiq.redis do |conn|
18
- stats.each { |stat| conn.set("stat:#{stat}", 0) }
24
+ conn.mset(*mset_args)
19
25
  end
20
26
  end
21
27
 
@@ -23,8 +29,16 @@ module Sidekiq
23
29
  Sidekiq.redis do |conn|
24
30
  queues = conn.smembers('queues')
25
31
 
32
+ lengths = conn.pipelined do
33
+ queues.each do |queue|
34
+ conn.llen("queue:#{queue}")
35
+ end
36
+ end
37
+
38
+ i = 0
26
39
  array_of_arrays = queues.inject({}) do |memo, queue|
27
- memo[queue] = conn.llen("queue:#{queue}")
40
+ memo[queue] = lengths[i]
41
+ i += 1
28
42
  memo
29
43
  end.sort_by { |_, size| size }
30
44
 
@@ -44,6 +58,10 @@ module Sidekiq
44
58
  Sidekiq.redis {|c| c.zcard('retry') }
45
59
  end
46
60
 
61
+ def dead_size
62
+ Sidekiq.redis {|c| c.zcard('dead') }
63
+ end
64
+
47
65
  class History
48
66
  def initialize(days_previous, start_date = nil)
49
67
  @days_previous = days_previous
@@ -63,15 +81,19 @@ module Sidekiq
63
81
  def date_stat_hash(stat)
64
82
  i = 0
65
83
  stat_hash = {}
84
+ keys = []
85
+ dates = []
86
+
87
+ while i < @days_previous
88
+ date = @start_date - i
89
+ keys << "stat:#{stat}:#{date}"
90
+ dates << date
91
+ i += 1
92
+ end
66
93
 
67
94
  Sidekiq.redis do |conn|
68
- while i < @days_previous
69
- date = @start_date - i
70
- value = conn.get("stat:#{stat}:#{date}")
71
-
72
- stat_hash[date.to_s] = value ? value.to_i : 0
73
-
74
- i += 1
95
+ conn.mget(keys).each_with_index do |value, i|
96
+ stat_hash[dates[i].to_s] = value ? value.to_i : 0
75
97
  end
76
98
  end
77
99
 
@@ -151,6 +173,7 @@ module Sidekiq
151
173
  end
152
174
  end
153
175
  end
176
+ alias_method :💣, :clear
154
177
  end
155
178
 
156
179
  ##
@@ -209,6 +232,7 @@ module Sidekiq
209
232
 
210
233
  class SortedEntry < Job
211
234
  attr_reader :score
235
+ attr_reader :parent
212
236
 
213
237
  def initialize(parent, score, item)
214
238
  super(item)
@@ -243,11 +267,11 @@ module Sidekiq
243
267
  end
244
268
 
245
269
  def retry
246
- raise "Retry not available on jobs not in the Retry queue." unless item["failed_at"]
270
+ raise "Retry not available on jobs which have not failed" unless item["failed_at"]
247
271
  Sidekiq.redis do |conn|
248
272
  results = conn.multi do
249
- conn.zrangebyscore('retry', score, score)
250
- conn.zremrangebyscore('retry', score, score)
273
+ conn.zrangebyscore(parent.name, score, score)
274
+ conn.zremrangebyscore(parent.name, score, score)
251
275
  end.first
252
276
  results.map do |message|
253
277
  msg = Sidekiq.load_json(message)
@@ -272,6 +296,16 @@ module Sidekiq
272
296
  Sidekiq.redis {|c| c.zcard(name) }
273
297
  end
274
298
 
299
+ def clear
300
+ Sidekiq.redis do |conn|
301
+ conn.del(name)
302
+ end
303
+ end
304
+ alias_method :💣, :clear
305
+ end
306
+
307
+ class JobSet < SortedSet
308
+
275
309
  def schedule(timestamp, message)
276
310
  Sidekiq.redis do |conn|
277
311
  conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(message))
@@ -349,11 +383,6 @@ module Sidekiq
349
383
  end
350
384
  end
351
385
 
352
- def clear
353
- Sidekiq.redis do |conn|
354
- conn.del(name)
355
- end
356
- end
357
386
  end
358
387
 
359
388
  ##
@@ -368,7 +397,7 @@ module Sidekiq
368
397
  # retri.args[0] == 'User' &&
369
398
  # retri.args[1] == 'setup_new_subscriber'
370
399
  # end.map(&:delete)
371
- class ScheduledSet < SortedSet
400
+ class ScheduledSet < JobSet
372
401
  def initialize
373
402
  super 'schedule'
374
403
  end
@@ -386,7 +415,7 @@ module Sidekiq
386
415
  # retri.args[0] == 'User' &&
387
416
  # retri.args[1] == 'setup_new_subscriber'
388
417
  # end.map(&:delete)
389
- class RetrySet < SortedSet
418
+ class RetrySet < JobSet
390
419
  def initialize
391
420
  super 'retry'
392
421
  end
@@ -398,6 +427,76 @@ module Sidekiq
398
427
  end
399
428
  end
400
429
 
430
+ class DeadSet < JobSet
431
+ def initialize
432
+ super 'dead'
433
+ end
434
+
435
+ def retry_all
436
+ while size > 0
437
+ each(&:retry)
438
+ end
439
+ end
440
+ end
441
+
442
+ ##
443
+ # Enumerates the set of Sidekiq processes which are actively working
444
+ # right now. Each process send a heartbeat to Redis every 5 seconds
445
+ # so this set should be relatively accurate, barring network partitions.
446
+ #
447
+ # Yields a hash of data which looks something like this:
448
+ #
449
+ # {
450
+ # 'hostname' => 'app-1.example.com',
451
+ # 'started_at' => <process start time>,
452
+ # 'pid' => 12345,
453
+ # 'tag' => 'myapp'
454
+ # 'concurrency' => 25,
455
+ # 'queues' => ['default', 'low'],
456
+ # 'busy' => 10,
457
+ # 'beat' => <last heartbeat>,
458
+ # }
459
+
460
+ class ProcessSet
461
+ include Enumerable
462
+
463
+ def each(&block)
464
+ procs = Sidekiq.redis { |conn| conn.smembers('processes') }
465
+
466
+ to_prune = []
467
+ sorted = procs.sort
468
+ Sidekiq.redis do |conn|
469
+ # We're making a tradeoff here between consuming more memory instead of
470
+ # making more roundtrips to Redis, but if you have hundreds or thousands of workers,
471
+ # you'll be happier this way
472
+ result = conn.pipelined do
473
+ sorted.each do |key|
474
+ conn.hmget(key, 'info', 'busy', 'beat')
475
+ end
476
+ end
477
+
478
+ result.each_with_index do |(info, busy, at_s), i|
479
+ # the hash named key has an expiry of 60 seconds.
480
+ # if it's not found, that means the process has not reported
481
+ # in to Redis and probably died.
482
+ (to_prune << sorted[i]; next) if info.nil?
483
+ hash = Sidekiq.load_json(info)
484
+ yield hash.merge('busy' => busy.to_i, 'beat' => at_s.to_f)
485
+ end
486
+ end
487
+
488
+ Sidekiq.redis {|conn| conn.srem('processes', to_prune) } unless to_prune.empty?
489
+ nil
490
+ end
491
+
492
+ # This method is not guaranteed accurate since it does not prune the set
493
+ # based on current heartbeat. #each does that and ensures the set only
494
+ # contains Sidekiq processes which have sent a heartbeat within the last
495
+ # 60 seconds.
496
+ def size
497
+ Sidekiq.redis { |conn| conn.scard('processes') }
498
+ end
499
+ end
401
500
 
402
501
  ##
403
502
  # Programmatic access to the current active worker set.
@@ -410,62 +509,50 @@ module Sidekiq
410
509
  #
411
510
  # workers = Sidekiq::Workers.new
412
511
  # workers.size => 2
413
- # workers.each do |name, work, started_at|
414
- # # name is a unique identifier per worker
512
+ # workers.each do |process_id, thread_id, work|
513
+ # # process_id is a unique identifier per Sidekiq process
514
+ # # thread_id is a unique identifier per thread
415
515
  # # work is a Hash which looks like:
416
516
  # # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg }
417
- # # started_at is a String rep of the time when the worker started working on the job
517
+ # # run_at is an epoch Integer.
418
518
  # end
419
-
519
+ #
420
520
  class Workers
421
521
  include Enumerable
422
522
 
423
523
  def each(&block)
424
524
  Sidekiq.redis do |conn|
425
- workers = conn.smembers("workers")
426
- workers.each do |w|
427
- json = conn.get("worker:#{w}")
428
- next unless json
429
- msg = Sidekiq.load_json(json)
430
- block.call(w, msg, Time.at(msg['run_at']).to_s)
525
+ procs = conn.smembers('processes')
526
+ procs.sort.each do |key|
527
+ valid, workers = conn.pipelined do
528
+ conn.exists(key)
529
+ conn.hgetall("#{key}:workers")
530
+ end
531
+ next unless valid
532
+ workers.each_pair do |tid, json|
533
+ yield key, tid, Sidekiq.load_json(json)
534
+ end
431
535
  end
432
536
  end
433
537
  end
434
538
 
539
+ # Note that #size is only as accurate as Sidekiq's heartbeat,
540
+ # which happens every 5 seconds. It is NOT real-time.
541
+ #
542
+ # Not very efficient if you have lots of Sidekiq
543
+ # processes but the alternative is a global counter
544
+ # which can easily get out of sync with crashy processes.
435
545
  def size
436
546
  Sidekiq.redis do |conn|
437
- conn.scard("workers")
438
- end.to_i
439
- end
547
+ procs = conn.smembers('processes')
548
+ return 0 if procs.empty?
440
549
 
441
- # Prune old worker entries from the Busy set. Worker entries
442
- # can be orphaned if Sidekiq hard crashes while processing jobs.
443
- # Default is to delete worker entries older than one hour.
444
- #
445
- # Returns the number of records removed.
446
- def prune(older_than=60*60)
447
- to_rem = []
448
- Sidekiq.redis do |conn|
449
- conn.smembers('workers').each do |w|
450
- msg = conn.get("worker:#{w}")
451
- if !msg
452
- to_rem << w
453
- else
454
- m = Sidekiq.load_json(msg)
455
- run_at = Time.at(m['run_at'])
456
- # prune jobs older than one hour
457
- if run_at < (Time.now - older_than)
458
- to_rem << w
459
- else
460
- end
550
+ conn.pipelined do
551
+ procs.each do |key|
552
+ conn.hget(key, 'busy')
461
553
  end
462
- end
463
- end
464
-
465
- if to_rem.size > 0
466
- Sidekiq.redis { |conn| conn.srem('workers', to_rem) }
554
+ end.map(&:to_i).inject(:+)
467
555
  end
468
- to_rem.size
469
556
  end
470
557
  end
471
558