sidekiq 3.3.0 → 3.3.1

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

Potentially problematic release.


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

Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -9
  3. data/Changes.md +16 -0
  4. data/Pro-2.0-Upgrade.md +102 -0
  5. data/Pro-Changes.md +14 -0
  6. data/README.md +3 -3
  7. data/Rakefile +0 -1
  8. data/bin/sidekiqctl +16 -14
  9. data/lib/sidekiq.rb +14 -14
  10. data/lib/sidekiq/api.rb +110 -40
  11. data/lib/sidekiq/cli.rb +11 -11
  12. data/lib/sidekiq/client.rb +13 -14
  13. data/lib/sidekiq/extensions/action_mailer.rb +5 -1
  14. data/lib/sidekiq/launcher.rb +4 -1
  15. data/lib/sidekiq/logging.rb +8 -7
  16. data/lib/sidekiq/manager.rb +9 -2
  17. data/lib/sidekiq/middleware/server/logging.rb +1 -1
  18. data/lib/sidekiq/middleware/server/retry_jobs.rb +4 -4
  19. data/lib/sidekiq/processor.rb +6 -6
  20. data/lib/sidekiq/testing.rb +8 -9
  21. data/lib/sidekiq/util.rb +8 -3
  22. data/lib/sidekiq/version.rb +1 -1
  23. data/lib/sidekiq/web.rb +33 -20
  24. data/lib/sidekiq/web_helpers.rb +13 -9
  25. data/lib/sidekiq/worker.rb +1 -2
  26. data/sidekiq.gemspec +1 -1
  27. data/test/test_api.rb +19 -10
  28. data/test/test_cli.rb +1 -1
  29. data/test/test_client.rb +1 -1
  30. data/test/test_exception_handler.rb +1 -1
  31. data/test/test_extensions.rb +1 -1
  32. data/test/test_fetch.rb +1 -1
  33. data/test/test_logging.rb +34 -0
  34. data/test/test_manager.rb +2 -2
  35. data/test/test_middleware.rb +1 -1
  36. data/test/test_processor.rb +1 -1
  37. data/test/test_redis_connection.rb +1 -1
  38. data/test/test_retry.rb +4 -2
  39. data/test/test_scheduled.rb +1 -1
  40. data/test/test_scheduling.rb +1 -1
  41. data/test/test_sidekiq.rb +13 -1
  42. data/test/test_testing.rb +1 -1
  43. data/test/test_testing_fake.rb +1 -1
  44. data/test/test_testing_inline.rb +1 -1
  45. data/test/test_web.rb +52 -9
  46. data/test/test_worker_generator.rb +1 -1
  47. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -0
  48. data/web/assets/javascripts/locales/{jquery.timeago.zh-CN.js → jquery.timeago.zh-cn.js} +0 -0
  49. data/web/assets/javascripts/locales/{jquery.timeago.zh-TW.js → jquery.timeago.zh-tw.js} +0 -0
  50. data/web/assets/stylesheets/application.css +1 -4
  51. data/web/locales/ru.yml +6 -0
  52. data/web/views/_footer.erb +1 -1
  53. data/web/views/_summary.erb +1 -1
  54. data/web/views/busy.erb +3 -4
  55. data/web/views/dashboard.erb +1 -1
  56. metadata +10 -7
@@ -107,17 +107,6 @@ module Sidekiq
107
107
  sss }
108
108
  end
109
109
 
110
- private
111
-
112
- def print_banner
113
- # Print logo and banner for development
114
- if environment == 'development' && $stdout.tty?
115
- puts "\e[#{31}m"
116
- puts Sidekiq::CLI.banner
117
- puts "\e[0m"
118
- end
119
- end
120
-
121
110
  def handle_signal(sig)
122
111
  Sidekiq.logger.debug "Got #{sig} signal"
123
112
  case sig
@@ -149,6 +138,17 @@ module Sidekiq
149
138
  end
150
139
  end
151
140
 
141
+ private
142
+
143
+ def print_banner
144
+ # Print logo and banner for development
145
+ if environment == 'development' && $stdout.tty?
146
+ puts "\e[#{31}m"
147
+ puts Sidekiq::CLI.banner
148
+ puts "\e[0m"
149
+ end
150
+ end
151
+
152
152
  def load_celluloid
153
153
  raise "Celluloid cannot be required until here, or it will break Sidekiq's daemonization" if defined?(::Celluloid) && options[:daemon]
154
154
 
@@ -55,7 +55,7 @@ module Sidekiq
55
55
  # All options must be strings, not symbols. NB: because we are serializing to JSON, all
56
56
  # symbols in 'args' will be converted to strings.
57
57
  #
58
- # Returns nil if not pushed to Redis or a unique Job ID if pushed.
58
+ # Returns a unique Job ID. If middleware stops the job, nil will be returned instead.
59
59
  #
60
60
  # Example:
61
61
  # push('queue' => 'my_queue', 'class' => MyWorker, 'args' => ['foo', 1, :bat => 'bar'])
@@ -64,9 +64,10 @@ module Sidekiq
64
64
  normed = normalize_item(item)
65
65
  payload = process_single(item['class'], normed)
66
66
 
67
- pushed = false
68
- pushed = raw_push([payload]) if payload
69
- pushed ? payload['jid'] : nil
67
+ if payload
68
+ raw_push([payload])
69
+ payload['jid']
70
+ end
70
71
  end
71
72
 
72
73
  ##
@@ -80,9 +81,8 @@ module Sidekiq
80
81
  # is run through the client middleware pipeline and each job gets its own Job ID
81
82
  # as normal.
82
83
  #
83
- # Returns an array of the of pushed jobs' jids or nil if the pushed failed. The number of jobs
84
- # pushed can be less than the number given if the middleware stopped processing for one
85
- # or more jobs.
84
+ # Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
85
+ # than the number given if the middleware stopped processing for one or more jobs.
86
86
  def push_bulk(items)
87
87
  normed = normalize_item(items)
88
88
  payloads = items['args'].map do |args|
@@ -90,9 +90,8 @@ module Sidekiq
90
90
  process_single(items['class'], normed.merge('args' => args, 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f))
91
91
  end.compact
92
92
 
93
- pushed = false
94
- pushed = raw_push(payloads) if !payloads.empty?
95
- pushed ? payloads.collect { |payload| payload['jid'] } : nil
93
+ raw_push(payloads) if !payloads.empty?
94
+ payloads.collect { |payload| payload['jid'] }
96
95
  end
97
96
 
98
97
  # Allows sharding of jobs across any number of Redis instances. All jobs
@@ -160,7 +159,7 @@ module Sidekiq
160
159
  ts = (int < 1_000_000_000 ? now + int : int)
161
160
 
162
161
  item = { 'class' => klass, 'args' => args, 'at' => ts, 'queue' => queue }
163
- item.delete('at') if ts <= now
162
+ item.delete('at'.freeze) if ts <= now
164
163
 
165
164
  klass.client_push(item)
166
165
  end
@@ -186,14 +185,14 @@ module Sidekiq
186
185
 
187
186
  def atomic_push(conn, payloads)
188
187
  if payloads.first['at']
189
- conn.zadd('schedule', payloads.map do |hash|
190
- at = hash.delete('at').to_s
188
+ conn.zadd('schedule'.freeze, payloads.map do |hash|
189
+ at = hash.delete('at'.freeze).to_s
191
190
  [at, Sidekiq.dump_json(hash)]
192
191
  end)
193
192
  else
194
193
  q = payloads.first['queue']
195
194
  to_push = payloads.map { |entry| Sidekiq.dump_json(entry) }
196
- conn.sadd('queues', q)
195
+ conn.sadd('queues'.freeze, q)
197
196
  conn.lpush("queue:#{q}", to_push)
198
197
  end
199
198
  end
@@ -17,7 +17,11 @@ module Sidekiq
17
17
  msg = target.public_send(method_name, *args)
18
18
  # The email method can return nil, which causes ActionMailer to return
19
19
  # an undeliverable empty message.
20
- deliver(msg) if msg && (msg.to || msg.cc || msg.bcc) && msg.from
20
+ if msg
21
+ deliver(msg)
22
+ else
23
+ raise "#{target.name}##{method_name} returned an undeliverable mail object"
24
+ end
21
25
  end
22
26
 
23
27
  private
@@ -27,7 +27,9 @@ module Sidekiq
27
27
  end
28
28
 
29
29
  def actor_died(actor, reason)
30
- return if @done
30
+ # https://github.com/mperham/sidekiq/issues/2057#issuecomment-66485477
31
+ return if @done || !reason
32
+
31
33
  Sidekiq.logger.warn("Sidekiq died due to the following error, cannot recover, process exiting")
32
34
  handle_exception(reason)
33
35
  exit(1)
@@ -73,6 +75,7 @@ module Sidekiq
73
75
  'concurrency' => @options[:concurrency],
74
76
  'queues' => @options[:queues].uniq,
75
77
  'labels' => Sidekiq.options[:labels],
78
+ 'identity' => identity,
76
79
  }
77
80
  # this data doesn't change so dump it to a string
78
81
  # now so we don't need to dump it every heartbeat.
@@ -5,6 +5,8 @@ module Sidekiq
5
5
  module Logging
6
6
 
7
7
  class Pretty < Logger::Formatter
8
+ SPACE = " "
9
+
8
10
  # Provide a call() method that returns the formatted message.
9
11
  def call(severity, time, program_name, message)
10
12
  "#{time.utc.iso8601(3)} #{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
@@ -12,17 +14,16 @@ module Sidekiq
12
14
 
13
15
  def context
14
16
  c = Thread.current[:sidekiq_context]
15
- c ? " #{c}" : ''
17
+ " #{c.join(SPACE)}" if c && c.any?
16
18
  end
17
19
  end
18
20
 
19
21
  def self.with_context(msg)
20
- begin
21
- Thread.current[:sidekiq_context] = msg
22
- yield
23
- ensure
24
- Thread.current[:sidekiq_context] = nil
25
- end
22
+ Thread.current[:sidekiq_context] ||= []
23
+ Thread.current[:sidekiq_context] << msg
24
+ yield
25
+ ensure
26
+ Thread.current[:sidekiq_context].pop
26
27
  end
27
28
 
28
29
  def self.initialize_logger(log_target = STDOUT)
@@ -21,6 +21,7 @@ module Sidekiq
21
21
  attr_accessor :fetcher
22
22
 
23
23
  SPIN_TIME_FOR_GRACEFUL_SHUTDOWN = 1
24
+ JVM_RESERVED_SIGNALS = ['USR1', 'USR2'] # Don't Process#kill if we get these signals via the API
24
25
 
25
26
  def initialize(condvar, options={})
26
27
  logger.debug { options.inspect }
@@ -47,7 +48,7 @@ module Sidekiq
47
48
 
48
49
  @done = true
49
50
 
50
- logger.info { "Shutting down #{@ready.size} quiet workers" }
51
+ logger.info { "Terminating #{@ready.size} quiet workers" }
51
52
  @ready.each { |x| x.terminate if x.alive? }
52
53
  @ready.clear
53
54
 
@@ -159,7 +160,13 @@ module Sidekiq
159
160
  end
160
161
  end
161
162
 
162
- ::Process.kill(msg, $$) if msg
163
+ return unless msg
164
+
165
+ if JVM_RESERVED_SIGNALS.include?(msg)
166
+ Sidekiq::CLI.instance.handle_signal(msg)
167
+ else
168
+ ::Process.kill(msg, $$)
169
+ end
163
170
  rescue => e
164
171
  # ignore all redis/network issues
165
172
  logger.error("heartbeat: #{e.message}")
@@ -4,7 +4,7 @@ module Sidekiq
4
4
  class Logging
5
5
 
6
6
  def call(worker, item, queue)
7
- Sidekiq::Logging.with_context("#{worker.class.to_s} JID-#{item['jid']}") do
7
+ Sidekiq::Logging.with_context("#{worker.class.to_s} JID-#{item['jid']}#{" BID-#{item['bid']}" if item['bid']}") do
8
8
  begin
9
9
  start = Time.now
10
10
  logger.info { "start" }
@@ -114,10 +114,10 @@ module Sidekiq
114
114
 
115
115
  if msg['backtrace'] == true
116
116
  msg['error_backtrace'] = exception.backtrace
117
- elsif msg['backtrace'] == false
117
+ elsif !msg['backtrace']
118
118
  # do nothing
119
119
  elsif msg['backtrace'].to_i != 0
120
- msg['error_backtrace'] = exception.backtrace[0..msg['backtrace'].to_i]
120
+ msg['error_backtrace'] = exception.backtrace[0...msg['backtrace'].to_i]
121
121
  end
122
122
 
123
123
  if count < max_retry_attempts
@@ -143,7 +143,7 @@ module Sidekiq
143
143
  worker.sidekiq_retries_exhausted_block.call(msg)
144
144
  end
145
145
  rescue => e
146
- handle_exception(e, { :context => "Error calling retries_exhausted for #{worker.class}", :job => msg })
146
+ handle_exception(e, { context: "Error calling retries_exhausted for #{worker.class}", job: msg })
147
147
  end
148
148
 
149
149
  send_to_morgue(msg) unless msg['dead'] == false
@@ -183,7 +183,7 @@ module Sidekiq
183
183
  begin
184
184
  worker.sidekiq_retry_in_block.call(count)
185
185
  rescue Exception => e
186
- handle_exception(e, { :context => "Failure scheduling retry using the defined `sidekiq_retry_in` in #{worker.class.name}, falling back to default" })
186
+ handle_exception(e, { context: "Failure scheduling retry using the defined `sidekiq_retry_in` in #{worker.class.name}, falling back to default" })
187
187
  nil
188
188
  end
189
189
  end
@@ -98,26 +98,26 @@ module Sidekiq
98
98
  yield
99
99
  rescue Exception
100
100
  retry_and_suppress_exceptions do
101
+ failed = "stat:failed:#{Time.now.utc.to_date}"
101
102
  Sidekiq.redis do |conn|
102
- failed = "stat:failed:#{Time.now.utc.to_date}"
103
- result = conn.multi do
103
+ conn.multi do
104
104
  conn.incrby("stat:failed", 1)
105
105
  conn.incrby(failed, 1)
106
+ conn.expire(failed, STATS_TIMEOUT)
106
107
  end
107
- conn.expire(failed, STATS_TIMEOUT) if result.last == 1
108
108
  end
109
109
  end
110
110
  raise
111
111
  ensure
112
112
  retry_and_suppress_exceptions do
113
+ processed = "stat:processed:#{Time.now.utc.to_date}"
113
114
  Sidekiq.redis do |conn|
114
- processed = "stat:processed:#{Time.now.utc.to_date}"
115
- result = conn.multi do
115
+ conn.multi do
116
116
  conn.hdel("#{identity}:workers", thread_identity)
117
117
  conn.incrby("stat:processed", 1)
118
118
  conn.incrby(processed, 1)
119
+ conn.expire(processed, STATS_TIMEOUT)
119
120
  end
120
- conn.expire(processed, STATS_TIMEOUT) if result.last == 1
121
121
  end
122
122
  end
123
123
  end
@@ -7,12 +7,12 @@ module Sidekiq
7
7
  class << self
8
8
  attr_accessor :__test_mode
9
9
 
10
- def __set_test_mode(mode, &block)
11
- if block
10
+ def __set_test_mode(mode)
11
+ if block_given?
12
12
  current_mode = self.__test_mode
13
13
  begin
14
14
  self.__test_mode = mode
15
- block.call
15
+ yield
16
16
  ensure
17
17
  self.__test_mode = current_mode
18
18
  end
@@ -66,12 +66,11 @@ module Sidekiq
66
66
  end
67
67
  true
68
68
  elsif Sidekiq::Testing.inline?
69
- payloads.each do |item|
70
- jid = item['jid'] || SecureRandom.hex(12)
71
- marshalled = Sidekiq.load_json(Sidekiq.dump_json(item))
72
- worker = marshalled['class'].constantize.new
73
- worker.jid = jid
74
- worker.perform(*marshalled['args'])
69
+ payloads.each do |job|
70
+ job['jid'] ||= SecureRandom.hex(12)
71
+ klass = job['class'].constantize
72
+ klass.jobs.unshift Sidekiq.load_json(Sidekiq.dump_json(job))
73
+ klass.perform_one
75
74
  end
76
75
  true
77
76
  else
@@ -1,4 +1,5 @@
1
1
  require 'socket'
2
+ require 'securerandom'
2
3
  require 'sidekiq/exception_handler'
3
4
  require 'sidekiq/core_ext'
4
5
 
@@ -14,7 +15,7 @@ module Sidekiq
14
15
  def watchdog(last_words)
15
16
  yield
16
17
  rescue Exception => ex
17
- handle_exception(ex, { :context => last_words })
18
+ handle_exception(ex, { context: last_words })
18
19
  raise ex
19
20
  end
20
21
 
@@ -30,8 +31,12 @@ module Sidekiq
30
31
  ENV['DYNO'] || Socket.gethostname
31
32
  end
32
33
 
34
+ def process_nonce
35
+ @@process_nonce ||= SecureRandom.hex(6)
36
+ end
37
+
33
38
  def identity
34
- @@identity ||= "#{hostname}:#{$$}"
39
+ @@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
35
40
  end
36
41
 
37
42
  def fire_event(event)
@@ -39,7 +44,7 @@ module Sidekiq
39
44
  begin
40
45
  block.call
41
46
  rescue => ex
42
- handle_exception(ex, { :event => event })
47
+ handle_exception(ex, { event: event })
43
48
  end
44
49
  end
45
50
  end
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "3.3.0"
2
+ VERSION = "3.3.1"
3
3
  end
@@ -45,12 +45,12 @@ module Sidekiq
45
45
  end
46
46
 
47
47
  post "/busy" do
48
- if params['hostname']
49
- p = Sidekiq::Process.new('hostname' => params["hostname"], 'pid' => params['pid'])
48
+ if params['identity']
49
+ p = Sidekiq::Process.new('identity' => params['identity'])
50
50
  p.quiet! if params[:quiet]
51
51
  p.stop! if params[:stop]
52
52
  else
53
- Sidekiq::ProcessSet.new.each do |pro|
53
+ processes.each do |pro|
54
54
  pro.quiet! if params[:quiet]
55
55
  pro.stop! if params[:stop]
56
56
  end
@@ -69,7 +69,7 @@ module Sidekiq
69
69
  @name = params[:name]
70
70
  @queue = Sidekiq::Queue.new(@name)
71
71
  (@current_page, @total_size, @messages) = page("queue:#{@name}", params[:page], @count)
72
- @messages = @messages.map {|msg| Sidekiq::Job.new(msg, @name) }
72
+ @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
73
73
  erb :queue
74
74
  end
75
75
 
@@ -85,8 +85,8 @@ module Sidekiq
85
85
 
86
86
  get '/morgue' do
87
87
  @count = (params[:count] || 25).to_i
88
- (@current_page, @total_size, @dead) = page("dead", params[:page], @count, :reverse => true)
89
- @dead = @dead.map {|msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
88
+ (@current_page, @total_size, @dead) = page("dead", params[:page], @count, reverse: true)
89
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
90
90
  erb :morgue
91
91
  end
92
92
 
@@ -128,7 +128,7 @@ module Sidekiq
128
128
  get '/retries' do
129
129
  @count = (params[:count] || 25).to_i
130
130
  (@current_page, @total_size, @retries) = page("retry", params[:page], @count)
131
- @retries = @retries.map {|msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
131
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
132
132
  erb :retries
133
133
  end
134
134
 
@@ -167,7 +167,7 @@ module Sidekiq
167
167
  get '/scheduled' do
168
168
  @count = (params[:count] || 25).to_i
169
169
  (@current_page, @total_size, @scheduled) = page("schedule", params[:page], @count)
170
- @scheduled = @scheduled.map {|msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
170
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
171
171
  erb :scheduled
172
172
  end
173
173
 
@@ -205,24 +205,37 @@ module Sidekiq
205
205
  REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
206
206
 
207
207
  get '/dashboard/stats' do
208
+ redirect "#{root_path}stats"
209
+ end
210
+
211
+ get '/stats' do
208
212
  sidekiq_stats = Sidekiq::Stats.new
209
- queue = Sidekiq::Queue.new
210
- redis_stats = redis_info.select{ |k, v| REDIS_KEYS.include? k }
213
+ redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
211
214
 
212
215
  content_type :json
213
- Sidekiq.dump_json({
216
+ Sidekiq.dump_json(
214
217
  sidekiq: {
215
- processed: sidekiq_stats.processed,
216
- failed: sidekiq_stats.failed,
217
- busy: workers_size,
218
- enqueued: sidekiq_stats.enqueued,
219
- scheduled: sidekiq_stats.scheduled_size,
220
- retries: sidekiq_stats.retry_size,
221
- dead: sidekiq_stats.dead_size,
222
- default_latency: queue.latency,
218
+ processed: sidekiq_stats.processed,
219
+ failed: sidekiq_stats.failed,
220
+ busy: sidekiq_stats.workers_size,
221
+ processes: sidekiq_stats.processes_size,
222
+ enqueued: sidekiq_stats.enqueued,
223
+ scheduled: sidekiq_stats.scheduled_size,
224
+ retries: sidekiq_stats.retry_size,
225
+ dead: sidekiq_stats.dead_size,
226
+ default_latency: sidekiq_stats.default_queue_latency
223
227
  },
224
228
  redis: redis_stats
225
- })
229
+ )
230
+ end
231
+
232
+ get '/stats/queues' do
233
+ queue_stats = Sidekiq::Stats::Queues.new
234
+
235
+ content_type :json
236
+ Sidekiq.dump_json(
237
+ queue_stats.lengths
238
+ )
226
239
  end
227
240
 
228
241
  private