sidekiq 6.0.3 → 6.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.

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +13 -24
  3. data/6.0-Upgrade.md +1 -1
  4. data/Changes.md +61 -0
  5. data/Ent-Changes.md +15 -2
  6. data/Gemfile +2 -2
  7. data/Gemfile.lock +121 -109
  8. data/Pro-Changes.md +15 -1
  9. data/README.md +2 -5
  10. data/bin/sidekiq +26 -2
  11. data/lib/generators/sidekiq/worker_generator.rb +1 -1
  12. data/lib/sidekiq.rb +13 -7
  13. data/lib/sidekiq/api.rb +9 -5
  14. data/lib/sidekiq/cli.rb +24 -5
  15. data/lib/sidekiq/client.rb +23 -12
  16. data/lib/sidekiq/extensions/active_record.rb +3 -2
  17. data/lib/sidekiq/extensions/class_methods.rb +5 -4
  18. data/lib/sidekiq/fetch.rb +20 -18
  19. data/lib/sidekiq/job_logger.rb +1 -1
  20. data/lib/sidekiq/job_retry.rb +2 -2
  21. data/lib/sidekiq/launcher.rb +34 -7
  22. data/lib/sidekiq/logger.rb +9 -9
  23. data/lib/sidekiq/manager.rb +3 -3
  24. data/lib/sidekiq/monitor.rb +2 -2
  25. data/lib/sidekiq/processor.rb +5 -5
  26. data/lib/sidekiq/rails.rb +16 -18
  27. data/lib/sidekiq/redis_connection.rb +21 -13
  28. data/lib/sidekiq/sd_notify.rb +149 -0
  29. data/lib/sidekiq/systemd.rb +24 -0
  30. data/lib/sidekiq/testing.rb +1 -1
  31. data/lib/sidekiq/version.rb +1 -1
  32. data/lib/sidekiq/web.rb +16 -8
  33. data/lib/sidekiq/web/application.rb +15 -9
  34. data/lib/sidekiq/web/csrf_protection.rb +153 -0
  35. data/lib/sidekiq/web/helpers.rb +4 -7
  36. data/lib/sidekiq/web/router.rb +2 -4
  37. data/lib/sidekiq/worker.rb +4 -7
  38. data/sidekiq.gemspec +2 -3
  39. data/web/assets/javascripts/application.js +24 -21
  40. data/web/assets/stylesheets/application-dark.css +132 -124
  41. data/web/assets/stylesheets/application.css +5 -0
  42. data/web/locales/en.yml +2 -0
  43. data/web/locales/fr.yml +2 -2
  44. data/web/locales/ja.yml +2 -0
  45. data/web/locales/lt.yml +83 -0
  46. data/web/locales/pl.yml +4 -4
  47. data/web/locales/vi.yml +83 -0
  48. data/web/views/layout.erb +1 -1
  49. data/web/views/queues.erb +8 -0
  50. metadata +14 -23
@@ -2,7 +2,21 @@
2
2
 
3
3
  [Sidekiq Changes](https://github.com/mperham/sidekiq/blob/master/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/master/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/master/Ent-Changes.md)
4
4
 
5
- Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how to buy.
5
+ Please see [sidekiq.org](https://sidekiq.org/) for more details and how to buy.
6
+
7
+ 5.1.0
8
+ ---------
9
+
10
+ - Remove old Statsd metrics with `WorkerName` in the name [#4377]
11
+ ```
12
+ job.WorkerName.count -> job.count with tag worker:WorkerName
13
+ job.WorkerName.perform -> job.perform with tag worker:WorkerName
14
+ job.WorkerName.failure -> job.failure with tag worker:WorkerName
15
+ ```
16
+ - Remove `concurrent-ruby` gem dependency [#4586]
17
+ - Update `constantize` for batch callbacks. [#4469]
18
+ - Add queue tag to `jobs.recovered.fetch` metric [#4594]
19
+ - Refactor Pro's fetch infrastructure [#4602]
6
20
 
7
21
  5.0.1
8
22
  ---------
data/README.md CHANGED
@@ -2,11 +2,8 @@ Sidekiq
2
2
  ==============
3
3
 
4
4
  [![Gem Version](https://badge.fury.io/rb/sidekiq.svg)](https://rubygems.org/gems/sidekiq)
5
- [![Code Climate](https://codeclimate.com/github/mperham/sidekiq.svg)](https://codeclimate.com/github/mperham/sidekiq)
6
- [![Test Coverage](https://codeclimate.com/github/mperham/sidekiq/badges/coverage.svg)](https://codeclimate.com/github/mperham/sidekiq/coverage)
5
+ [![Codecov](https://codecov.io/gh/mperham/sidekiq/branch/master/graph/badge.svg)](https://codecov.io/gh/mperham/sidekiq)
7
6
  [![Build Status](https://circleci.com/gh/mperham/sidekiq/tree/master.svg?style=svg)](https://circleci.com/gh/mperham/sidekiq/tree/master)
8
- [![Gitter Chat](https://badges.gitter.im/mperham/sidekiq.svg)](https://gitter.im/mperham/sidekiq)
9
-
10
7
 
11
8
  Simple, efficient background processing for Ruby.
12
9
 
@@ -94,4 +91,4 @@ Please see [LICENSE](https://github.com/mperham/sidekiq/blob/master/LICENSE) for
94
91
  Author
95
92
  -----------------
96
93
 
97
- Mike Perham, [@mperham@mastodon.xyz](https://mastodon.xyz/@mperham) / [@sidekiq](https://twitter.com/sidekiq), [https://www.mikeperham.com](https://www.mikeperham.com) / [https://www.contribsys.com](https://www.contribsys.com)
94
+ Mike Perham, [@getajobmike](https://twitter.com/getajobmike) / [@sidekiq](https://twitter.com/sidekiq), [https://www.mikeperham.com](https://www.mikeperham.com) / [https://www.contribsys.com](https://www.contribsys.com)
@@ -6,13 +6,37 @@ $TESTING = false
6
6
 
7
7
  require_relative '../lib/sidekiq/cli'
8
8
 
9
+ def integrate_with_systemd
10
+ return unless ENV["NOTIFY_SOCKET"]
11
+
12
+ Sidekiq.configure_server do |config|
13
+ Sidekiq.logger.info "Enabling systemd notification integration"
14
+ require "sidekiq/sd_notify"
15
+ config.on(:startup) do
16
+ Sidekiq::SdNotify.ready
17
+ end
18
+ config.on(:shutdown) do
19
+ Sidekiq::SdNotify.stopping
20
+ end
21
+ Sidekiq.start_watchdog if Sidekiq::SdNotify.watchdog?
22
+ end
23
+ end
24
+
9
25
  begin
10
26
  cli = Sidekiq::CLI.instance
11
27
  cli.parse
28
+
29
+ integrate_with_systemd
30
+
12
31
  cli.run
13
32
  rescue => e
14
33
  raise e if $DEBUG
15
- STDERR.puts e.message
16
- STDERR.puts e.backtrace.join("\n")
34
+ if Sidekiq.error_handlers.length == 0
35
+ STDERR.puts e.message
36
+ STDERR.puts e.backtrace.join("\n")
37
+ else
38
+ cli.handle_exception e
39
+ end
40
+
17
41
  exit 1
18
42
  end
@@ -18,7 +18,7 @@ module Sidekiq
18
18
  def create_test_file
19
19
  return unless test_framework
20
20
 
21
- if defined?(RSpec)
21
+ if test_framework == :rspec
22
22
  create_worker_spec
23
23
  else
24
24
  create_worker_test
@@ -20,6 +20,7 @@ module Sidekiq
20
20
  labels: [],
21
21
  concurrency: 10,
22
22
  require: ".",
23
+ strict: true,
23
24
  environment: nil,
24
25
  timeout: 25,
25
26
  poll_interval_average: nil,
@@ -30,16 +31,16 @@ module Sidekiq
30
31
  startup: [],
31
32
  quiet: [],
32
33
  shutdown: [],
33
- heartbeat: [],
34
+ heartbeat: []
34
35
  },
35
36
  dead_max_jobs: 10_000,
36
37
  dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
37
- reloader: proc { |&block| block.call },
38
+ reloader: proc { |&block| block.call }
38
39
  }
39
40
 
40
41
  DEFAULT_WORKER_OPTIONS = {
41
42
  "retry" => true,
42
- "queue" => "default",
43
+ "queue" => "default"
43
44
  }
44
45
 
45
46
  FAKE_INFO = {
@@ -47,7 +48,7 @@ module Sidekiq
47
48
  "uptime_in_days" => "9999",
48
49
  "connected_clients" => "9999",
49
50
  "used_memory_human" => "9P",
50
- "used_memory_peak_human" => "9P",
51
+ "used_memory_peak_human" => "9P"
51
52
  }
52
53
 
53
54
  def self.❨╯°□°❩╯︵┻━┻
@@ -95,10 +96,11 @@ module Sidekiq
95
96
  retryable = true
96
97
  begin
97
98
  yield conn
98
- rescue Redis::CommandError => ex
99
+ rescue Redis::BaseError => ex
99
100
  # 2550 Failover can cause the server to become a replica, need
100
101
  # to disconnect and reopen the socket to get back to the primary.
101
- if retryable && ex.message =~ /READONLY/
102
+ # 4495 Use the same logic if we have a "Not enough replicas" error from the primary
103
+ if retryable && ex.message =~ /READONLY|NOREPLICAS/
102
104
  conn.disconnect!
103
105
  retryable = false
104
106
  retry
@@ -154,7 +156,7 @@ module Sidekiq
154
156
 
155
157
  def self.default_worker_options=(hash)
156
158
  # stringify
157
- @default_worker_options = default_worker_options.merge(Hash[hash.map { |k, v| [k.to_s, v] }])
159
+ @default_worker_options = default_worker_options.merge(hash.transform_keys(&:to_s))
158
160
  end
159
161
 
160
162
  def self.default_worker_options
@@ -210,6 +212,10 @@ module Sidekiq
210
212
  @logger = logger
211
213
  end
212
214
 
215
+ def self.pro?
216
+ defined?(Sidekiq::Pro)
217
+ end
218
+
213
219
  # How frequently Redis should be checked by a random Sidekiq process for
214
220
  # scheduled and retriable jobs. Each individual process will take turns by
215
221
  # waiting some multiple of this value.
@@ -105,7 +105,7 @@ module Sidekiq
105
105
 
106
106
  default_queue_latency: default_queue_latency,
107
107
  workers_size: workers_size,
108
- enqueued: enqueued,
108
+ enqueued: enqueued
109
109
  }
110
110
  end
111
111
 
@@ -273,7 +273,7 @@ module Sidekiq
273
273
  def clear
274
274
  Sidekiq.redis do |conn|
275
275
  conn.multi do
276
- conn.del(@rname)
276
+ conn.unlink(@rname)
277
277
  conn.srem("queues", name)
278
278
  end
279
279
  end
@@ -562,7 +562,7 @@ module Sidekiq
562
562
 
563
563
  def clear
564
564
  Sidekiq.redis do |conn|
565
- conn.del(name)
565
+ conn.unlink(name)
566
566
  end
567
567
  end
568
568
  alias_method :💣, :clear
@@ -921,12 +921,16 @@ module Sidekiq
921
921
  procs = conn.sscan_each("processes").to_a
922
922
  procs.sort.each do |key|
923
923
  valid, workers = conn.pipelined {
924
- conn.exists(key)
924
+ conn.exists?(key)
925
925
  conn.hgetall("#{key}:workers")
926
926
  }
927
927
  next unless valid
928
928
  workers.each_pair do |tid, json|
929
- yield key, tid, Sidekiq.load_json(json)
929
+ hsh = Sidekiq.load_json(json)
930
+ p = hsh["payload"]
931
+ # avoid breaking API, this is a side effect of the JSON optimization in #4316
932
+ hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String)
933
+ yield key, tid, hsh
930
934
  end
931
935
  end
932
936
  end
@@ -38,6 +38,7 @@ module Sidekiq
38
38
  if environment == "development" && $stdout.tty? && Sidekiq.log_formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
39
39
  print_banner
40
40
  end
41
+ logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
41
42
 
42
43
  self_read, self_write = IO.pipe
43
44
  sigs = %w[INT TERM TTIN TSTP]
@@ -53,7 +54,7 @@ module Sidekiq
53
54
 
54
55
  logger.info "Running in #{RUBY_DESCRIPTION}"
55
56
  logger.info Sidekiq::LICENSE
56
- logger.info "Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org" unless defined?(::Sidekiq::Pro)
57
+ logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro)
57
58
 
58
59
  # touch the connection pool so it is created before we
59
60
  # fire startup and start multithreading.
@@ -162,7 +163,7 @@ module Sidekiq
162
163
  Sidekiq.logger.warn "<no backtrace available>"
163
164
  end
164
165
  end
165
- },
166
+ }
166
167
  }
167
168
  UNHANDLED_SIGNAL_HANDLER = ->(cli) { Sidekiq.logger.info "No signal handler registered, ignoring" }
168
169
  SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER
@@ -181,7 +182,11 @@ module Sidekiq
181
182
  end
182
183
 
183
184
  def set_environment(cli_env)
184
- @environment = cli_env || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
185
+ # See #984 for discussion.
186
+ # APP_ENV is now the preferred ENV term since it is not tech-specific.
187
+ # Both Sinatra 2.0+ and Sidekiq support this term.
188
+ # RAILS_ENV and RACK_ENV are there for legacy support.
189
+ @environment = cli_env || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
185
190
  end
186
191
 
187
192
  def symbolize_keys_deep!(hash)
@@ -223,8 +228,7 @@ module Sidekiq
223
228
  opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file]
224
229
 
225
230
  # set defaults
226
- opts[:queues] = ["default"] if opts[:queues].nil? || opts[:queues].empty?
227
- opts[:strict] = true if opts[:strict].nil?
231
+ opts[:queues] = ["default"] if opts[:queues].nil?
228
232
  opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"]
229
233
 
230
234
  # merge with defaults
@@ -353,6 +357,12 @@ module Sidekiq
353
357
  Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose]
354
358
  end
355
359
 
360
+ INTERNAL_OPTIONS = [
361
+ # These are options that are set internally and cannot be
362
+ # set via the config file or command line arguments.
363
+ :strict
364
+ ]
365
+
356
366
  def parse_config(path)
357
367
  opts = YAML.load(ERB.new(File.read(path)).result) || {}
358
368
 
@@ -363,6 +373,8 @@ module Sidekiq
363
373
  end
364
374
 
365
375
  opts = opts.merge(opts.delete(environment.to_sym) || {})
376
+ opts.delete(*INTERNAL_OPTIONS)
377
+
366
378
  parse_queues(opts, opts.delete(:queues) || [])
367
379
 
368
380
  opts
@@ -374,9 +386,16 @@ module Sidekiq
374
386
 
375
387
  def parse_queue(opts, queue, weight = nil)
376
388
  opts[:queues] ||= []
389
+ opts[:strict] = true if opts[:strict].nil?
377
390
  raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
378
391
  [weight.to_i, 1].max.times { opts[:queues] << queue }
379
392
  opts[:strict] = false if weight.to_i > 0
380
393
  end
394
+
395
+ def rails_app?
396
+ defined?(::Rails) && ::Rails.respond_to?(:application)
397
+ end
381
398
  end
382
399
  end
400
+
401
+ require "sidekiq/systemd"
@@ -90,16 +90,17 @@ module Sidekiq
90
90
  # Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
91
91
  # than the number given if the middleware stopped processing for one or more jobs.
92
92
  def push_bulk(items)
93
- arg = items["args"].first
94
- return [] unless arg # no jobs to push
95
- raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless arg.is_a?(Array)
93
+ args = items["args"]
94
+ raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless args.is_a?(Array) && args.all?(Array)
95
+ return [] if args.empty? # no jobs to push
96
96
 
97
97
  at = items.delete("at")
98
98
  raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all?(Numeric))
99
+ raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
99
100
 
100
101
  normed = normalize_item(items)
101
- payloads = items["args"].map.with_index { |args, index|
102
- copy = normed.merge("args" => args, "jid" => SecureRandom.hex(12), "enqueued_at" => Time.now.to_f)
102
+ payloads = args.map.with_index { |job_args, index|
103
+ copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12), "enqueued_at" => Time.now.to_f)
103
104
  copy["at"] = (at.is_a?(Array) ? at[index] : at) if at
104
105
 
105
106
  result = process_single(items["class"], copy)
@@ -218,25 +219,35 @@ module Sidekiq
218
219
  end
219
220
  end
220
221
 
222
+ def validate(item)
223
+ raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args")
224
+ raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array)
225
+ raise(ArgumentError, "Job class must be either a Class or String representation of the class name: `#{item}`") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
226
+ raise(ArgumentError, "Job 'at' must be a Numeric timestamp: `#{item}`") if item.key?("at") && !item["at"].is_a?(Numeric)
227
+ raise(ArgumentError, "Job tags must be an Array: `#{item}`") if item["tags"] && !item["tags"].is_a?(Array)
228
+ end
229
+
221
230
  def normalize_item(item)
222
231
  # 6.0.0 push_bulk bug, #4321
223
232
  # TODO Remove after a while...
224
233
  item.delete("at") if item.key?("at") && item["at"].nil?
225
234
 
226
- 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.key?("class") && item.key?("args")
227
- raise(ArgumentError, "Job args must be an Array") unless item["args"].is_a?(Array)
228
- raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item["class"].is_a?(Class) || item["class"].is_a?(String)
229
- raise(ArgumentError, "Job 'at' must be a Numeric timestamp") if item.key?("at") && !item["at"].is_a?(Numeric)
230
- raise(ArgumentError, "Job tags must be an Array") if item["tags"] && !item["tags"].is_a?(Array)
235
+ validate(item)
231
236
  # raise(ArgumentError, "Arguments must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices") unless JSON.load(JSON.dump(item['args'])) == item['args']
232
237
 
233
- normalized_hash(item["class"])
234
- .each { |key, value| item[key] = value if item[key].nil? }
238
+ # merge in the default sidekiq_options for the item's class and/or wrapped element
239
+ # this allows ActiveJobs to control sidekiq_options too.
240
+ defaults = normalized_hash(item["class"])
241
+ defaults = defaults.merge(item["wrapped"].get_sidekiq_options) if item["wrapped"].respond_to?("get_sidekiq_options")
242
+ item = defaults.merge(item)
243
+
244
+ raise(ArgumentError, "Job must include a valid queue name") if item["queue"].nil? || item["queue"] == ""
235
245
 
236
246
  item["class"] = item["class"].to_s
237
247
  item["queue"] = item["queue"].to_s
238
248
  item["jid"] ||= SecureRandom.hex(12)
239
249
  item["created_at"] ||= Time.now.to_f
250
+
240
251
  item
241
252
  end
242
253
 
@@ -6,9 +6,10 @@ module Sidekiq
6
6
  module Extensions
7
7
  ##
8
8
  # Adds 'delay', 'delay_for' and `delay_until` methods to ActiveRecord to offload instance method
9
- # execution to Sidekiq. Examples:
9
+ # execution to Sidekiq.
10
10
  #
11
- # User.recent_signups.each { |user| user.delay.mark_as_awesome }
11
+ # @example
12
+ # User.recent_signups.each { |user| user.delay.mark_as_awesome }
12
13
  #
13
14
  # Please note, this is not recommended as this will serialize the entire
14
15
  # object to Redis. Your Sidekiq jobs should pass IDs, not entire instances.
@@ -5,11 +5,12 @@ require "sidekiq/extensions/generic_proxy"
5
5
  module Sidekiq
6
6
  module Extensions
7
7
  ##
8
- # Adds 'delay', 'delay_for' and `delay_until` methods to all Classes to offload class method
9
- # execution to Sidekiq. Examples:
8
+ # Adds `delay`, `delay_for` and `delay_until` methods to all Classes to offload class method
9
+ # execution to Sidekiq.
10
10
  #
11
- # User.delay.delete_inactive
12
- # Wikipedia.delay.download_changes_for(Date.today)
11
+ # @example
12
+ # User.delay.delete_inactive
13
+ # Wikipedia.delay.download_changes_for(Date.today)
13
14
  #
14
15
  class DelayedClass
15
16
  include Sidekiq::Worker
@@ -25,8 +25,10 @@ module Sidekiq
25
25
  }
26
26
 
27
27
  def initialize(options)
28
- @strictly_ordered_queues = !!options[:strict]
29
- @queues = options[:queues].map { |q| "queue:#{q}" }
28
+ raise ArgumentError, "missing queue list" unless options[:queues]
29
+ @options = options
30
+ @strictly_ordered_queues = !!@options[:strict]
31
+ @queues = @options[:queues].map { |q| "queue:#{q}" }
30
32
  if @strictly_ordered_queues
31
33
  @queues.uniq!
32
34
  @queues << TIMEOUT
@@ -38,24 +40,9 @@ module Sidekiq
38
40
  UnitOfWork.new(*work) if work
39
41
  end
40
42
 
41
- # Creating the Redis#brpop command takes into account any
42
- # configured queue weights. By default Redis#brpop returns
43
- # data from the first queue that has pending elements. We
44
- # recreate the queue command each time we invoke Redis#brpop
45
- # to honor weights and avoid queue starvation.
46
- def queues_cmd
47
- if @strictly_ordered_queues
48
- @queues
49
- else
50
- queues = @queues.shuffle!.uniq
51
- queues << TIMEOUT
52
- queues
53
- end
54
- end
55
-
56
43
  # By leaving this as a class method, it can be pluggable and used by the Manager actor. Making it
57
44
  # an instance method will make it async to the Fetcher actor
58
- def self.bulk_requeue(inprogress, options)
45
+ def bulk_requeue(inprogress, options)
59
46
  return if inprogress.empty?
60
47
 
61
48
  Sidekiq.logger.debug { "Re-queueing terminated jobs" }
@@ -76,5 +63,20 @@ module Sidekiq
76
63
  rescue => ex
77
64
  Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}")
78
65
  end
66
+
67
+ # Creating the Redis#brpop command takes into account any
68
+ # configured queue weights. By default Redis#brpop returns
69
+ # data from the first queue that has pending elements. We
70
+ # recreate the queue command each time we invoke Redis#brpop
71
+ # to honor weights and avoid queue starvation.
72
+ def queues_cmd
73
+ if @strictly_ordered_queues
74
+ @queues
75
+ else
76
+ queues = @queues.shuffle!.uniq
77
+ queues << TIMEOUT
78
+ queues
79
+ end
80
+ end
79
81
  end
80
82
  end