sidekiq 3.1.3 → 3.1.4

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: 4adde5768b38bd2da507698bc57358f36ce64041
4
- data.tar.gz: 82b200a66bfcc8fcc46532b5b0d055ea87f91370
3
+ metadata.gz: 842995b5a36bd3651ba6ed63d3f0c2c5f99a2c1b
4
+ data.tar.gz: 4f362b15d3748d3df41dad8ba1364d967a5e37cd
5
5
  SHA512:
6
- metadata.gz: 111778ad7a808b0a9b093969846037c03d88440d5dd9e6bcec4cb5196c8831b24a4e6d2bf425e11fbed02b4b61053c15f039d0392d4c9ee67a8278a18edf40c0
7
- data.tar.gz: 055b58470ee8899a33e0467d6a4d11c202eed48db7c41d740aa469646ad5b075917f2f7bf11e320062d4d3c718065f15a66fbd44d67779e3d925b676259fdaff
6
+ metadata.gz: dc001192b00a22ad94893693543234b2542a6f91af74abc0a9963bf19bfd96c33babe084efcad2ce454f26bd78bdbbb356364fc86ba7d63bc5e0fb441a514a09
7
+ data.tar.gz: 7ea2ab5908163f70dc386b9ab89ce84321f3e93bf5bc4d20f0152ecc3a1ae67dba100abf088bcea4c9c163cf904623208877c16b993127d3e927038a0dafbfde
@@ -4,9 +4,9 @@ services:
4
4
  rvm:
5
5
  - 1.9.3
6
6
  - jruby-19mode
7
- - rbx
7
+ - rbx-2
8
8
  - 2.0.0
9
9
  - 2.1
10
10
  matrix:
11
11
  allow_failures:
12
- - rvm: rbx
12
+ - rvm: rbx-2
@@ -29,7 +29,10 @@ changes a few data elements in Redis. To upgrade cleanly:
29
29
  * Redis-to-Go is no longer transparently activated on Heroku so as to not play
30
30
  favorites with any particular Redis service. You need to set a config option
31
31
  for your app:
32
- `heroku config:set REDIS_PROVIDER=REDISTOGO_URL`
32
+ `heroku config:set REDIS_PROVIDER=REDISTOGO_URL`. You may also use
33
+ the generic `REDIS_URL`. See
34
+ [Advanced Options: Setting the Location of your Redis server][1]
35
+ for details.
33
36
  * Anyone using Airbrake, Honeybadger, Exceptional or ExceptionNotifier
34
37
  will need to update their error gem version to the latest to pull in
35
38
  Sidekiq support. Sidekiq will not provide explicit support for these
@@ -63,3 +66,5 @@ end
63
66
  ```
64
67
 
65
68
  Your error handler must respond to `call(exception, context_hash)`.
69
+
70
+ [1]: https://github.com/mperham/sidekiq/wiki/Advanced-Options#via-env-variable
data/Changes.md CHANGED
@@ -1,3 +1,12 @@
1
+ 3.1.4
2
+ -----------
3
+
4
+ - Happy π release!
5
+ - Self-tuning Scheduler polling, we use heartbeat info to better tune poll\_interval [#1630]
6
+ - Remove all table column width rules, hopefully get better column formatting [#1747]
7
+ - Handle edge case where YAML can't be decoded in dev mode [#1761]
8
+ - Fix lingering jobs in Busy page on Heroku [#1764]
9
+
1
10
  3.1.3
2
11
  -----------
3
12
 
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require 'bundler/gem_tasks'
1
2
  require 'rake/testtask'
2
3
  Rake::TestTask.new(:test) do |test|
3
4
  test.libs << 'test'
@@ -118,9 +118,7 @@ module Sidekiq
118
118
  Sidekiq::Logging.logger = log
119
119
  end
120
120
 
121
- # The default poll interval is 15 seconds. This should generally be set to
122
- # (Sidekiq process count * 5). So if you have a dozen Sidekiq processes, set
123
- # poll_interval to 60.
121
+ # See sidekiq/scheduled.rb for an in-depth explanation of this value
124
122
  def self.poll_interval=(interval)
125
123
  self.options[:poll_interval] = interval
126
124
  end
@@ -149,20 +147,6 @@ module Sidekiq
149
147
  raise ArgumentError, "Invalid event name: #{event}" if !options[:lifecycle_events].keys.include?(event)
150
148
  options[:lifecycle_events][event] << block
151
149
  end
152
-
153
- BANNER = %q{ s
154
- ss
155
- sss sss ss
156
- s sss s ssss sss ____ _ _ _ _
157
- s sssss ssss / ___|(_) __| | ___| | _(_) __ _
158
- s sss \___ \| |/ _` |/ _ \ |/ / |/ _` |
159
- s sssss s ___) | | (_| | __/ <| | (_| |
160
- ss s s |____/|_|\__,_|\___|_|\_\_|\__, |
161
- s s s |_|
162
- s s
163
- sss
164
- sss }
165
-
166
150
  end
167
151
 
168
152
  require 'sidekiq/extensions/class_methods'
@@ -206,8 +206,9 @@ module Sidekiq
206
206
  # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
207
207
  @klass ||= case klass
208
208
  when /\ASidekiq::Extensions::Delayed/
209
- (target, method, _) = YAML.load(args[0])
210
- "#{target}.#{method}"
209
+ safe_load(args[0], klass) do |target, method, _|
210
+ "#{target}.#{method}"
211
+ end
211
212
  when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
212
213
  args[0]
213
214
  else
@@ -219,8 +220,9 @@ module Sidekiq
219
220
  # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
220
221
  @args ||= case klass
221
222
  when /\ASidekiq::Extensions::Delayed/
222
- (_, _, arg) = YAML.load(args[0])
223
- arg
223
+ safe_load(args[0], args) do |_, _, arg|
224
+ arg
225
+ end
224
226
  when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
225
227
  args[1..-1]
226
228
  else
@@ -260,6 +262,19 @@ module Sidekiq
260
262
  def [](name)
261
263
  @item.__send__(:[], name)
262
264
  end
265
+
266
+ private
267
+
268
+ def safe_load(content, default)
269
+ begin
270
+ yield *YAML.load(content)
271
+ rescue ::ArgumentError => ex
272
+ # #1761 in dev mode, it's possible to have jobs enqueued which haven't been loaded into
273
+ # memory yet so the YAML can't be loaded.
274
+ Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless Sidekiq.options[:environment] == 'development'
275
+ default
276
+ end
277
+ end
263
278
  end
264
279
 
265
280
  class SortedEntry < Job
@@ -286,32 +301,56 @@ module Sidekiq
286
301
  end
287
302
 
288
303
  def add_to_queue
289
- Sidekiq.redis do |conn|
290
- results = conn.multi do
291
- conn.zrangebyscore('schedule', score, score)
292
- conn.zremrangebyscore('schedule', score, score)
293
- end.first
294
- results.map do |message|
295
- msg = Sidekiq.load_json(message)
296
- Sidekiq::Client.push(msg)
297
- end
304
+ remove_job do |message|
305
+ msg = Sidekiq.load_json(message)
306
+ Sidekiq::Client.push(msg)
298
307
  end
299
308
  end
300
309
 
301
310
  def retry
302
311
  raise "Retry not available on jobs which have not failed" unless item["failed_at"]
312
+ remove_job do |message|
313
+ msg = Sidekiq.load_json(message)
314
+ msg['retry_count'] = msg['retry_count'] - 1
315
+ Sidekiq::Client.push(msg)
316
+ end
317
+ end
318
+
319
+ private
320
+
321
+ def remove_job
303
322
  Sidekiq.redis do |conn|
304
323
  results = conn.multi do
305
324
  conn.zrangebyscore(parent.name, score, score)
306
325
  conn.zremrangebyscore(parent.name, score, score)
307
326
  end.first
308
- results.map do |message|
309
- msg = Sidekiq.load_json(message)
310
- msg['retry_count'] = msg['retry_count'] - 1
311
- Sidekiq::Client.push(msg)
327
+
328
+ if results.size == 1
329
+ yield results.first
330
+ else
331
+ # multiple jobs with the same score
332
+ # find the one with the right JID and push it
333
+ hash = results.group_by do |message|
334
+ if message.index(jid)
335
+ msg = Sidekiq.load_json(message)
336
+ msg['jid'] == jid
337
+ else
338
+ false
339
+ end
340
+ end
341
+ message = hash[true].first
342
+ yield message
343
+
344
+ # push the rest back onto the sorted set
345
+ conn.multi do
346
+ hash[false].each do |message|
347
+ conn.zadd(parent.name, score.to_f.to_s, message)
348
+ end
349
+ end
312
350
  end
313
351
  end
314
352
  end
353
+
315
354
  end
316
355
 
317
356
  class SortedSet
@@ -91,13 +91,28 @@ module Sidekiq
91
91
  end
92
92
  end
93
93
 
94
+ def self.banner
95
+ %q{ s
96
+ ss
97
+ sss sss ss
98
+ s sss s ssss sss ____ _ _ _ _
99
+ s sssss ssss / ___|(_) __| | ___| | _(_) __ _
100
+ s sss \___ \| |/ _` |/ _ \ |/ / |/ _` |
101
+ s sssss s ___) | | (_| | __/ <| | (_| |
102
+ ss s s |____/|_|\__,_|\___|_|\_\_|\__, |
103
+ s s s |_|
104
+ s s
105
+ sss
106
+ sss }
107
+ end
108
+
94
109
  private
95
110
 
96
111
  def print_banner
97
112
  # Print logo and banner for development
98
113
  if environment == 'development' && $stdout.tty?
99
114
  puts "\e[#{31}m"
100
- puts Sidekiq::BANNER
115
+ puts Sidekiq::CLI.banner
101
116
  puts "\e[0m"
102
117
  end
103
118
  end
@@ -83,7 +83,10 @@ module Sidekiq
83
83
 
84
84
  def stop_heartbeat
85
85
  Sidekiq.redis do |conn|
86
- conn.srem('processes', identity)
86
+ conn.pipelined do
87
+ conn.srem('processes', identity)
88
+ conn.del("#{identity}:workers")
89
+ end
87
90
  end
88
91
  end
89
92
  end
@@ -85,7 +85,7 @@ module Sidekiq
85
85
  Sidekiq.redis do |conn|
86
86
  conn.multi do
87
87
  conn.hmset("#{identity}:workers", thread_identity, hash)
88
- conn.expire("#{identity}:workers", 60*60)
88
+ conn.expire("#{identity}:workers", 60*60*4)
89
89
  end
90
90
  end
91
91
  end
@@ -5,7 +5,7 @@ require 'sidekiq/actor'
5
5
  module Sidekiq
6
6
  module Scheduled
7
7
 
8
- POLL_INTERVAL = 15
8
+ INITIAL_WAIT = 10
9
9
 
10
10
  ##
11
11
  # The Poller checks Redis every N seconds for messages in the retry or scheduled
@@ -20,7 +20,7 @@ module Sidekiq
20
20
 
21
21
  def poll(first_time=false)
22
22
  watchdog('scheduling poller thread died!') do
23
- add_jitter if first_time
23
+ initial_wait if first_time
24
24
 
25
25
  begin
26
26
  # A message's "score" in Redis is the time at which it should be processed.
@@ -51,19 +51,39 @@ module Sidekiq
51
51
  logger.error ex.backtrace.first
52
52
  end
53
53
 
54
- after(poll_interval) { poll }
54
+ after(poll_interval * rand) { poll }
55
55
  end
56
56
  end
57
57
 
58
58
  private
59
59
 
60
+ # We do our best to tune poll_interval to the size of the active Sidekiq
61
+ # cluster. If you have 30 processes and poll every 15 seconds, that means one
62
+ # Sidekiq is checking Redis every 0.5 seconds - way too often for most people
63
+ # and really bad if the retry or scheduled sets are large.
64
+ #
65
+ # Instead try to avoid polling more than once every 15 seconds. If you have
66
+ # 30 Sidekiq processes, we'll set poll_interval to 30 * 15 * 2 or 900 seconds.
67
+ # To keep things statistically random, we'll sleep a random amount between
68
+ # 0 and 900 seconds for each poll or 450 seconds on average. Otherwise restarting
69
+ # all your Sidekiq processes at the same time will lead to them all polling at
70
+ # the same time: the thundering herd problem.
71
+ #
72
+ # We only do this if poll_interval is unset (the default).
60
73
  def poll_interval
61
- Sidekiq.options[:poll_interval] || POLL_INTERVAL
74
+ Sidekiq.options[:poll_interval] ||= begin
75
+ pcount = Sidekiq.redis {|c| c.scard('processes') } || 1
76
+ pcount * 15 * 2
77
+ end
62
78
  end
63
79
 
64
- def add_jitter
80
+ def initial_wait
65
81
  begin
66
- sleep(poll_interval * rand)
82
+ # Have all processes sleep between 10-15 seconds. 10 seconds
83
+ # to give time for the heartbeat to register and 5 random seconds
84
+ # to ensure they don't all hit Redis at the same time.
85
+ sleep(INITIAL_WAIT)
86
+ sleep(5 * rand)
67
87
  rescue Celluloid::Task::TerminatedError
68
88
  # Hit Ctrl-C when Sidekiq is finished booting and we have a chance
69
89
  # to get here.
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "3.1.3"
2
+ VERSION = "3.1.4"
3
3
  end
@@ -31,9 +31,9 @@ Sidekiq.logger.level = Logger::ERROR
31
31
  Sidekiq::Test = Minitest::Test
32
32
 
33
33
  require 'sidekiq/redis_connection'
34
- redis_url = ENV['REDIS_URL'] || 'redis://localhost/15'
35
- REDIS = Sidekiq::RedisConnection.create(:url => redis_url, :namespace => 'testy')
34
+ REDIS_URL = ENV['REDIS_URL'] || 'redis://localhost/15'
35
+ REDIS = Sidekiq::RedisConnection.create(:url => REDIS_URL, :namespace => 'testy')
36
36
 
37
37
  Sidekiq.configure_client do |config|
38
- config.redis = { :url => redis_url, :namespace => 'testy' }
38
+ config.redis = { :url => REDIS_URL, :namespace => 'testy' }
39
39
  end
@@ -219,6 +219,7 @@ class TestApi < Sidekiq::Test
219
219
  end
220
220
 
221
221
  it "can move scheduled job to queue" do
222
+ remain_id = ApiWorker.perform_in(100, 1, 'jason')
222
223
  job_id = ApiWorker.perform_in(100, 1, 'jason')
223
224
  job = Sidekiq::ScheduledSet.new.find_job(job_id)
224
225
  q = Sidekiq::Queue.new
@@ -226,8 +227,24 @@ class TestApi < Sidekiq::Test
226
227
  queued_job = q.find_job(job_id)
227
228
  refute_nil queued_job
228
229
  assert_equal queued_job.jid, job_id
230
+ assert_nil Sidekiq::ScheduledSet.new.find_job(job_id)
231
+ refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id)
232
+ end
233
+
234
+ it "handles multiple scheduled jobs when moving to queue" do
235
+ jids = Sidekiq::Client.push_bulk('class' => ApiWorker,
236
+ 'args' => [[1, 'jason'], [2, 'jason']],
237
+ 'at' => Time.now.to_f)
238
+ assert_equal 2, jids.size
239
+ (remain_id, job_id) = jids
229
240
  job = Sidekiq::ScheduledSet.new.find_job(job_id)
230
- assert_nil job
241
+ q = Sidekiq::Queue.new
242
+ job.add_to_queue
243
+ queued_job = q.find_job(job_id)
244
+ refute_nil queued_job
245
+ assert_equal queued_job.jid, job_id
246
+ assert_nil Sidekiq::ScheduledSet.new.find_job(job_id)
247
+ refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id)
231
248
  end
232
249
 
233
250
  it 'can find job by id in sorted sets' do
@@ -4,7 +4,7 @@ require 'sidekiq/fetch'
4
4
  class TestFetcher < Sidekiq::Test
5
5
  describe 'fetcher' do
6
6
  before do
7
- Sidekiq.redis = { :namespace => 'fuzzy' }
7
+ Sidekiq.redis = { :url => REDIS_URL, :namespace => 'fuzzy' }
8
8
  Sidekiq.redis do |conn|
9
9
  conn.flushdb
10
10
  conn.rpush('queue:basic', 'msg')
@@ -115,7 +115,7 @@ class TestManager < Sidekiq::Test
115
115
  info = Sidekiq.redis { |c| c.hmget('identity', 'busy') }
116
116
  assert_equal ["1"], info
117
117
  expires = Sidekiq.redis { |c| c.pttl('identity') }
118
- assert_in_delta 60000, expires, 2
118
+ assert_in_delta 60000, expires, 10
119
119
  end
120
120
  end
121
121
 
@@ -70,6 +70,14 @@ class TestRedisConnection < Sidekiq::Test
70
70
 
71
71
  describe ".determine_redis_provider" do
72
72
 
73
+ before do
74
+ @old_env = ENV.to_hash
75
+ end
76
+
77
+ after do
78
+ ENV.update(@old_env)
79
+ end
80
+
73
81
  def with_env_var(var, uri, skip_provider=false)
74
82
  vars = ['REDISTOGO_URL', 'REDIS_PROVIDER', 'REDIS_URL'] - [var]
75
83
  vars.each do |v|
@@ -197,18 +197,6 @@ td form {
197
197
  padding: 0px;
198
198
  }
199
199
 
200
- .table td {
201
- /* Non standard for webkit */
202
- word-break: break-word;
203
-
204
- -webkit-hyphens: auto;
205
- -moz-hyphens: auto;
206
- hyphens: auto;
207
-
208
- -ms-word-break: break-all;
209
- word-break: break-all;
210
- }
211
-
212
200
  table .table-checkbox label {
213
201
  height: 100%;
214
202
  width: 100%;
@@ -20,7 +20,7 @@
20
20
  <input type="checkbox" class="check_all" />
21
21
  </label>
22
22
  </th>
23
- <th width="25%"><%= t('LastRetry') %></th>
23
+ <th><%= t('LastRetry') %></th>
24
24
  <th><%= t('Queue') %></th>
25
25
  <th><%= t('Job') %></th>
26
26
  <th><%= t('Arguments') %></th>
@@ -20,8 +20,8 @@
20
20
  <input type="checkbox" class="check_all" />
21
21
  </label>
22
22
  </th>
23
- <th width="25%"><%= t('NextRetry') %></th>
24
- <th width="11%"><%= t('RetryCount') %></th>
23
+ <th><%= t('NextRetry') %></th>
24
+ <th><%= t('RetryCount') %></th>
25
25
  <th><%= t('Queue') %></th>
26
26
  <th><%= t('Job') %></th>
27
27
  <th><%= t('Arguments') %></th>
@@ -19,8 +19,8 @@
19
19
  <th width="20px">
20
20
  <input type="checkbox" class="check_all" />
21
21
  </th>
22
- <th width="25%"><%= t('When') %></th>
23
- <th width="10%"><%= t('Queue') %></th>
22
+ <th><%= t('When') %></th>
23
+ <th><%= t('Queue') %></th>
24
24
  <th><%= t('Job') %></th>
25
25
  <th><%= t('Arguments') %></th>
26
26
  </tr>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.3
4
+ version: 3.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-01 00:00:00.000000000 Z
11
+ date: 2014-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis