sidekiq 3.0.2 → 3.1.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a7c0bb226bdf38ccb6a57bcf5fee433966e8f641
4
- data.tar.gz: c90d47b16b78595ae858c209a89f34d4eb698ce5
3
+ metadata.gz: 2c396adca6a73c134f306d045e66ee17a6f462f5
4
+ data.tar.gz: aa1be6ef05116a539c57390f4ca6f23dd035cd74
5
5
  SHA512:
6
- metadata.gz: 28d2b3599cd512c2de0a524df4b60a9381698ff1b4cdf4c14251868f3a08a8e85ef5297c6fb3bcbe6e80a6780b4767e1637798bbfb8249c4de29f22b9c2c7119
7
- data.tar.gz: 81f5f2949d44afe92709e4a078e56f727cb5315a2fb9bf86a0f0379b1d8a7f315f9a9508e7ed6c2d63958048c2bdf402daf3634f87086140cd8d2e5a506e0f9c
6
+ metadata.gz: e5b673786330ff3a01c5f5577d194fb34feba04a579853036ba41ef6e612440745cf340a7f279da7bb82e1712466ee864d6688dca7c23e79631e94d520acdcd4
7
+ data.tar.gz: 33c32fbea3ea8f04a85c1134f2181c4549d630631dd1894dfb7e04ae9d872fbbcfc0ce22d79051e9ea2e7a348060eefd7cf1c6be560f8d06bb2293c79b6474f8
@@ -6,7 +6,7 @@ rvm:
6
6
  - jruby-19mode
7
7
  - rbx
8
8
  - 2.0.0
9
- - 2.1.0
9
+ - 2.1
10
10
  matrix:
11
11
  allow_failures:
12
12
  - rvm: rbx
data/Changes.md CHANGED
@@ -1,3 +1,26 @@
1
+ 3.1.0
2
+ -----------
3
+
4
+ - New **remote control** feature: you can remotely trigger Sidekiq to quiet
5
+ or terminate via API, without signals. This is most useful on JRuby
6
+ or Heroku which does not support the USR1 'quiet' signal. Now you can
7
+ run a rake task like this at the start of your deploy to quiet your
8
+ set of Sidekiq processes. [#1703]
9
+ ```ruby
10
+ namespace :sidekiq do
11
+ task :quiet => :environment do
12
+ Sidekiq::ProcessSet.new.each(&:quiet!)
13
+ end
14
+ end
15
+ ```
16
+ - The Web UI can use the API to quiet or stop all processes via the Busy page.
17
+ - The Web UI understands and hides the `Sidekiq::Extensions::Delay*`
18
+ classes, instead showing `Class.method` as the Job. [#1718]
19
+ - The poll interval is now configurable in the Web UI [madebydna, #1713]
20
+ - Delay extensions can be removed so they don't conflict with
21
+ DelayedJob: put `Sidekiq.remove_delay!` in your initializer. [devaroop, #1674]
22
+
23
+
1
24
  3.0.2
2
25
  -----------
3
26
 
@@ -3,6 +3,14 @@ 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
+ 1.7.1
7
+ -----------
8
+
9
+ - Fix for paused queues being processed for a few seconds when starting
10
+ a new Sidekiq process.
11
+ - Add a 5 sec delay when starting reliable fetch on Heroku to minimize
12
+ any duplicate job processing with another process shutting down.
13
+
6
14
  1.7.0
7
15
  -----------
8
16
 
@@ -189,11 +189,12 @@ module Sidekiq
189
189
  # removed from the queue via Job#delete.
190
190
  #
191
191
  class Job
192
+ KNOWN_WRAPPERS = [/\ASidekiq::Extensions::Delayed/, "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"]
192
193
  attr_reader :item
193
194
 
194
195
  def initialize(item, queue_name=nil)
195
196
  @value = item
196
- @item = Sidekiq.load_json(item)
197
+ @item = item.is_a?(Hash) ? item : Sidekiq.load_json(item)
197
198
  @queue = queue_name || @item['queue']
198
199
  end
199
200
 
@@ -201,6 +202,32 @@ module Sidekiq
201
202
  @item['class']
202
203
  end
203
204
 
205
+ def display_class
206
+ # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
207
+ @klass ||= case klass
208
+ when /\ASidekiq::Extensions::Delayed/
209
+ (target, method, _) = YAML.load(args[0])
210
+ "#{target}.#{method}"
211
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
212
+ args[0]
213
+ else
214
+ klass
215
+ end
216
+ end
217
+
218
+ def display_args
219
+ # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
220
+ @args ||= case klass
221
+ when /\ASidekiq::Extensions::Delayed/
222
+ (_, _, arg) = YAML.load(args[0])
223
+ arg
224
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
225
+ args[1..-1]
226
+ else
227
+ args
228
+ end
229
+ end
230
+
204
231
  def args
205
232
  @item['args']
206
233
  end
@@ -449,18 +476,8 @@ module Sidekiq
449
476
  # right now. Each process send a heartbeat to Redis every 5 seconds
450
477
  # so this set should be relatively accurate, barring network partitions.
451
478
  #
452
- # Yields a hash of data which looks something like this:
479
+ # Yields a Sidekiq::Process.
453
480
  #
454
- # {
455
- # 'hostname' => 'app-1.example.com',
456
- # 'started_at' => <process start time>,
457
- # 'pid' => 12345,
458
- # 'tag' => 'myapp'
459
- # 'concurrency' => 25,
460
- # 'queues' => ['default', 'low'],
461
- # 'busy' => 10,
462
- # 'beat' => <last heartbeat>,
463
- # }
464
481
 
465
482
  class ProcessSet
466
483
  include Enumerable
@@ -486,7 +503,7 @@ module Sidekiq
486
503
  # in to Redis and probably died.
487
504
  (to_prune << sorted[i]; next) if info.nil?
488
505
  hash = Sidekiq.load_json(info)
489
- yield hash.merge('busy' => busy.to_i, 'beat' => at_s.to_f)
506
+ yield Process.new(hash.merge('busy' => busy.to_i, 'beat' => at_s.to_f))
490
507
  end
491
508
  end
492
509
 
@@ -503,6 +520,53 @@ module Sidekiq
503
520
  end
504
521
  end
505
522
 
523
+ #
524
+ # Sidekiq::Process has a set of attributes which look like this:
525
+ #
526
+ # {
527
+ # 'hostname' => 'app-1.example.com',
528
+ # 'started_at' => <process start time>,
529
+ # 'pid' => 12345,
530
+ # 'tag' => 'myapp'
531
+ # 'concurrency' => 25,
532
+ # 'queues' => ['default', 'low'],
533
+ # 'busy' => 10,
534
+ # 'beat' => <last heartbeat>,
535
+ # }
536
+ class Process
537
+ def initialize(hash)
538
+ @attribs = hash
539
+ end
540
+
541
+ def [](key)
542
+ @attribs[key]
543
+ end
544
+
545
+ def quiet!
546
+ signal('USR1')
547
+ end
548
+
549
+ def stop!
550
+ signal('TERM')
551
+ end
552
+
553
+ private
554
+
555
+ def signal(sig)
556
+ key = "#{identity}-signals"
557
+ Sidekiq.redis do |c|
558
+ c.multi do
559
+ c.lpush(key, sig)
560
+ c.expire(key, 60)
561
+ end
562
+ end
563
+ end
564
+
565
+ def identity
566
+ @id ||= "#{self['hostname']}:#{self['pid']}"
567
+ end
568
+ end
569
+
506
570
  ##
507
571
  # Programmatic access to the current active worker set.
508
572
  #
@@ -20,7 +20,7 @@ module Sidekiq
20
20
 
21
21
  class CLI
22
22
  include Util
23
- include Singleton
23
+ include Singleton unless $TESTING
24
24
 
25
25
  # Used for CLI testing
26
26
  attr_accessor :code
@@ -40,16 +40,14 @@ module Sidekiq
40
40
  daemonize
41
41
  write_pid
42
42
  load_celluloid
43
- boot_system
43
+ print_banner
44
44
  end
45
45
 
46
+ # Code within this method is not tested because it alters
47
+ # global process state irreversibly. PRs which improve the
48
+ # test coverage of Sidekiq::CLI are welcomed.
46
49
  def run
47
- # Print logo and banner for development
48
- if environment == 'development' && $stdout.tty?
49
- puts "\e[#{31}m"
50
- puts Sidekiq::BANNER
51
- puts "\e[0m"
52
- end
50
+ boot_system
53
51
 
54
52
  self_read, self_write = IO.pipe
55
53
 
@@ -95,13 +93,12 @@ module Sidekiq
95
93
 
96
94
  private
97
95
 
98
- def fire_event(event)
99
- Sidekiq.options[:lifecycle_events][event].each do |block|
100
- begin
101
- block.call
102
- rescue => ex
103
- handle_exception(ex, { :event => event })
104
- end
96
+ def print_banner
97
+ # Print logo and banner for development
98
+ if environment == 'development' && $stdout.tty?
99
+ puts "\e[#{31}m"
100
+ puts Sidekiq::BANNER
101
+ puts "\e[0m"
105
102
  end
106
103
  end
107
104
 
@@ -158,7 +155,7 @@ module Sidekiq
158
155
  files_to_reopen << file unless file.closed?
159
156
  end
160
157
 
161
- Process.daemon(true, true)
158
+ ::Process.daemon(true, true)
162
159
 
163
160
  files_to_reopen.each do |file|
164
161
  begin
@@ -323,7 +320,7 @@ module Sidekiq
323
320
  if path = options[:pidfile]
324
321
  pidfile = File.expand_path(path)
325
322
  File.open(pidfile, 'w') do |f|
326
- f.puts Process.pid
323
+ f.puts ::Process.pid
327
324
  end
328
325
  end
329
326
  end
@@ -7,7 +7,7 @@ module Sidekiq
7
7
  class Pretty < Logger::Formatter
8
8
  # Provide a call() method that returns the formatted message.
9
9
  def call(severity, time, program_name, message)
10
- "#{time.utc.iso8601} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
10
+ "#{time.utc.iso8601} #{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
11
11
  end
12
12
 
13
13
  def context
@@ -149,12 +149,15 @@ module Sidekiq
149
149
 
150
150
  def ❤(key)
151
151
  begin
152
- Sidekiq.redis do |conn|
152
+ _, _, msg = Sidekiq.redis do |conn|
153
153
  conn.multi do
154
154
  conn.hmset(key, 'busy', @busy.size, 'beat', Time.now.to_f)
155
155
  conn.expire(key, 60)
156
+ conn.rpop("#{key}-signals")
156
157
  end
157
158
  end
159
+
160
+ ::Process.kill(msg, $$) if msg
158
161
  rescue => e
159
162
  # ignore all redis/network issues
160
163
  logger.error("heartbeat: #{e.message}")
@@ -119,9 +119,6 @@ module Sidekiq
119
119
  end
120
120
  end
121
121
 
122
- # Singleton classes are not clonable.
123
- SINGLETON_CLASSES = [ NilClass, TrueClass, FalseClass, Symbol, Fixnum, Float, Bignum ].freeze
124
-
125
122
  # Deep clone the arguments passed to the worker so that if
126
123
  # the message fails, what is pushed back onto Redis hasn't
127
124
  # been mutated by the worker.
@@ -9,6 +9,21 @@ module Sidekiq
9
9
  end
10
10
  end
11
11
 
12
+ # Removes the generic aliases which MAY clash with names of already
13
+ # created methods by other applications. The methods `sidekiq_delay`,
14
+ # `sidekiq_delay_for` and `sidekiq_delay_until` can be used instead.
15
+ def self.remove_delay!
16
+ [Extensions::ActiveRecord,
17
+ Extensions::ActionMailer,
18
+ Extensions::Klass].each do |mod|
19
+ mod.module_eval do
20
+ remove_method :delay if respond_to?(:delay)
21
+ remove_method :delay_for if respond_to?(:delay_for)
22
+ remove_method :delay_until if respond_to?(:delay_until)
23
+ end
24
+ end
25
+ end
26
+
12
27
  class Rails < ::Rails::Engine
13
28
  initializer 'sidekiq' do
14
29
  Sidekiq.hook_rails!
@@ -33,5 +33,16 @@ module Sidekiq
33
33
  def identity
34
34
  @@identity ||= "#{hostname}:#{$$}"
35
35
  end
36
+
37
+ def fire_event(event)
38
+ Sidekiq.options[:lifecycle_events][event].each do |block|
39
+ begin
40
+ block.call
41
+ rescue => ex
42
+ handle_exception(ex, { :event => event })
43
+ end
44
+ end
45
+ end
46
+
36
47
  end
37
48
  end
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "3.0.2"
2
+ VERSION = "3.1.0"
3
3
  end
@@ -42,6 +42,20 @@ module Sidekiq
42
42
  erb :busy
43
43
  end
44
44
 
45
+ post "/busy" do
46
+ if params['hostname']
47
+ pro = Sidekiq::Process.new('hostname' => params["hostname"], 'pid' => params['pid'])
48
+ pro.quiet! if params[:quiet]
49
+ pro.stop! if params[:stop]
50
+ else
51
+ Sidekiq::ProcessSet.new.each do |pro|
52
+ pro.quiet! if params[:quiet]
53
+ pro.stop! if params[:stop]
54
+ end
55
+ end
56
+ redirect "#{root_path}busy"
57
+ end
58
+
45
59
  get "/queues" do
46
60
  @queues = Sidekiq::Queue.all
47
61
  erb :queues
@@ -53,7 +67,7 @@ module Sidekiq
53
67
  @name = params[:name]
54
68
  @queue = Sidekiq::Queue.new(@name)
55
69
  (@current_page, @total_size, @messages) = page("queue:#{@name}", params[:page], @count)
56
- @messages = @messages.map {|msg| Sidekiq.load_json(msg) }
70
+ @messages = @messages.map {|msg| Sidekiq::Job.new(msg, @name) }
57
71
  erb :queue
58
72
  end
59
73
 
@@ -86,12 +100,7 @@ module Sidekiq
86
100
 
87
101
  params['key'].each do |key|
88
102
  job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
89
- next unless job
90
- if params['retry']
91
- job.retry
92
- elsif params['delete']
93
- job.delete
94
- end
103
+ retry_or_delete job, params if job
95
104
  end
96
105
  redirect_with_query("#{root_path}morgue")
97
106
  end
@@ -109,13 +118,7 @@ module Sidekiq
109
118
  post "/morgue/:key" do
110
119
  halt 404 unless params['key']
111
120
  job = Sidekiq::DeadSet.new.fetch(*parse_params(params['key'])).first
112
- if job
113
- if params['retry']
114
- job.retry
115
- elsif params['delete']
116
- job.delete
117
- end
118
- end
121
+ retry_or_delete job, params if job
119
122
  redirect_with_query("#{root_path}morgue")
120
123
  end
121
124
 
@@ -139,12 +142,7 @@ module Sidekiq
139
142
 
140
143
  params['key'].each do |key|
141
144
  job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
142
- next unless job
143
- if params['retry']
144
- job.retry
145
- elsif params['delete']
146
- job.delete
147
- end
145
+ retry_or_delete job, params if job
148
146
  end
149
147
  redirect_with_query("#{root_path}retries")
150
148
  end
@@ -162,13 +160,7 @@ module Sidekiq
162
160
  post "/retries/:key" do
163
161
  halt 404 unless params['key']
164
162
  job = Sidekiq::RetrySet.new.fetch(*parse_params(params['key'])).first
165
- if job
166
- if params['retry']
167
- job.retry
168
- elsif params['delete']
169
- job.delete
170
- end
171
- end
163
+ retry_or_delete job, params if job
172
164
  redirect_with_query("#{root_path}retries")
173
165
  end
174
166
 
@@ -191,13 +183,7 @@ module Sidekiq
191
183
 
192
184
  params['key'].each do |key|
193
185
  job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
194
- if job
195
- if params['delete']
196
- job.delete
197
- elsif params['add_to_queue']
198
- job.add_to_queue
199
- end
200
- end
186
+ delete_or_add_queue job, params if job
201
187
  end
202
188
  redirect_with_query("#{root_path}scheduled")
203
189
  end
@@ -205,13 +191,7 @@ module Sidekiq
205
191
  post "/scheduled/:key" do
206
192
  halt 404 unless params['key']
207
193
  job = Sidekiq::ScheduledSet.new.fetch(*parse_params(params['key'])).first
208
- if job
209
- if params['add_to_queue']
210
- job.add_to_queue
211
- elsif params['delete']
212
- job.delete
213
- end
214
- end
194
+ delete_or_add_queue job, params if job
215
195
  redirect_with_query("#{root_path}scheduled")
216
196
  end
217
197
 
@@ -223,7 +203,7 @@ module Sidekiq
223
203
  erb :dashboard
224
204
  end
225
205
 
226
- REDIS_KEYS = %w(redis_stats uptime_in_days connected_clients used_memory_human used_memory_peak_human)
206
+ REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
227
207
 
228
208
  get '/dashboard/stats' do
229
209
  sidekiq_stats = Sidekiq::Stats.new
@@ -245,5 +225,22 @@ module Sidekiq
245
225
  })
246
226
  end
247
227
 
228
+ private
229
+
230
+ def retry_or_delete job, params
231
+ if params['retry']
232
+ job.retry
233
+ elsif params['delete']
234
+ job.delete
235
+ end
236
+ end
237
+
238
+ def delete_or_add_queue job, params
239
+ if params['delete']
240
+ job.delete
241
+ elsif params['add_to_queue']
242
+ job.add_to_queue
243
+ end
244
+ end
248
245
  end
249
246
  end