sidekiq 6.5.12 → 7.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +149 -20
  3. data/README.md +42 -34
  4. data/bin/sidekiq +3 -8
  5. data/bin/sidekiqload +204 -118
  6. data/bin/sidekiqmon +3 -0
  7. data/lib/sidekiq/api.rb +114 -128
  8. data/lib/sidekiq/capsule.rb +127 -0
  9. data/lib/sidekiq/cli.rb +56 -74
  10. data/lib/sidekiq/client.rb +65 -36
  11. data/lib/sidekiq/component.rb +4 -1
  12. data/lib/sidekiq/config.rb +282 -0
  13. data/lib/sidekiq/deploy.rb +62 -0
  14. data/lib/sidekiq/embedded.rb +61 -0
  15. data/lib/sidekiq/fetch.rb +11 -14
  16. data/lib/sidekiq/job.rb +371 -10
  17. data/lib/sidekiq/job_logger.rb +2 -2
  18. data/lib/sidekiq/job_retry.rb +33 -15
  19. data/lib/sidekiq/job_util.rb +51 -15
  20. data/lib/sidekiq/launcher.rb +66 -62
  21. data/lib/sidekiq/logger.rb +1 -26
  22. data/lib/sidekiq/manager.rb +9 -11
  23. data/lib/sidekiq/metrics/query.rb +3 -3
  24. data/lib/sidekiq/metrics/shared.rb +8 -7
  25. data/lib/sidekiq/metrics/tracking.rb +20 -18
  26. data/lib/sidekiq/middleware/chain.rb +19 -18
  27. data/lib/sidekiq/middleware/current_attributes.rb +52 -20
  28. data/lib/sidekiq/monitor.rb +16 -3
  29. data/lib/sidekiq/paginator.rb +1 -1
  30. data/lib/sidekiq/processor.rb +46 -51
  31. data/lib/sidekiq/rails.rb +8 -7
  32. data/lib/sidekiq/redis_client_adapter.rb +10 -69
  33. data/lib/sidekiq/redis_connection.rb +12 -111
  34. data/lib/sidekiq/scheduled.rb +21 -22
  35. data/lib/sidekiq/testing.rb +6 -34
  36. data/lib/sidekiq/transaction_aware_client.rb +4 -5
  37. data/lib/sidekiq/version.rb +2 -1
  38. data/lib/sidekiq/web/action.rb +3 -3
  39. data/lib/sidekiq/web/application.rb +76 -11
  40. data/lib/sidekiq/web/csrf_protection.rb +2 -2
  41. data/lib/sidekiq/web/helpers.rb +39 -24
  42. data/lib/sidekiq/web.rb +17 -16
  43. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  44. data/lib/sidekiq.rb +76 -274
  45. data/sidekiq.gemspec +12 -10
  46. data/web/assets/javascripts/application.js +18 -0
  47. data/web/assets/javascripts/base-charts.js +106 -0
  48. data/web/assets/javascripts/dashboard-charts.js +168 -0
  49. data/web/assets/javascripts/dashboard.js +3 -223
  50. data/web/assets/javascripts/metrics.js +117 -115
  51. data/web/assets/stylesheets/application-dark.css +4 -0
  52. data/web/assets/stylesheets/application-rtl.css +2 -91
  53. data/web/assets/stylesheets/application.css +23 -298
  54. data/web/locales/ar.yml +70 -70
  55. data/web/locales/cs.yml +62 -62
  56. data/web/locales/da.yml +60 -53
  57. data/web/locales/de.yml +65 -65
  58. data/web/locales/el.yml +2 -7
  59. data/web/locales/en.yml +78 -70
  60. data/web/locales/es.yml +68 -68
  61. data/web/locales/fa.yml +65 -65
  62. data/web/locales/fr.yml +81 -67
  63. data/web/locales/gd.yml +99 -0
  64. data/web/locales/he.yml +65 -64
  65. data/web/locales/hi.yml +59 -59
  66. data/web/locales/it.yml +53 -53
  67. data/web/locales/ja.yml +67 -69
  68. data/web/locales/ko.yml +52 -52
  69. data/web/locales/lt.yml +66 -66
  70. data/web/locales/nb.yml +61 -61
  71. data/web/locales/nl.yml +52 -52
  72. data/web/locales/pl.yml +45 -45
  73. data/web/locales/pt-br.yml +79 -69
  74. data/web/locales/pt.yml +51 -51
  75. data/web/locales/ru.yml +67 -66
  76. data/web/locales/sv.yml +53 -53
  77. data/web/locales/ta.yml +60 -60
  78. data/web/locales/uk.yml +62 -61
  79. data/web/locales/ur.yml +64 -64
  80. data/web/locales/vi.yml +67 -67
  81. data/web/locales/zh-cn.yml +20 -18
  82. data/web/locales/zh-tw.yml +10 -1
  83. data/web/views/_footer.erb +5 -2
  84. data/web/views/_job_info.erb +18 -2
  85. data/web/views/_metrics_period_select.erb +12 -0
  86. data/web/views/_paging.erb +2 -0
  87. data/web/views/_poll_link.erb +1 -1
  88. data/web/views/busy.erb +39 -28
  89. data/web/views/dashboard.erb +36 -5
  90. data/web/views/filtering.erb +7 -0
  91. data/web/views/metrics.erb +33 -20
  92. data/web/views/metrics_for_job.erb +25 -44
  93. data/web/views/morgue.erb +5 -9
  94. data/web/views/queue.erb +10 -14
  95. data/web/views/queues.erb +3 -1
  96. data/web/views/retries.erb +5 -9
  97. data/web/views/scheduled.erb +12 -13
  98. metadata +44 -39
  99. data/lib/sidekiq/delay.rb +0 -43
  100. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  101. data/lib/sidekiq/extensions/active_record.rb +0 -43
  102. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  103. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  104. data/lib/sidekiq/metrics/deploy.rb +0 -47
  105. data/lib/sidekiq/worker.rb +0 -370
  106. data/web/assets/javascripts/graph.js +0 -16
  107. /data/{LICENSE → LICENSE.txt} +0 -0
data/lib/sidekiq/cli.rb CHANGED
@@ -9,20 +9,11 @@ require "erb"
9
9
  require "fileutils"
10
10
 
11
11
  require "sidekiq"
12
+ require "sidekiq/config"
12
13
  require "sidekiq/component"
14
+ require "sidekiq/capsule"
13
15
  require "sidekiq/launcher"
14
16
 
15
- # module ScoutApm
16
- # VERSION = "5.3.1"
17
- # end
18
- fail <<~EOM if defined?(ScoutApm::VERSION) && ScoutApm::VERSION < "5.2.0"
19
-
20
-
21
- scout_apm v#{ScoutApm::VERSION} is unsafe with Sidekiq 6.5. Please run `bundle up scout_apm` to upgrade to 5.2.0 or greater.
22
-
23
-
24
- EOM
25
-
26
17
  module Sidekiq # :nodoc:
27
18
  class CLI
28
19
  include Sidekiq::Component
@@ -33,9 +24,7 @@ module Sidekiq # :nodoc:
33
24
  attr_accessor :config
34
25
 
35
26
  def parse(args = ARGV.dup)
36
- @config = Sidekiq
37
- @config[:error_handlers].clear
38
- @config[:error_handlers] << @config.method(:default_error_handler)
27
+ @config ||= Sidekiq.default_configuration
39
28
 
40
29
  setup_options(args)
41
30
  initialize_logger
@@ -52,7 +41,7 @@ module Sidekiq # :nodoc:
52
41
  def run(boot_app: true)
53
42
  boot_application if boot_app
54
43
 
55
- if environment == "development" && $stdout.tty? && @config.log_formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
44
+ if environment == "development" && $stdout.tty? && @config.logger.formatter.is_a?(Sidekiq::Logger::Formatters::Pretty)
56
45
  print_banner
57
46
  end
58
47
  logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app?
@@ -84,26 +73,27 @@ module Sidekiq # :nodoc:
84
73
  # touch the connection pool so it is created before we
85
74
  # fire startup and start multithreading.
86
75
  info = @config.redis_info
87
- ver = info["redis_version"]
88
- raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4"
76
+ ver = Gem::Version.new(info["redis_version"])
77
+ raise "You are connecting to Redis #{ver}, Sidekiq requires Redis 6.2.0 or greater" if ver < Gem::Version.new("6.2.0")
89
78
 
90
79
  maxmemory_policy = info["maxmemory_policy"]
91
- if maxmemory_policy != "noeviction"
80
+ if maxmemory_policy != "noeviction" && maxmemory_policy != ""
81
+ # Redis Enterprise Cloud returns "" for their policy 😳
92
82
  logger.warn <<~EOM
93
83
 
94
84
 
95
85
  WARNING: Your Redis instance will evict Sidekiq data under heavy load.
96
86
  The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}').
97
- See: https://github.com/mperham/sidekiq/wiki/Using-Redis#memory
87
+ See: https://github.com/sidekiq/sidekiq/wiki/Using-Redis#memory
98
88
 
99
89
  EOM
100
90
  end
101
91
 
102
92
  # Since the user can pass us a connection pool explicitly in the initializer, we
103
93
  # need to verify the size is large enough or else Sidekiq's performance is dramatically slowed.
104
- cursize = @config.redis_pool.size
105
- needed = @config[:concurrency] + 2
106
- raise "Your pool of #{cursize} Redis connections is too small, please increase the size to at least #{needed}" if cursize < needed
94
+ @config.capsules.each_pair do |name, cap|
95
+ raise ArgumentError, "Pool size too small for #{name}" if cap.redis_pool.size < cap.concurrency
96
+ end
107
97
 
108
98
  # cache process identity
109
99
  @config[:identity] = identity
@@ -115,8 +105,8 @@ module Sidekiq # :nodoc:
115
105
  # Starting here the process will now have multiple threads running.
116
106
  fire_event(:startup, reverse: false, reraise: true)
117
107
 
118
- logger.debug { "Client Middleware: #{@config.client_middleware.map(&:klass).join(", ")}" }
119
- logger.debug { "Server Middleware: #{@config.server_middleware.map(&:klass).join(", ")}" }
108
+ logger.debug { "Client Middleware: #{@config.default_capsule.client_middleware.map(&:klass).join(", ")}" }
109
+ logger.debug { "Server Middleware: #{@config.default_capsule.server_middleware.map(&:klass).join(", ")}" }
120
110
 
121
111
  launch(self_read)
122
112
  end
@@ -149,19 +139,34 @@ module Sidekiq # :nodoc:
149
139
  end
150
140
  end
151
141
 
152
- def self.w
153
- "\e[37m"
142
+ HOLIDAY_COLORS = {
143
+ # got other color-specific holidays from around the world?
144
+ # https://developer-book.com/post/definitive-guide-for-colored-text-in-terminal/#256-color-escape-codes
145
+ "3-17" => "\e[1;32m", # St. Patrick's Day green
146
+ "10-31" => "\e[38;5;208m" # Halloween orange
147
+ }
148
+
149
+ def self.day
150
+ @@day ||= begin
151
+ t = Date.today
152
+ "#{t.month}-#{t.day}"
153
+ end
154
154
  end
155
155
 
156
156
  def self.r
157
- "\e[31m"
157
+ @@r ||= HOLIDAY_COLORS[day] || "\e[1;31m"
158
158
  end
159
159
 
160
160
  def self.b
161
- "\e[30m"
161
+ @@b ||= HOLIDAY_COLORS[day] || "\e[30m"
162
+ end
163
+
164
+ def self.w
165
+ "\e[1;37m"
162
166
  end
163
167
 
164
168
  def self.reset
169
+ @@b = @@r = @@day = nil
165
170
  "\e[0m"
166
171
  end
167
172
 
@@ -174,7 +179,7 @@ module Sidekiq # :nodoc:
174
179
  #{w} ,$$$$$b#{b}/#{w}md$$$P^'
175
180
  #{w} .d$$$$$$#{b}/#{w}$$$P'
176
181
  #{w} $$^' `"#{b}/#{w}$$$' #{r}____ _ _ _ _
177
- #{w} $: ,$$: #{r} / ___|(_) __| | ___| | _(_) __ _
182
+ #{w} $: #{b}'#{w},$$: #{r} / ___|(_) __| | ___| | _(_) __ _
178
183
  #{w} `b :$$ #{r} \\___ \\| |/ _` |/ _ \\ |/ / |/ _` |
179
184
  #{w} $$: #{r} ___) | | (_| | __/ <| | (_| |
180
185
  #{w} $$ #{r}|____/|_|\\__,_|\\___|_|\\_\\_|\\__, |
@@ -272,6 +277,18 @@ module Sidekiq # :nodoc:
272
277
 
273
278
  # merge with defaults
274
279
  @config.merge!(opts)
280
+
281
+ @config.default_capsule.tap do |cap|
282
+ cap.queues = opts[:queues]
283
+ cap.concurrency = opts[:concurrency] || @config[:concurrency]
284
+ end
285
+
286
+ opts[:capsules]&.each do |name, cap_config|
287
+ @config.capsule(name.to_s) do |cap|
288
+ cap.queues = cap_config[:queues]
289
+ cap.concurrency = cap_config[:concurrency]
290
+ end
291
+ end
275
292
  end
276
293
 
277
294
  def boot_application
@@ -279,12 +296,11 @@ module Sidekiq # :nodoc:
279
296
 
280
297
  if File.directory?(@config[:require])
281
298
  require "rails"
282
- if ::Rails::VERSION::MAJOR < 5
283
- raise "Sidekiq no longer supports this version of Rails"
284
- else
285
- require "sidekiq/rails"
286
- require File.expand_path("#{@config[:require]}/config/environment.rb")
299
+ if ::Rails::VERSION::MAJOR < 6
300
+ warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 6+"
287
301
  end
302
+ require "sidekiq/rails"
303
+ require File.expand_path("#{@config[:require]}/config/environment.rb")
288
304
  @config[:tag] ||= default_tag
289
305
  else
290
306
  require @config[:require]
@@ -332,10 +348,6 @@ module Sidekiq # :nodoc:
332
348
  opts[:concurrency] = Integer(arg)
333
349
  end
334
350
 
335
- o.on "-d", "--daemon", "Daemonize process" do |arg|
336
- puts "ERROR: Daemonization mode was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
337
- end
338
-
339
351
  o.on "-e", "--environment ENV", "Application environment" do |arg|
340
352
  opts[:environment] = arg
341
353
  end
@@ -345,8 +357,8 @@ module Sidekiq # :nodoc:
345
357
  end
346
358
 
347
359
  o.on "-q", "--queue QUEUE[,WEIGHT]", "Queues to process with optional weights" do |arg|
348
- queue, weight = arg.split(",")
349
- parse_queue opts, queue, weight
360
+ opts[:queues] ||= []
361
+ opts[:queues] << arg
350
362
  end
351
363
 
352
364
  o.on "-r", "--require [PATH|DIR]", "Location of Rails application with jobs or file to require" do |arg|
@@ -365,15 +377,7 @@ module Sidekiq # :nodoc:
365
377
  opts[:config_file] = arg
366
378
  end
367
379
 
368
- o.on "-L", "--logfile PATH", "path to writable logfile" do |arg|
369
- puts "ERROR: Logfile redirection was removed in Sidekiq 6.0, Sidekiq will only log to STDOUT"
370
- end
371
-
372
- o.on "-P", "--pidfile PATH", "path to pidfile" do |arg|
373
- puts "ERROR: PID file creation was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services"
374
- end
375
-
376
- o.on "-V", "--version", "Print version and exit" do |arg|
380
+ o.on "-V", "--version", "Print version and exit" do
377
381
  puts "Sidekiq #{Sidekiq::VERSION}"
378
382
  die(0)
379
383
  end
@@ -393,9 +397,9 @@ module Sidekiq # :nodoc:
393
397
  end
394
398
 
395
399
  def parse_config(path)
396
- erb = ERB.new(File.read(path))
400
+ erb = ERB.new(File.read(path), trim_mode: "-")
397
401
  erb.filename = File.expand_path(path)
398
- opts = load_yaml(erb.result) || {}
402
+ opts = YAML.safe_load(erb.result, permitted_classes: [Symbol], aliases: true) || {}
399
403
 
400
404
  if opts.respond_to? :deep_symbolize_keys!
401
405
  opts.deep_symbolize_keys!
@@ -406,31 +410,9 @@ module Sidekiq # :nodoc:
406
410
  opts = opts.merge(opts.delete(environment.to_sym) || {})
407
411
  opts.delete(:strict)
408
412
 
409
- parse_queues(opts, opts.delete(:queues) || [])
410
-
411
413
  opts
412
414
  end
413
415
 
414
- def load_yaml(src)
415
- if Psych::VERSION > "4.0"
416
- YAML.safe_load(src, permitted_classes: [Symbol], aliases: true)
417
- else
418
- YAML.load(src)
419
- end
420
- end
421
-
422
- def parse_queues(opts, queues_and_weights)
423
- queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
424
- end
425
-
426
- def parse_queue(opts, queue, weight = nil)
427
- opts[:queues] ||= []
428
- opts[:strict] = true if opts[:strict].nil?
429
- raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue)
430
- [weight.to_i, 1].max.times { opts[:queues] << queue.to_s }
431
- opts[:strict] = false if weight.to_i > 0
432
- end
433
-
434
416
  def rails_app?
435
417
  defined?(::Rails) && ::Rails.respond_to?(:application)
436
418
  end
@@ -438,4 +420,4 @@ module Sidekiq # :nodoc:
438
420
  end
439
421
 
440
422
  require "sidekiq/systemd"
441
- require "sidekiq/metrics/tracking" if ENV["SIDEKIQ_METRICS_BETA"]
423
+ require "sidekiq/metrics/tracking"
@@ -21,7 +21,6 @@ module Sidekiq
21
21
  # Sidekiq.client_middleware but you can change as necessary.
22
22
  #
23
23
  def middleware(&block)
24
- @chain ||= Sidekiq.client_middleware
25
24
  if block
26
25
  @chain = @chain.dup
27
26
  yield @chain
@@ -31,18 +30,32 @@ module Sidekiq
31
30
 
32
31
  attr_accessor :redis_pool
33
32
 
34
- # Sidekiq::Client normally uses the default Redis pool but you may
35
- # pass a custom ConnectionPool if you want to shard your
36
- # Sidekiq jobs across several Redis instances (for scalability
37
- # reasons, e.g.)
33
+ # Sidekiq::Client is responsible for pushing job payloads to Redis.
34
+ # Requires the :pool or :config keyword argument.
38
35
  #
39
- # Sidekiq::Client.new(ConnectionPool.new { Redis.new })
36
+ # Sidekiq::Client.new(pool: Sidekiq::RedisConnection.create)
40
37
  #
41
- # Generally this is only needed for very large Sidekiq installs processing
42
- # thousands of jobs per second. I don't recommend sharding unless you
43
- # cannot scale any other way (e.g. splitting your app into smaller apps).
44
- def initialize(redis_pool = nil)
45
- @redis_pool = redis_pool || Thread.current[:sidekiq_via_pool] || Sidekiq.redis_pool
38
+ # Inside the Sidekiq process, you can reuse the configured resources:
39
+ #
40
+ # Sidekiq::Client.new(config: config)
41
+ #
42
+ # @param pool [ConnectionPool] explicit Redis pool to use
43
+ # @param config [Sidekiq::Config] use the pool and middleware from the given Sidekiq container
44
+ # @param chain [Sidekiq::Middleware::Chain] use the given middleware chain
45
+ def initialize(*args, **kwargs)
46
+ if args.size == 1 && kwargs.size == 0
47
+ warn "Sidekiq::Client.new(pool) is deprecated, please use Sidekiq::Client.new(pool: pool), #{caller(0..3)}"
48
+ # old calling method, accept 1 pool argument
49
+ @redis_pool = args[0]
50
+ @chain = Sidekiq.default_configuration.client_middleware
51
+ @config = Sidekiq.default_configuration
52
+ else
53
+ # new calling method: keyword arguments
54
+ @config = kwargs[:config] || Sidekiq.default_configuration
55
+ @redis_pool = kwargs[:pool] || Thread.current[:sidekiq_redis_pool] || @config&.redis_pool
56
+ @chain = kwargs[:chain] || @config&.client_middleware
57
+ raise ArgumentError, "No Redis pool available for Sidekiq::Client" unless @redis_pool
58
+ end
46
59
  end
47
60
 
48
61
  ##
@@ -53,6 +66,7 @@ module Sidekiq
53
66
  # args - an array of simple arguments to the perform method, must be JSON-serializable
54
67
  # at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f)
55
68
  # retry - whether to retry this job if it fails, default true or an integer number of retries
69
+ # retry_for - relative amount of time to retry this job if it fails, default nil
56
70
  # backtrace - whether to save any error backtrace, default false
57
71
  #
58
72
  # If class is set to the class name, the jobs' options will be based on Sidekiq's default
@@ -83,8 +97,9 @@ module Sidekiq
83
97
 
84
98
  ##
85
99
  # Push a large number of jobs to Redis. This method cuts out the redis
86
- # network round trip latency. I wouldn't recommend pushing more than
87
- # 1000 per call but YMMV based on network quality, size of job args, etc.
100
+ # network round trip latency. It pushes jobs in batches if more than
101
+ # `:batch_size` (1000 by default) of jobs are passed. I wouldn't recommend making `:batch_size`
102
+ # larger than 1000 but YMMV based on network quality, size of job args, etc.
88
103
  # A large number of jobs can cause a bit of Redis command processing latency.
89
104
  #
90
105
  # Takes the same arguments as #push except that args is expected to be
@@ -92,13 +107,15 @@ module Sidekiq
92
107
  # is run through the client middleware pipeline and each job gets its own Job ID
93
108
  # as normal.
94
109
  #
95
- # Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
96
- # than the number given if the middleware stopped processing for one or more jobs.
110
+ # Returns an array of the of pushed jobs' jids, may contain nils if any client middleware
111
+ # prevented a job push.
112
+ #
113
+ # Example (pushing jobs in batches):
114
+ # push_bulk('class' => 'MyJob', 'args' => (1..100_000).to_a, batch_size: 1_000)
115
+ #
97
116
  def push_bulk(items)
117
+ batch_size = items.delete(:batch_size) || items.delete("batch_size") || 1_000
98
118
  args = items["args"]
99
- raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless args.is_a?(Array) && args.all?(Array)
100
- return [] if args.empty? # no jobs to push
101
-
102
119
  at = items.delete("at")
103
120
  raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all? { |entry| entry.is_a?(Numeric) })
104
121
  raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
@@ -107,18 +124,28 @@ module Sidekiq
107
124
  raise ArgumentError, "Explicitly passing 'jid' when pushing more than one job is not supported" if jid && args.size > 1
108
125
 
109
126
  normed = normalize_item(items)
110
- payloads = args.map.with_index { |job_args, index|
111
- copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12))
112
- copy["at"] = (at.is_a?(Array) ? at[index] : at) if at
113
- result = middleware.invoke(items["class"], copy, copy["queue"], @redis_pool) do
114
- verify_json(copy)
115
- copy
116
- end
117
- result || nil
118
- }.compact
127
+ slice_index = 0
128
+ result = args.each_slice(batch_size).flat_map do |slice|
129
+ raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless slice.is_a?(Array) && slice.all?(Array)
130
+ break [] if slice.empty? # no jobs to push
131
+
132
+ payloads = slice.map.with_index { |job_args, index|
133
+ copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12))
134
+ copy["at"] = (at.is_a?(Array) ? at[slice_index + index] : at) if at
135
+ result = middleware.invoke(items["class"], copy, copy["queue"], @redis_pool) do
136
+ verify_json(copy)
137
+ copy
138
+ end
139
+ result || nil
140
+ }
141
+ slice_index += batch_size
142
+
143
+ to_push = payloads.compact
144
+ raw_push(to_push) unless to_push.empty?
145
+ payloads.map { |payload| payload&.[]("jid") }
146
+ end
119
147
 
120
- raw_push(payloads) unless payloads.empty?
121
- payloads.collect { |payload| payload["jid"] }
148
+ result.is_a?(Enumerator::Lazy) ? result.force : result
122
149
  end
123
150
 
124
151
  # Allows sharding of jobs across any number of Redis instances. All jobs
@@ -135,11 +162,11 @@ module Sidekiq
135
162
  # you cannot scale any other way (e.g. splitting your app into smaller apps).
136
163
  def self.via(pool)
137
164
  raise ArgumentError, "No pool given" if pool.nil?
138
- current_sidekiq_pool = Thread.current[:sidekiq_via_pool]
139
- Thread.current[:sidekiq_via_pool] = pool
165
+ current_sidekiq_pool = Thread.current[:sidekiq_redis_pool]
166
+ Thread.current[:sidekiq_redis_pool] = pool
140
167
  yield
141
168
  ensure
142
- Thread.current[:sidekiq_via_pool] = current_sidekiq_pool
169
+ Thread.current[:sidekiq_redis_pool] = current_sidekiq_pool
143
170
  end
144
171
 
145
172
  class << self
@@ -147,8 +174,8 @@ module Sidekiq
147
174
  new.push(item)
148
175
  end
149
176
 
150
- def push_bulk(items)
151
- new.push_bulk(items)
177
+ def push_bulk(...)
178
+ new.push_bulk(...)
152
179
  end
153
180
 
154
181
  # Resque compatibility helpers. Note all helpers
@@ -201,14 +228,14 @@ module Sidekiq
201
228
  conn.pipelined do |pipeline|
202
229
  atomic_push(pipeline, payloads)
203
230
  end
204
- rescue RedisConnection.adapter::BaseError => ex
231
+ rescue RedisClient::Error => ex
205
232
  # 2550 Failover can cause the server to become a replica, need
206
233
  # to disconnect and reopen the socket to get back to the primary.
207
234
  # 4495 Use the same logic if we have a "Not enough replicas" error from the primary
208
235
  # 4985 Use the same logic when a blocking command is force-unblocked
209
236
  # The retry logic is copied from sidekiq.rb
210
237
  if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
211
- conn.disconnect!
238
+ conn.close
212
239
  retryable = false
213
240
  retry
214
241
  end
@@ -222,6 +249,8 @@ module Sidekiq
222
249
  if payloads.first.key?("at")
223
250
  conn.zadd("schedule", payloads.flat_map { |hash|
224
251
  at = hash.delete("at").to_s
252
+ # ActiveJob sets this but the job has not been enqueued yet
253
+ hash.delete("enqueued_at")
225
254
  [at, Sidekiq.dump_json(hash)]
226
255
  })
227
256
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sidekiq
2
4
  ##
3
5
  # Sidekiq::Component assumes a config instance is available at @config
@@ -13,7 +15,7 @@ module Sidekiq
13
15
 
14
16
  def safe_thread(name, &block)
15
17
  Thread.new do
16
- Thread.current.name = name
18
+ Thread.current.name = "sidekiq.#{name}"
17
19
  watchdog(name, &block)
18
20
  end
19
21
  end
@@ -50,6 +52,7 @@ module Sidekiq
50
52
  oneshot = options.fetch(:oneshot, true)
51
53
  reverse = options[:reverse]
52
54
  reraise = options[:reraise]
55
+ logger.debug("Firing #{event} event") if oneshot
53
56
 
54
57
  arr = config[:lifecycle_events][event]
55
58
  arr.reverse! if reverse