sidekiq 3.4.1 → 4.0.2

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

Potentially problematic release.


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

Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -2
  3. data/4.0-Upgrade.md +50 -0
  4. data/COMM-LICENSE +55 -45
  5. data/Changes.md +85 -1
  6. data/Ent-Changes.md +79 -0
  7. data/Gemfile +7 -1
  8. data/Pro-2.0-Upgrade.md +2 -2
  9. data/Pro-3.0-Upgrade.md +46 -0
  10. data/Pro-Changes.md +60 -2
  11. data/README.md +20 -16
  12. data/bin/sidekiq +4 -0
  13. data/bin/sidekiqctl +8 -2
  14. data/bin/sidekiqload +167 -0
  15. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +2 -2
  16. data/lib/generators/sidekiq/templates/worker_test.rb.erb +5 -5
  17. data/lib/sidekiq/api.rb +43 -33
  18. data/lib/sidekiq/cli.rb +41 -42
  19. data/lib/sidekiq/client.rb +5 -10
  20. data/lib/sidekiq/fetch.rb +35 -111
  21. data/lib/sidekiq/launcher.rb +102 -42
  22. data/lib/sidekiq/manager.rb +80 -180
  23. data/lib/sidekiq/middleware/server/logging.rb +13 -8
  24. data/lib/sidekiq/middleware/server/retry_jobs.rb +6 -6
  25. data/lib/sidekiq/processor.rb +126 -97
  26. data/lib/sidekiq/redis_connection.rb +23 -5
  27. data/lib/sidekiq/scheduled.rb +47 -26
  28. data/lib/sidekiq/testing.rb +139 -17
  29. data/lib/sidekiq/util.rb +20 -0
  30. data/lib/sidekiq/version.rb +1 -1
  31. data/lib/sidekiq/web.rb +17 -1
  32. data/lib/sidekiq/web_helpers.rb +33 -5
  33. data/lib/sidekiq/worker.rb +16 -0
  34. data/lib/sidekiq.rb +37 -14
  35. data/sidekiq.gemspec +10 -11
  36. data/test/helper.rb +45 -10
  37. data/test/test_actors.rb +137 -0
  38. data/test/test_api.rb +417 -384
  39. data/test/test_cli.rb +29 -59
  40. data/test/test_client.rb +60 -135
  41. data/test/test_extensions.rb +29 -23
  42. data/test/test_fetch.rb +2 -57
  43. data/test/test_launcher.rb +80 -0
  44. data/test/test_logging.rb +1 -1
  45. data/test/test_manager.rb +16 -131
  46. data/test/test_middleware.rb +3 -5
  47. data/test/test_processor.rb +110 -76
  48. data/test/test_rails.rb +21 -0
  49. data/test/test_redis_connection.rb +0 -1
  50. data/test/test_retry.rb +114 -162
  51. data/test/test_scheduled.rb +11 -17
  52. data/test/test_scheduling.rb +20 -42
  53. data/test/test_sidekiq.rb +46 -16
  54. data/test/test_testing.rb +80 -20
  55. data/test/test_testing_fake.rb +83 -8
  56. data/test/test_testing_inline.rb +3 -3
  57. data/test/test_util.rb +16 -0
  58. data/test/test_web.rb +28 -9
  59. data/test/test_web_helpers.rb +3 -2
  60. data/web/assets/images/favicon.ico +0 -0
  61. data/web/assets/javascripts/application.js +6 -1
  62. data/web/assets/javascripts/dashboard.js +2 -8
  63. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +14 -14
  64. data/web/assets/stylesheets/application.css +33 -56
  65. data/web/locales/de.yml +1 -1
  66. data/web/locales/en.yml +2 -0
  67. data/web/locales/fr.yml +2 -2
  68. data/web/locales/ja.yml +10 -1
  69. data/web/locales/{no.yml → nb.yml} +10 -2
  70. data/web/locales/uk.yml +76 -0
  71. data/web/views/_footer.erb +2 -7
  72. data/web/views/_job_info.erb +5 -1
  73. data/web/views/_nav.erb +2 -2
  74. data/web/views/_poll_js.erb +5 -0
  75. data/web/views/{_poll.erb → _poll_link.erb} +0 -3
  76. data/web/views/busy.erb +2 -1
  77. data/web/views/dead.erb +1 -0
  78. data/web/views/layout.erb +2 -0
  79. data/web/views/morgue.erb +3 -0
  80. data/web/views/queue.erb +1 -0
  81. data/web/views/queues.erb +1 -0
  82. data/web/views/retries.erb +3 -0
  83. data/web/views/retry.erb +1 -0
  84. data/web/views/scheduled.erb +1 -0
  85. data/web/views/scheduled_job_info.erb +1 -0
  86. metadata +75 -55
  87. data/lib/sidekiq/actor.rb +0 -39
  88. data/test/test_worker_generator.rb +0 -17
data/README.md CHANGED
@@ -4,7 +4,6 @@ Sidekiq
4
4
  [![Gem Version](https://badge.fury.io/rb/sidekiq.svg)](https://rubygems.org/gems/sidekiq)
5
5
  [![Code Climate](https://codeclimate.com/github/mperham/sidekiq.svg)](https://codeclimate.com/github/mperham/sidekiq)
6
6
  [![Build Status](https://travis-ci.org/mperham/sidekiq.svg)](https://travis-ci.org/mperham/sidekiq)
7
- [![Coverage Status](https://coveralls.io/repos/mperham/sidekiq/badge.svg?branch=master)](https://coveralls.io/r/mperham/sidekiq)
8
7
  [![Gitter Chat](https://badges.gitter.im/mperham/sidekiq.svg)](https://gitter.im/mperham/sidekiq)
9
8
 
10
9
 
@@ -19,21 +18,26 @@ message format as Resque so it can integrate into an existing Resque processing
19
18
  You can have Sidekiq and Resque run side-by-side at the same time and
20
19
  use the Resque client to enqueue jobs in Redis to be processed by Sidekiq.
21
20
 
22
- At the same time, Sidekiq uses multithreading so it is much more memory efficient than
23
- Resque (which forks a new process for every job). You'll find that you might need
24
- 10 200MB resque processes to peg your CPU whereas one 300MB Sidekiq process will peg
25
- the same CPU and perform the same amount of work.
21
+ Sidekiq is fast.
22
+
23
+ Version | Latency | Garbage created for 10,000 jobs | Time to process 100,000 jobs | Throughput
24
+ -----------------|------|---------|---------|------------------------
25
+ Sidekiq 4.0.0 | 10ms | 151 MB | 22 sec | **4500 jobs/sec**
26
+ Sidekiq 3.5.1 | 22ms | 1257 MB | 125 sec | 800 jobs/sec
27
+ Resque 1.25.2 | - | - | 420 sec | 240 jobs/sec
28
+ DelayedJob 4.1.1 | - | - | 465 sec | 215 jobs/sec
26
29
 
27
30
 
28
31
  Requirements
29
32
  -----------------
30
33
 
31
- I test with the latest MRI (2.2, 2.1 and 2.0) and JRuby versions (1.7). Other versions/VMs
32
- are untested but might work fine. MRI 1.9 is no longer supported.
34
+ I test with the latest CRuby (2.2, 2.1 and 2.0) and JRuby versions (9k). Other versions/VMs
35
+ are untested but might work fine. CRuby 1.9 is not supported.
33
36
 
34
- All Rails releases starting from 3.2 are officially supported.
37
+ All Rails releases from 3.2 are officially supported.
35
38
 
36
- Redis 2.4 or greater is required.
39
+ Redis 2.8 or greater is required. 3.0.3+ is recommended for large
40
+ installations with thousands of worker threads.
37
41
 
38
42
 
39
43
  Installation
@@ -45,7 +49,7 @@ Installation
45
49
  Getting Started
46
50
  -----------------
47
51
 
48
- See the [sidekiq home page](http://sidekiq.org) for the simple 3-step process.
52
+ See the [Getting Started wiki page](https://github.com/mperham/sidekiq/wiki/Getting-Started) and follow the simple setup process.
49
53
  You can watch [Railscast #366](http://railscasts.com/episodes/366-sidekiq) to see Sidekiq in action. If you do everything right, you should see this:
50
54
 
51
55
  ![Web UI](https://github.com/mperham/sidekiq/raw/master/examples/web-ui.png)
@@ -54,10 +58,10 @@ You can watch [Railscast #366](http://railscasts.com/episodes/366-sidekiq) to se
54
58
  Want to Upgrade?
55
59
  -------------------
56
60
 
57
- I also sell Sidekiq Pro, an extension to Sidekiq which provides more
58
- features, a commercial-friendly license and allows you to support high
61
+ I also sell Sidekiq Pro and Sidekiq Enterprise, extensions to Sidekiq which provide more
62
+ features, a commercial-friendly license and allow you to support high
59
63
  quality open source development all at the same time. Please see the
60
- [Sidekiq Pro](http://sidekiq.org/pro) homepage for more detail.
64
+ [Sidekiq](http://sidekiq.org/) homepage for more detail.
61
65
 
62
66
 
63
67
  More Information
@@ -67,7 +71,7 @@ Please see the [sidekiq wiki](https://github.com/mperham/sidekiq/wiki) for the o
67
71
  [mperham/sidekiq on Gitter](https://gitter.im/mperham/sidekiq) is dedicated to this project,
68
72
  but bug reports or feature requests suggestions should still go through [issues on Github](https://github.com/mperham/sidekiq/issues). Release announcements are made to the [@sidekiq](https://twitter.com/sidekiq) Twitter account.
69
73
 
70
- You may also find useful a [Google Group](https://groups.google.com/forum/#!forum/sidekiq) dedicated to Sidekiq discussion and [a Sidekiq tag](https://stackoverflow.com/questions/tagged/sidekiq) on Stack Overflow.
74
+ You may also find useful a [Reddit area](https://reddit.com/r/sidekiq) dedicated to Sidekiq discussion and [a Sidekiq tag](https://stackoverflow.com/questions/tagged/sidekiq) on Stack Overflow.
71
75
 
72
76
 
73
77
  Problems?
@@ -76,14 +80,14 @@ Problems?
76
80
  **Please do not directly email any Sidekiq committers with questions or problems.** A community is best served when discussions are held in public.
77
81
 
78
82
  If you have a problem, please review the [FAQ](https://github.com/mperham/sidekiq/wiki/FAQ) and [Troubleshooting](https://github.com/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, chat in Gitter, or open a new issue.
79
- The mailing list is the preferred place to ask questions on usage. If you are encountering what you think is a bug, please open an issue.
83
+ StackOverflow or Reddit is the preferred place to ask questions on usage. If you are encountering what you think is a bug, please open an issue.
80
84
 
81
85
 
82
86
  Thanks
83
87
  -----------------
84
88
 
85
89
  Sidekiq stays fast by using the [JProfiler java profiler](http://www.ej-technologies.com/products/jprofiler/overview.html) to find and fix
86
- performance problems on JRuby. Unfortunately MRI does not have good profile tooling.
90
+ performance problems on JRuby. Unfortunately MRI does not have good multithreaded profiling tools.
87
91
 
88
92
 
89
93
  License
data/bin/sidekiq CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Quiet some warnings we see when running in warning mode:
4
+ # RUBYOPT=-w bundle exec sidekiq
5
+ $TESTING = false
6
+
3
7
  require_relative '../lib/sidekiq/cli'
4
8
 
5
9
  begin
data/bin/sidekiqctl CHANGED
@@ -41,9 +41,13 @@ class Sidekiqctl
41
41
  end
42
42
 
43
43
  def fetch_process
44
- Process.getpgid(pid)
44
+ Process.kill(0, pid)
45
45
  rescue Errno::ESRCH
46
46
  done "Process doesn't exist", :error
47
+ # We were not allowed to send a signal, but the process must have existed
48
+ # when Process.kill() was called.
49
+ rescue Errno::EPERM
50
+ return pid
47
51
  end
48
52
 
49
53
  def done(msg, error = nil)
@@ -67,10 +71,12 @@ class Sidekiqctl
67
71
  `kill -TERM #{pid}`
68
72
  kill_timeout.times do
69
73
  begin
70
- Process.getpgid(pid)
74
+ Process.kill(0, pid)
71
75
  rescue Errno::ESRCH
72
76
  FileUtils.rm_f pidfile
73
77
  done 'Sidekiq shut down gracefully.'
78
+ rescue Errno::EPERM
79
+ done 'Not permitted to shut down Sidekiq.'
74
80
  end
75
81
  sleep 1
76
82
  end
data/bin/sidekiqload ADDED
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Quiet some warnings we see when running in warning mode:
4
+ # RUBYOPT=-w bundle exec sidekiq
5
+ $TESTING = false
6
+
7
+ #require 'ruby-prof'
8
+ Bundler.require(:default)
9
+
10
+ require_relative '../lib/sidekiq/cli'
11
+ require_relative '../lib/sidekiq/launcher'
12
+
13
+ include Sidekiq::Util
14
+
15
+ # brew tap shopify/shopify
16
+ # brew install toxiproxy
17
+ # gem install toxiproxy
18
+ require 'toxiproxy'
19
+ # simulate a non-localhost network for realer-world conditions.
20
+ # adding 1ms of network latency has an ENORMOUS impact on benchmarks
21
+ Toxiproxy.populate([{
22
+ "name": "redis",
23
+ "listen": "127.0.0.1:6380",
24
+ "upstream": "127.0.0.1:6379"
25
+ }])
26
+
27
+
28
+ Sidekiq.configure_server do |config|
29
+ config.redis = { db: 13, port: 6380 }
30
+ #config.redis = { db: 13 }
31
+ config.options[:queues] << 'default'
32
+ config.logger.level = Logger::ERROR
33
+ config.average_scheduled_poll_interval = 2
34
+ config.reliable! if defined?(Sidekiq::Pro)
35
+ end
36
+
37
+ class LoadWorker
38
+ include Sidekiq::Worker
39
+ sidekiq_options retry: 1
40
+ sidekiq_retry_in do |x|
41
+ 1
42
+ end
43
+
44
+ def perform(idx)
45
+ #raise idx.to_s if idx % 100 == 1
46
+ end
47
+ end
48
+
49
+ # brew tap shopify/shopify
50
+ # brew install toxiproxy
51
+ # gem install toxiproxy
52
+ require 'toxiproxy'
53
+ # simulate a non-localhost network for realer-world conditions.
54
+ # adding 1ms of network latency has an ENORMOUS impact on benchmarks
55
+ Toxiproxy.populate([{
56
+ "name": "redis",
57
+ "listen": "127.0.0.1:6380",
58
+ "upstream": "127.0.0.1:6379"
59
+ }])
60
+
61
+ self_read, self_write = IO.pipe
62
+ %w(INT TERM USR1 USR2 TTIN).each do |sig|
63
+ begin
64
+ trap sig do
65
+ self_write.puts(sig)
66
+ end
67
+ rescue ArgumentError
68
+ puts "Signal #{sig} not supported"
69
+ end
70
+ end
71
+
72
+ Sidekiq.redis {|c| c.flushdb}
73
+ def handle_signal(launcher, sig)
74
+ Sidekiq.logger.debug "Got #{sig} signal"
75
+ case sig
76
+ when 'INT'
77
+ # Handle Ctrl-C in JRuby like MRI
78
+ # http://jira.codehaus.org/browse/JRUBY-4637
79
+ raise Interrupt
80
+ when 'TERM'
81
+ # Heroku sends TERM and then waits 10 seconds for process to exit.
82
+ raise Interrupt
83
+ when 'USR1'
84
+ Sidekiq.logger.info "Received USR1, no longer accepting new work"
85
+ launcher.quiet
86
+ when 'USR2'
87
+ if Sidekiq.options[:logfile]
88
+ Sidekiq.logger.info "Received USR2, reopening log file"
89
+ Sidekiq::Logging.reopen_logs
90
+ end
91
+ when 'TTIN'
92
+ Thread.list.each do |thread|
93
+ Sidekiq.logger.warn "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
94
+ if thread.backtrace
95
+ Sidekiq.logger.warn thread.backtrace.join("\n")
96
+ else
97
+ Sidekiq.logger.warn "<no backtrace available>"
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ def Process.rss
104
+ `ps -o rss= -p #{Process.pid}`.chomp.to_i
105
+ end
106
+
107
+ iter = 10
108
+ count = 10_000
109
+
110
+ iter.times do
111
+ arr = Array.new(count) do
112
+ []
113
+ end
114
+ count.times do |idx|
115
+ arr[idx][0] = idx
116
+ end
117
+ Sidekiq::Client.push_bulk('class' => LoadWorker, 'args' => arr)
118
+ end
119
+ Sidekiq.logger.error "Created #{count*iter} jobs"
120
+
121
+ Monitoring = Thread.new do
122
+ watchdog("monitor thread") do
123
+ while true
124
+ sleep 2
125
+ qsize, retries = Sidekiq.redis do |conn|
126
+ conn.pipelined do
127
+ conn.llen "queue:default"
128
+ conn.zcard "retry"
129
+ end
130
+ end.map(&:to_i)
131
+ total = qsize + retries
132
+ #GC.start
133
+ Sidekiq.logger.error("RSS: #{Process.rss} Pending: #{total}")
134
+ if total == 0
135
+ Sidekiq.logger.error("Done")
136
+ exit(0)
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ begin
143
+ #RubyProf::exclude_threads = [ Monitoring ]
144
+ #RubyProf.start
145
+ fire_event(:startup)
146
+ Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
147
+ Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
148
+ launcher = Sidekiq::Launcher.new(Sidekiq.options)
149
+ launcher.run
150
+
151
+ while readable_io = IO.select([self_read])
152
+ signal = readable_io.first[0].gets.strip
153
+ handle_signal(launcher, signal)
154
+ end
155
+ end
156
+ rescue SystemExit => e
157
+ #Sidekiq.logger.error("Profiling...")
158
+ #result = RubyProf.stop
159
+ #printer = RubyProf::GraphHtmlPrinter.new(result)
160
+ #printer.print(File.new("output.html", "w"), :min_percent => 1)
161
+ # normal
162
+ rescue => e
163
+ raise e if $DEBUG
164
+ STDERR.puts e.message
165
+ STDERR.puts e.backtrace.join("\n")
166
+ exit 1
167
+ end
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
  <% module_namespacing do -%>
3
- RSpec.describe <%= class_name %>Worker, :type => :worker do
3
+ RSpec.describe <%= class_name %>Worker, type: :worker do
4
4
  pending "add some examples to (or delete) #{__FILE__}"
5
5
  end
6
- <% end -%>
6
+ <% end -%>
@@ -1,8 +1,8 @@
1
1
  require_relative 'test_helper'
2
2
  <% module_namespacing do -%>
3
- class <%= class_name %>WorkerTest < MiniTest::Unit::TestCase
4
- def test_example
5
- skip "add some examples to (or delete) #{__FILE__}"
6
- end
3
+ class <%= class_name %>WorkerTest < <% if defined? Minitest::Test %>Minitest::Test<% else %>MiniTest::Unit::TestCase<% end %>
4
+ def test_example
5
+ skip "add some examples to (or delete) #{__FILE__}"
6
+ end
7
7
  end
8
- <% end -%>
8
+ <% end -%>
data/lib/sidekiq/api.rb CHANGED
@@ -159,14 +159,15 @@ module Sidekiq
159
159
 
160
160
  while i < @days_previous
161
161
  date = @start_date - i
162
- keys << "stat:#{stat}:#{date}"
163
- dates << date
162
+ datestr = date.strftime("%Y-%m-%d".freeze)
163
+ keys << "stat:#{stat}:#{datestr}"
164
+ dates << datestr
164
165
  i += 1
165
166
  end
166
167
 
167
168
  Sidekiq.redis do |conn|
168
169
  conn.mget(keys).each_with_index do |value, idx|
169
- stat_hash[dates[idx].to_s] = value ? value.to_i : 0
170
+ stat_hash[dates[idx]] = value ? value.to_i : 0
170
171
  end
171
172
  end
172
173
 
@@ -224,7 +225,7 @@ module Sidekiq
224
225
  page = 0
225
226
  page_size = 50
226
227
 
227
- loop do
228
+ while true do
228
229
  range_start = page * page_size - deleted_size
229
230
  range_end = page * page_size - deleted_size + (page_size - 1)
230
231
  entries = Sidekiq.redis do |conn|
@@ -262,7 +263,6 @@ module Sidekiq
262
263
  # removed from the queue via Job#delete.
263
264
  #
264
265
  class Job
265
- KNOWN_WRAPPERS = [/\ASidekiq::Extensions::Delayed/, "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"]
266
266
  attr_reader :item
267
267
 
268
268
  def initialize(item, queue_name=nil)
@@ -283,7 +283,13 @@ module Sidekiq
283
283
  "#{target}.#{method}"
284
284
  end
285
285
  when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
286
- @item['wrapped'] || args[0]
286
+ job_class = @item['wrapped'] || args[0]
287
+ if 'ActionMailer::DeliveryJob' == job_class
288
+ # MailerClass#mailer_method
289
+ args[0]['arguments'][0..1].join('#')
290
+ else
291
+ job_class
292
+ end
287
293
  else
288
294
  klass
289
295
  end
@@ -297,7 +303,13 @@ module Sidekiq
297
303
  arg
298
304
  end
299
305
  when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
300
- @item['wrapped'] ? args[0]["arguments"] : []
306
+ job_args = @item['wrapped'] ? args[0]["arguments"] : []
307
+ if 'ActionMailer::DeliveryJob' == (@item['wrapped'] || args[0])
308
+ # remove MailerClass, mailer_method and 'deliver_now'
309
+ job_args.drop(3)
310
+ else
311
+ job_args
312
+ end
301
313
  else
302
314
  args
303
315
  end
@@ -312,7 +324,7 @@ module Sidekiq
312
324
  end
313
325
 
314
326
  def enqueued_at
315
- Time.at(@item['enqueued_at'] || 0).utc
327
+ @item['enqueued_at'] ? Time.at(@item['enqueued_at']).utc : nil
316
328
  end
317
329
 
318
330
  def created_at
@@ -369,11 +381,15 @@ module Sidekiq
369
381
  end
370
382
 
371
383
  def delete
372
- @parent.delete(score, jid)
384
+ if @value
385
+ @parent.delete_by_value(@parent.name, @value)
386
+ else
387
+ @parent.delete_by_jid(score, jid)
388
+ end
373
389
  end
374
390
 
375
391
  def reschedule(at)
376
- @parent.delete(score, jid)
392
+ delete
377
393
  @parent.schedule(at, item)
378
394
  end
379
395
 
@@ -484,7 +500,7 @@ module Sidekiq
484
500
  page = -1
485
501
  page_size = 50
486
502
 
487
- loop do
503
+ while true do
488
504
  range_start = page * page_size + offset_size
489
505
  range_end = page * page_size + offset_size + (page_size - 1)
490
506
  elements = Sidekiq.redis do |conn|
@@ -519,36 +535,30 @@ module Sidekiq
519
535
  self.detect { |j| j.jid == jid }
520
536
  end
521
537
 
522
- def delete(score, jid = nil)
523
- if jid
524
- elements = Sidekiq.redis do |conn|
525
- conn.zrangebyscore(name, score, score)
526
- end
538
+ def delete_by_value(name, value)
539
+ Sidekiq.redis do |conn|
540
+ ret = conn.zrem(name, value)
541
+ @_size -= 1 if ret
542
+ ret
543
+ end
544
+ end
527
545
 
528
- elements_with_jid = elements.map do |element|
546
+ def delete_by_jid(score, jid)
547
+ Sidekiq.redis do |conn|
548
+ elements = conn.zrangebyscore(name, score, score)
549
+ elements.each do |element|
529
550
  message = Sidekiq.load_json(element)
530
-
531
551
  if message["jid"] == jid
532
- _, @_size = Sidekiq.redis do |conn|
533
- conn.multi do
534
- conn.zrem(name, element)
535
- conn.zcard name
536
- end
537
- end
538
- end
539
- end
540
- elements_with_jid.count != 0
541
- else
542
- count, @_size = Sidekiq.redis do |conn|
543
- conn.multi do
544
- conn.zremrangebyscore(name, score, score)
545
- conn.zcard name
552
+ ret = conn.zrem(name, element)
553
+ @_size -= 1 if ret
554
+ break ret
546
555
  end
556
+ false
547
557
  end
548
- count != 0
549
558
  end
550
559
  end
551
560
 
561
+ alias_method :delete, :delete_by_jid
552
562
  end
553
563
 
554
564
  ##
data/lib/sidekiq/cli.rb CHANGED
@@ -11,18 +11,18 @@ require 'sidekiq'
11
11
  require 'sidekiq/util'
12
12
 
13
13
  module Sidekiq
14
- # We are shutting down Sidekiq but what about workers that
15
- # are working on some long job? This error is
16
- # raised in workers that have not finished within the hard
17
- # timeout limit. This is needed to rollback db transactions,
18
- # otherwise Ruby's Thread#kill will commit. See #377.
19
- # DO NOT RESCUE THIS ERROR.
20
- class Shutdown < Interrupt; end
21
-
22
14
  class CLI
23
15
  include Util
24
16
  include Singleton unless $TESTING
25
17
 
18
+ PROCTITLES = [
19
+ proc { 'sidekiq'.freeze },
20
+ proc { Sidekiq::VERSION },
21
+ proc { |me, data| data['tag'] },
22
+ proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data['concurrency']} busy]" },
23
+ proc { |me, data| "stopping" if me.stopping? },
24
+ ]
25
+
26
26
  # Used for CLI testing
27
27
  attr_accessor :code
28
28
  attr_accessor :launcher
@@ -40,7 +40,6 @@ module Sidekiq
40
40
  validate!
41
41
  daemonize
42
42
  write_pid
43
- load_celluloid
44
43
  end
45
44
 
46
45
  # Code within this method is not tested because it alters
@@ -64,15 +63,23 @@ module Sidekiq
64
63
 
65
64
  logger.info "Running in #{RUBY_DESCRIPTION}"
66
65
  logger.info Sidekiq::LICENSE
67
- logger.info "Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org/pro" unless defined?(::Sidekiq::Pro)
68
-
69
- fire_event(:startup)
66
+ logger.info "Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org" unless defined?(::Sidekiq::Pro)
70
67
 
71
68
  Sidekiq.redis do |conn|
72
69
  # touch the connection pool so it is created before we
73
- # launch the actors.
70
+ # fire startup and start multithreading.
71
+ ver = conn.info['redis_version']
72
+ raise "You are using Redis v#{ver}, Sidekiq requires Redis v2.8.0 or greater" if ver < '2.8'
74
73
  end
75
74
 
75
+ # Before this point, the process is initializing with just the main thread.
76
+ # Starting here the process will now have multiple threads running.
77
+ fire_event(:startup)
78
+
79
+ logger.debug {
80
+ "Middleware: #{Sidekiq.server_middleware.map(&:klass).join(', ')}"
81
+ }
82
+
76
83
  if !options[:daemon]
77
84
  logger.info 'Starting processing, hit Ctrl-C to stop'
78
85
  end
@@ -90,26 +97,28 @@ module Sidekiq
90
97
  rescue Interrupt
91
98
  logger.info 'Shutting down'
92
99
  launcher.stop
93
- fire_event(:shutdown, true)
94
100
  # Explicitly exit so busy Processor threads can't block
95
101
  # process shutdown.
102
+ logger.info "Bye!"
96
103
  exit(0)
97
104
  end
98
105
  end
99
106
 
100
107
  def self.banner
101
- %q{ s
102
- ss
103
- sss sss ss
104
- s sss s ssss sss ____ _ _ _ _
105
- s sssss ssss / ___|(_) __| | ___| | _(_) __ _
106
- s sss \___ \| |/ _` |/ _ \ |/ / |/ _` |
107
- s sssss s ___) | | (_| | __/ <| | (_| |
108
- ss s s |____/|_|\__,_|\___|_|\_\_|\__, |
109
- s s s |_|
110
- s s
111
- sss
112
- sss }
108
+ %q{
109
+ m,
110
+ `$b
111
+ .ss, $$: .,d$
112
+ `$$P,d$P' .,md$P"'
113
+ ,$$$$$bmmd$$$P^'
114
+ .d$$$$$$$$$$P'
115
+ $$^' `"^$$$' ____ _ _ _ _
116
+ $: ,$$: / ___|(_) __| | ___| | _(_) __ _
117
+ `b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` |
118
+ $$: ___) | | (_| | __/ <| | (_| |
119
+ $$ |____/|_|\__,_|\___|_|\_\_|\__, |
120
+ .d$$ |_|
121
+ }
113
122
  end
114
123
 
115
124
  def handle_signal(sig)
@@ -124,8 +133,7 @@ module Sidekiq
124
133
  raise Interrupt
125
134
  when 'USR1'
126
135
  Sidekiq.logger.info "Received USR1, no longer accepting new work"
127
- launcher.manager.async.stop
128
- fire_event(:quiet, true)
136
+ launcher.quiet
129
137
  when 'USR2'
130
138
  if Sidekiq.options[:logfile]
131
139
  Sidekiq.logger.info "Received USR2, reopening log file"
@@ -154,19 +162,6 @@ module Sidekiq
154
162
  end
155
163
  end
156
164
 
157
- def load_celluloid
158
- raise "Celluloid cannot be required until here, or it will break Sidekiq's daemonization" if defined?(::Celluloid) && options[:daemon]
159
-
160
- # Celluloid can't be loaded until after we've daemonized
161
- # because it spins up threads and creates locks which get
162
- # into a very bad state if forked.
163
- require 'celluloid/autostart'
164
- Celluloid.logger = (options[:verbose] ? Sidekiq.logger : nil)
165
-
166
- require 'sidekiq/manager'
167
- require 'sidekiq/scheduled'
168
- end
169
-
170
165
  def daemonize
171
166
  return unless options[:daemon]
172
167
 
@@ -340,7 +335,11 @@ module Sidekiq
340
335
  die 1
341
336
  end
342
337
  @parser.parse!(argv)
343
- opts[:config_file] ||= 'config/sidekiq.yml' if File.exist?('config/sidekiq.yml')
338
+
339
+ %w[config/sidekiq.yml config/sidekiq.yml.erb].each do |filename|
340
+ opts[:config_file] ||= filename if File.exist?(filename)
341
+ end
342
+
344
343
  opts
345
344
  end
346
345
 
@@ -119,16 +119,12 @@ module Sidekiq
119
119
 
120
120
  class << self
121
121
 
122
- def default
123
- @default ||= new
124
- end
125
-
126
122
  def push(item)
127
- default.push(item)
123
+ new.push(item)
128
124
  end
129
125
 
130
126
  def push_bulk(items)
131
- default.push_bulk(items)
127
+ new.push_bulk(items)
132
128
  end
133
129
 
134
130
  # Resque compatibility helpers. Note all helpers
@@ -210,10 +206,9 @@ module Sidekiq
210
206
  end
211
207
 
212
208
  def normalize_item(item)
213
- raise(ArgumentError, "Message must be a Hash of the form: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash)
214
- raise(ArgumentError, "Message must include a class and set of arguments: #{item.inspect}") if !item['class'] || !item['args']
215
- raise(ArgumentError, "Message args must be an Array") unless item['args'].is_a?(Array)
216
- raise(ArgumentError, "Message class must be either a Class or String representation of the class name") unless item['class'].is_a?(Class) || item['class'].is_a?(String)
209
+ raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.has_key?('class'.freeze) && item.has_key?('args'.freeze)
210
+ raise(ArgumentError, "Job args must be an Array") unless item['args'].is_a?(Array)
211
+ raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'.freeze].is_a?(Class) || item['class'.freeze].is_a?(String)
217
212
 
218
213
  normalized_hash(item['class'.freeze])
219
214
  .each{ |key, value| item[key] = value if item[key].nil? }