sidekiq 3.3.4 → 3.4.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: a6ca011daf81025502d9f9abfdf4e38fafd05d83
4
- data.tar.gz: 3687865623e0ad65b98fcd7e5e576f540d80df23
3
+ metadata.gz: a49959a367b719b42c86d617948251db3a840b9b
4
+ data.tar.gz: c82ac2415a18b96ff62e0b66a399c0ba27602f08
5
5
  SHA512:
6
- metadata.gz: 87bf6bf803ce2c24d2c424348fe83681c77dafda7206300c13ebda6c9aca5d36d1551642c68a0d2f37558540a64e8bd5aa5cfb6d95baaa7ce1fd08d62000ffdb
7
- data.tar.gz: ddf4ccef0e503b5e59b874d8ec14ecb6d94721cf674f19385cb0a6e3d180c3605da63b03bb4752fd11c4da60be2d41f63f08c2f2a413e623d15bd929bc9210ff
6
+ metadata.gz: a09bdb137a14e2a24683edd6eac08bf60c1bcfa071bfc0dd1827bdbf8b06538083775b7cef09a01088383603ea8d994b277a9fd34fdbf2585e8fb8f104147a7c
7
+ data.tar.gz: 4e6417eadd49df60666dd944a55d8e515feaa3475209a9f69e77145e28d361d1664368b7cb5805f84dd6a5c2febd835b148bf28813f42a84c2a122f5e28439ca
@@ -60,7 +60,7 @@ if Sidekiq::VERSION < '3'
60
60
  end
61
61
  else
62
62
  Sidekiq.configure_server do |config|
63
- config.error_handlers << Proc.new {|ex,context| MyErrorService.notify(ex, context) }
63
+ config.error_handlers << proc {|ex,context| MyErrorService.notify(ex, context) }
64
64
  end
65
65
  end
66
66
  ```
data/Changes.md CHANGED
@@ -1,3 +1,20 @@
1
+ 3.4.0
2
+ -----------
3
+
4
+ - Set a `created_at` attribute when jobs are created, set `enqueued_at` only
5
+ when they go into a queue. Fixes invalid latency calculations with scheduled jobs.
6
+ [#2373, mrsimo]
7
+ - Don't log timestamp on Heroku [#2343]
8
+ - Run `shutdown` event handlers in reverse order of definition [#2374]
9
+ - Rename and rework `poll_interval` to be simpler, more predictable [#2317, cainlevy]
10
+ The new setting is `average_scheduled_poll_interval`. To configure
11
+ Sidekiq to look for scheduled jobs every 5 seconds, just set it to 5.
12
+ ```ruby
13
+ Sidekiq.configure_server do |config|
14
+ config.average_scheduled_poll_interval = 5
15
+ end
16
+ ```
17
+
1
18
  3.3.4
2
19
  -----------
3
20
 
@@ -205,7 +222,7 @@ end
205
222
  occur anywhere within Sidekiq, not just within middleware.
206
223
  ```ruby
207
224
  Sidekiq.configure_server do |config|
208
- config.error_handlers << Proc.new {|ex,ctx| ... }
225
+ config.error_handlers << proc {|ex,ctx| ... }
209
226
  end
210
227
  ```
211
228
  - **Process Heartbeat** - each Sidekiq process will ping Redis every 5
data/Gemfile CHANGED
@@ -12,7 +12,7 @@ platforms :ruby do
12
12
  gem 'sqlite3'
13
13
  end
14
14
 
15
- platforms :mri_21 do
15
+ platforms :mri do
16
16
  gem 'pry-byebug'
17
17
  end
18
18
 
@@ -3,6 +3,18 @@ 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
+ - Reliable push now only catches Redis exceptions [#2307]
10
+
11
+ 2.0.3
12
+ -----------
13
+
14
+ - Display Batch callback data on the Batch details page. [#2347]
15
+ - Fix incompatibility with Pro Web and Rack middleware. [#2344] Thank
16
+ you to Jason Clark for the tip on how to fix it.
17
+
6
18
  2.0.2
7
19
  -----------
8
20
 
@@ -12,9 +24,12 @@ Please see [http://sidekiq.org/pro](http://sidekiq.org/pro) for more details and
12
24
  POOL1 = ConnectionPool.new { Redis.new(:url => "redis://localhost:6379/0") }
13
25
  POOL2 = ConnectionPool.new { Redis.new(:url => "redis://localhost:6378/0") }
14
26
 
15
- mount Sidekiq::Pro::Web.with(redis_pool: POOL1) => '/sidekiq1'
16
- mount Sidekiq::Pro::Web.with(redis_pool: POOL2) => '/sidekiq2'
27
+ mount Sidekiq::Pro::Web => '/sidekiq' # default
28
+ mount Sidekiq::Pro::Web.with(redis_pool: POOL1), at: '/sidekiq1', as: 'sidekiq1' # shard1
29
+ mount Sidekiq::Pro::Web.with(redis_pool: POOL2), at: '/sidekiq2', as: 'sidekiq2' # shard2
17
30
  ```
31
+ - **SECURITY** Fix batch XSS in error data. Thanks to moneybird.com for
32
+ reporting the issue.
18
33
 
19
34
  2.0.1
20
35
  -----------
@@ -20,6 +20,8 @@ module Sidekiq
20
20
  require: '.',
21
21
  environment: nil,
22
22
  timeout: 8,
23
+ poll_interval_average: nil,
24
+ average_scheduled_poll_interval: 15,
23
25
  error_handlers: [],
24
26
  lifecycle_events: {
25
27
  startup: [],
@@ -36,7 +38,7 @@ module Sidekiq
36
38
  }
37
39
 
38
40
  def self.❨╯°□°❩╯︵┻━┻
39
- puts "Calm down, bro"
41
+ puts "Calm down, yo."
40
42
  end
41
43
 
42
44
  def self.options
@@ -127,15 +129,29 @@ module Sidekiq
127
129
  Sidekiq::Logging.logger = log
128
130
  end
129
131
 
132
+ # When set, overrides Sidekiq.options[:average_scheduled_poll_interval] and sets
133
+ # the average interval that this process will delay before checking for
134
+ # scheduled jobs or job retries that are ready to run.
135
+ #
130
136
  # See sidekiq/scheduled.rb for an in-depth explanation of this value
131
137
  def self.poll_interval=(interval)
132
- self.options[:poll_interval] = interval
138
+ $stderr.puts "DEPRECATION: `config.poll_interval = #{interval}` will be removed in Sidekiq 4. Please update to `config.average_scheduled_poll_interval = #{interval}`."
139
+ self.options[:poll_interval_average] = interval
140
+ end
141
+
142
+ # How frequently Redis should be checked by a random Sidekiq process for
143
+ # scheduled and retriable jobs. Each individual process will take turns by
144
+ # waiting some multiple of this value.
145
+ #
146
+ # See sidekiq/scheduled.rb for an in-depth explanation of this value
147
+ def self.average_scheduled_poll_interval=(interval)
148
+ self.options[:average_scheduled_poll_interval] = interval
133
149
  end
134
150
 
135
151
  # Register a proc to handle any error which occurs within the Sidekiq process.
136
152
  #
137
153
  # Sidekiq.configure_server do |config|
138
- # config.error_handlers << Proc.new {|ex,ctx_hash| MyErrorService.notify(ex, ctx_hash) }
154
+ # config.error_handlers << proc {|ex,ctx_hash| MyErrorService.notify(ex, ctx_hash) }
139
155
  # end
140
156
  #
141
157
  # The default error handler logs errors to Sidekiq.logger.
@@ -315,12 +315,16 @@ module Sidekiq
315
315
  Time.at(@item['enqueued_at'] || 0).utc
316
316
  end
317
317
 
318
+ def created_at
319
+ Time.at(@item['created_at'] || @item['enqueued_at'] || 0).utc
320
+ end
321
+
318
322
  def queue
319
323
  @queue
320
324
  end
321
325
 
322
326
  def latency
323
- Time.now.to_f - @item['enqueued_at']
327
+ Time.now.to_f - (@item['enqueued_at'] || @item['created_at'])
324
328
  end
325
329
 
326
330
  ##
@@ -90,7 +90,7 @@ module Sidekiq
90
90
  rescue Interrupt
91
91
  logger.info 'Shutting down'
92
92
  launcher.stop
93
- fire_event(:shutdown)
93
+ fire_event(:shutdown, true)
94
94
  # Explicitly exit so busy Processor threads can't block
95
95
  # process shutdown.
96
96
  exit(0)
@@ -125,7 +125,7 @@ module Sidekiq
125
125
  when 'USR1'
126
126
  Sidekiq.logger.info "Received USR1, no longer accepting new work"
127
127
  launcher.manager.async.stop
128
- fire_event(:quiet)
128
+ fire_event(:quiet, true)
129
129
  when 'USR2'
130
130
  if Sidekiq.options[:logfile]
131
131
  Sidekiq.logger.info "Received USR2, reopening log file"
@@ -269,6 +269,10 @@ module Sidekiq
269
269
  logger.info @parser
270
270
  die(1)
271
271
  end
272
+
273
+ [:concurrency, :timeout].each do |opt|
274
+ raise ArgumentError, "#{opt}: #{options[opt]} is not a valid value" if options.has_key?(opt) && options[opt].to_i <= 0
275
+ end
272
276
  end
273
277
 
274
278
  def parse_options(argv)
@@ -191,7 +191,11 @@ module Sidekiq
191
191
  end)
192
192
  else
193
193
  q = payloads.first['queue']
194
- to_push = payloads.map { |entry| Sidekiq.dump_json(entry) }
194
+ now = Time.now.to_f
195
+ to_push = payloads.map do |entry|
196
+ entry['enqueued_at'.freeze] = now
197
+ Sidekiq.dump_json(entry)
198
+ end
195
199
  conn.sadd('queues'.freeze, q)
196
200
  conn.lpush("queue:#{q}", to_push)
197
201
  end
@@ -217,7 +221,7 @@ module Sidekiq
217
221
  item['class'.freeze] = item['class'.freeze].to_s
218
222
  item['queue'.freeze] = item['queue'.freeze].to_s
219
223
  item['jid'.freeze] ||= SecureRandom.hex(12)
220
- item['enqueued_at'.freeze] ||= Time.now.to_f
224
+ item['created_at'.freeze] ||= Time.now.to_f
221
225
  item
222
226
  end
223
227
 
@@ -6,7 +6,7 @@ module Sidekiq
6
6
  class Logger
7
7
  def call(ex, ctxHash)
8
8
  Sidekiq.logger.warn(ctxHash) if !ctxHash.empty?
9
- Sidekiq.logger.warn ex
9
+ Sidekiq.logger.warn "#{ex.class.name}: #{ex.message}"
10
10
  Sidekiq.logger.warn ex.backtrace.join("\n") unless ex.backtrace.nil?
11
11
  end
12
12
 
@@ -18,6 +18,12 @@ module Sidekiq
18
18
  end
19
19
  end
20
20
 
21
+ class WithoutTimestamp < Pretty
22
+ def call(severity, time, program_name, message)
23
+ "#{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
24
+ end
25
+ end
26
+
21
27
  def self.with_context(msg)
22
28
  Thread.current[:sidekiq_context] ||= []
23
29
  Thread.current[:sidekiq_context] << msg
@@ -30,7 +36,7 @@ module Sidekiq
30
36
  oldlogger = defined?(@logger) ? @logger : nil
31
37
  @logger = Logger.new(log_target)
32
38
  @logger.level = Logger::INFO
33
- @logger.formatter = Pretty.new
39
+ @logger.formatter = ENV['DYNO'] ? WithoutTimestamp.new : Pretty.new
34
40
  oldlogger.close if oldlogger && !$TESTING # don't want to close testing's STDOUT logging
35
41
  @logger
36
42
  end
@@ -134,12 +134,18 @@ module Sidekiq
134
134
  @threads[proxy_id] = thr
135
135
  end
136
136
 
137
+ PROCTITLES = [
138
+ proc { 'sidekiq'.freeze },
139
+ proc { Sidekiq::VERSION },
140
+ proc { |mgr, data| data['tag'] },
141
+ proc { |mgr, data| "[#{mgr.busy.size} of #{data['concurrency']} busy]" },
142
+ proc { |mgr, data| "stopping" if mgr.stopped? },
143
+ ]
144
+
137
145
  def heartbeat(key, data, json)
138
- proctitle = ['sidekiq', Sidekiq::VERSION]
139
- proctitle << data['tag'] unless data['tag'].empty?
140
- proctitle << "[#{@busy.size} of #{data['concurrency']} busy]"
141
- proctitle << 'stopping' if stopped?
142
- $0 = proctitle.join(' ')
146
+ results = PROCTITLES.map {|x| x.(self, data) }
147
+ results.compact!
148
+ $0 = results.join(' ')
143
149
 
144
150
  ❤(key, json)
145
151
  after(5) do
@@ -147,6 +153,10 @@ module Sidekiq
147
153
  end
148
154
  end
149
155
 
156
+ def stopped?
157
+ @done
158
+ end
159
+
150
160
  private
151
161
 
152
162
  def ❤(key, json)
@@ -206,10 +216,6 @@ module Sidekiq
206
216
  @fetcher.async.fetch
207
217
  end
208
218
 
209
- def stopped?
210
- @done
211
- end
212
-
213
219
  def shutdown
214
220
  requeue
215
221
  @finished.signal
@@ -132,7 +132,7 @@ module Sidekiq
132
132
 
133
133
  # If an exception occurs in the block passed to this method, that block will be retried up to max_retries times.
134
134
  # All exceptions will be swallowed and logged.
135
- def retry_and_suppress_exceptions(max_retries = 2)
135
+ def retry_and_suppress_exceptions(max_retries = 5)
136
136
  retry_count = 0
137
137
  begin
138
138
  yield
@@ -140,12 +140,16 @@ module Sidekiq
140
140
  retry_count += 1
141
141
  if retry_count <= max_retries
142
142
  Sidekiq.logger.debug {"Suppressing and retrying error: #{e.inspect}"}
143
- sleep(1)
143
+ pause_for_recovery(retry_count)
144
144
  retry
145
145
  else
146
146
  handle_exception(e, { :message => "Exhausted #{max_retries} retries"})
147
147
  end
148
148
  end
149
149
  end
150
+
151
+ def pause_for_recovery(retry_count)
152
+ sleep(retry_count)
153
+ end
150
154
  end
151
155
  end
@@ -60,35 +60,41 @@ module Sidekiq
60
60
  logger.error ex.backtrace.first
61
61
  end
62
62
 
63
- # Randomizing scales the interval by half since
64
- # on average calling `rand` returns 0.5.
65
- # We make up for this by doubling the interval
66
- after(poll_interval * 2 * rand) { poll }
63
+ after(random_poll_interval) { poll }
67
64
  end
68
65
  end
69
66
 
70
67
  private
71
68
 
72
- # We do our best to tune poll_interval to the size of the active Sidekiq
69
+ # Calculates a random interval that is ±50% the desired average.
70
+ def random_poll_interval
71
+ poll_interval_average * rand + poll_interval_average.to_f / 2
72
+ end
73
+
74
+ # We do our best to tune the poll interval to the size of the active Sidekiq
73
75
  # cluster. If you have 30 processes and poll every 15 seconds, that means one
74
76
  # Sidekiq is checking Redis every 0.5 seconds - way too often for most people
75
77
  # and really bad if the retry or scheduled sets are large.
76
78
  #
77
79
  # Instead try to avoid polling more than once every 15 seconds. If you have
78
- # 30 Sidekiq processes, we'll set poll_interval to 30 * 15 * 2 or 900 seconds.
80
+ # 30 Sidekiq processes, we'll poll every 30 * 15 or 450 seconds.
79
81
  # To keep things statistically random, we'll sleep a random amount between
80
- # 0 and 900 seconds for each poll or 450 seconds on average. Otherwise restarting
82
+ # 225 and 675 seconds for each poll or 450 seconds on average. Otherwise restarting
81
83
  # all your Sidekiq processes at the same time will lead to them all polling at
82
84
  # the same time: the thundering herd problem.
83
85
  #
84
86
  # We only do this if poll_interval is unset (the default).
85
- def poll_interval
86
- Sidekiq.options[:poll_interval] ||= begin
87
- ps = Sidekiq::ProcessSet.new
88
- pcount = ps.size
89
- pcount = 1 if pcount == 0
90
- pcount * 15
91
- end
87
+ def poll_interval_average
88
+ Sidekiq.options[:poll_interval_average] ||= scaled_poll_interval
89
+ end
90
+
91
+ # Calculates an average poll interval based on the number of known Sidekiq processes.
92
+ # This minimizes a single point of failure by dispersing check-ins but without taxing
93
+ # Redis if you run many Sidekiq processes.
94
+ def scaled_poll_interval
95
+ pcount = Sidekiq::ProcessSet.new.size
96
+ pcount = 1 if pcount == 0
97
+ pcount * Sidekiq.options[:average_scheduled_poll_interval]
92
98
  end
93
99
 
94
100
  def initial_wait
@@ -96,7 +102,7 @@ module Sidekiq
96
102
  # Have all processes sleep between 5-15 seconds. 10 seconds
97
103
  # to give time for the heartbeat to register (if the poll interval is going to be calculated by the number
98
104
  # of workers), and 5 random seconds to ensure they don't all hit Redis at the same time.
99
- sleep(INITIAL_WAIT) unless Sidekiq.options[:poll_interval]
105
+ sleep(INITIAL_WAIT) unless Sidekiq.options[:poll_interval_average]
100
106
  sleep(5 * rand)
101
107
  rescue Celluloid::Task::TerminatedError
102
108
  # Hit Ctrl-C when Sidekiq is finished booting and we have a chance
@@ -152,6 +152,7 @@ module Sidekiq
152
152
  while job = jobs.shift do
153
153
  worker = new
154
154
  worker.jid = job['jid']
155
+ worker.bid = job['bid'] if worker.respond_to?(:bid=)
155
156
  execute_job(worker, job['args'])
156
157
  end
157
158
  end
@@ -162,6 +163,7 @@ module Sidekiq
162
163
  job = jobs.shift
163
164
  worker = new
164
165
  worker.jid = job['jid']
166
+ worker.bid = job['bid'] if worker.respond_to?(:bid=)
165
167
  execute_job(worker, job['args'])
166
168
  end
167
169
 
@@ -39,8 +39,10 @@ module Sidekiq
39
39
  @@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
40
40
  end
41
41
 
42
- def fire_event(event)
43
- Sidekiq.options[:lifecycle_events][event].each do |block|
42
+ def fire_event(event, reverse=false)
43
+ arr = Sidekiq.options[:lifecycle_events][event]
44
+ arr.reverse! if reverse
45
+ arr.each do |block|
44
46
  begin
45
47
  block.call
46
48
  rescue => ex
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "3.3.4"
2
+ VERSION = "3.4.0"
3
3
  end
@@ -12,8 +12,8 @@ module Sidekiq
12
12
  include Sidekiq::Paginator
13
13
 
14
14
  set :root, File.expand_path(File.dirname(__FILE__) + "/../../web")
15
- set :public_folder, Proc.new { "#{root}/assets" }
16
- set :views, Proc.new { "#{root}/views" }
15
+ set :public_folder, proc { "#{root}/assets" }
16
+ set :views, proc { "#{root}/views" }
17
17
  set :locales, ["#{root}/locales"]
18
18
 
19
19
  helpers WebHelpers
@@ -65,6 +65,7 @@ module Sidekiq
65
65
  locale = 'en'.freeze
66
66
  languages = request.env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze
67
67
  languages.downcase.split(','.freeze).each do |lang|
68
+ next if lang == '*'.freeze
68
69
  lang = lang.split(';'.freeze)[0]
69
70
  break locale = lang if find_locale_files(lang).any?
70
71
  end
@@ -110,11 +110,13 @@ class TestManager < Sidekiq::Test
110
110
 
111
111
  describe 'when manager is active' do
112
112
  before do
113
+ Sidekiq::Manager::PROCTITLES << proc { "xyz" }
113
114
  @mgr.heartbeat('identity', heartbeat_data, Sidekiq.dump_json(heartbeat_data))
115
+ Sidekiq::Manager::PROCTITLES.pop
114
116
  end
115
117
 
116
118
  it 'sets useful info to proctitle' do
117
- assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy]", $0
119
+ assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] xyz", $0
118
120
  end
119
121
 
120
122
  it 'stores process info in redis' do