sidekiq 6.1.2 → 6.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +215 -2
  3. data/LICENSE +3 -3
  4. data/README.md +9 -4
  5. data/bin/sidekiq +3 -3
  6. data/bin/sidekiqload +70 -66
  7. data/bin/sidekiqmon +1 -1
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +321 -145
  13. data/lib/sidekiq/cli.rb +73 -40
  14. data/lib/sidekiq/client.rb +48 -72
  15. data/lib/sidekiq/{util.rb → component.rb} +12 -14
  16. data/lib/sidekiq/delay.rb +3 -1
  17. data/lib/sidekiq/extensions/generic_proxy.rb +4 -2
  18. data/lib/sidekiq/fetch.rb +31 -20
  19. data/lib/sidekiq/job.rb +13 -0
  20. data/lib/sidekiq/job_logger.rb +16 -28
  21. data/lib/sidekiq/job_retry.rb +79 -59
  22. data/lib/sidekiq/job_util.rb +71 -0
  23. data/lib/sidekiq/launcher.rb +126 -65
  24. data/lib/sidekiq/logger.rb +11 -20
  25. data/lib/sidekiq/manager.rb +35 -34
  26. data/lib/sidekiq/metrics/deploy.rb +47 -0
  27. data/lib/sidekiq/metrics/query.rb +153 -0
  28. data/lib/sidekiq/metrics/shared.rb +94 -0
  29. data/lib/sidekiq/metrics/tracking.rb +134 -0
  30. data/lib/sidekiq/middleware/chain.rb +87 -41
  31. data/lib/sidekiq/middleware/current_attributes.rb +63 -0
  32. data/lib/sidekiq/middleware/i18n.rb +6 -4
  33. data/lib/sidekiq/middleware/modules.rb +21 -0
  34. data/lib/sidekiq/monitor.rb +1 -1
  35. data/lib/sidekiq/paginator.rb +8 -8
  36. data/lib/sidekiq/processor.rb +47 -41
  37. data/lib/sidekiq/rails.rb +22 -4
  38. data/lib/sidekiq/redis_client_adapter.rb +154 -0
  39. data/lib/sidekiq/redis_connection.rb +84 -55
  40. data/lib/sidekiq/ring_buffer.rb +29 -0
  41. data/lib/sidekiq/scheduled.rb +55 -25
  42. data/lib/sidekiq/testing/inline.rb +4 -4
  43. data/lib/sidekiq/testing.rb +38 -39
  44. data/lib/sidekiq/transaction_aware_client.rb +45 -0
  45. data/lib/sidekiq/version.rb +1 -1
  46. data/lib/sidekiq/web/action.rb +3 -3
  47. data/lib/sidekiq/web/application.rb +37 -13
  48. data/lib/sidekiq/web/csrf_protection.rb +30 -8
  49. data/lib/sidekiq/web/helpers.rb +60 -28
  50. data/lib/sidekiq/web/router.rb +4 -1
  51. data/lib/sidekiq/web.rb +38 -78
  52. data/lib/sidekiq/worker.rb +136 -13
  53. data/lib/sidekiq.rb +114 -31
  54. data/sidekiq.gemspec +12 -4
  55. data/web/assets/images/apple-touch-icon.png +0 -0
  56. data/web/assets/javascripts/application.js +113 -60
  57. data/web/assets/javascripts/chart.min.js +13 -0
  58. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  59. data/web/assets/javascripts/dashboard.js +50 -67
  60. data/web/assets/javascripts/graph.js +16 -0
  61. data/web/assets/javascripts/metrics.js +262 -0
  62. data/web/assets/stylesheets/application-dark.css +36 -36
  63. data/web/assets/stylesheets/application-rtl.css +0 -4
  64. data/web/assets/stylesheets/application.css +82 -237
  65. data/web/locales/ar.yml +8 -2
  66. data/web/locales/el.yml +43 -19
  67. data/web/locales/en.yml +11 -1
  68. data/web/locales/es.yml +18 -2
  69. data/web/locales/fr.yml +8 -1
  70. data/web/locales/ja.yml +3 -0
  71. data/web/locales/lt.yml +1 -1
  72. data/web/locales/pt-br.yml +27 -9
  73. data/web/views/_footer.erb +1 -1
  74. data/web/views/_job_info.erb +1 -1
  75. data/web/views/_nav.erb +1 -1
  76. data/web/views/_poll_link.erb +2 -5
  77. data/web/views/_summary.erb +7 -7
  78. data/web/views/busy.erb +50 -19
  79. data/web/views/dashboard.erb +23 -14
  80. data/web/views/dead.erb +1 -1
  81. data/web/views/layout.erb +2 -1
  82. data/web/views/metrics.erb +69 -0
  83. data/web/views/metrics_for_job.erb +87 -0
  84. data/web/views/morgue.erb +6 -6
  85. data/web/views/queue.erb +15 -11
  86. data/web/views/queues.erb +3 -3
  87. data/web/views/retries.erb +7 -7
  88. data/web/views/retry.erb +1 -1
  89. data/web/views/scheduled.erb +1 -1
  90. metadata +43 -36
  91. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  92. data/.github/contributing.md +0 -32
  93. data/.github/workflows/ci.yml +0 -41
  94. data/.gitignore +0 -13
  95. data/.standard.yml +0 -20
  96. data/3.0-Upgrade.md +0 -70
  97. data/4.0-Upgrade.md +0 -53
  98. data/5.0-Upgrade.md +0 -56
  99. data/6.0-Upgrade.md +0 -72
  100. data/COMM-LICENSE +0 -97
  101. data/Ent-2.0-Upgrade.md +0 -37
  102. data/Ent-Changes.md +0 -281
  103. data/Gemfile +0 -24
  104. data/Gemfile.lock +0 -192
  105. data/Pro-2.0-Upgrade.md +0 -138
  106. data/Pro-3.0-Upgrade.md +0 -44
  107. data/Pro-4.0-Upgrade.md +0 -35
  108. data/Pro-5.0-Upgrade.md +0 -25
  109. data/Pro-Changes.md +0 -805
  110. data/Rakefile +0 -10
  111. data/code_of_conduct.md +0 -50
  112. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  113. data/lib/sidekiq/exception_handler.rb +0 -27
@@ -10,18 +10,25 @@ module Sidekiq
10
10
  module WebHelpers
11
11
  def strings(lang)
12
12
  @strings ||= {}
13
- @strings[lang] ||= begin
14
- # Allow sidekiq-web extensions to add locale paths
15
- # so extensions can be localized
16
- settings.locales.each_with_object({}) do |path, global|
17
- find_locale_files(lang).each do |file|
18
- strs = YAML.load(File.open(file))
19
- global.merge!(strs[lang])
20
- end
13
+
14
+ # Allow sidekiq-web extensions to add locale paths
15
+ # so extensions can be localized
16
+ @strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
17
+ find_locale_files(lang).each do |file|
18
+ strs = YAML.safe_load(File.open(file))
19
+ global.merge!(strs[lang])
21
20
  end
22
21
  end
23
22
  end
24
23
 
24
+ def singularize(str, count)
25
+ if count == 1 && str.respond_to?(:singularize) # rails
26
+ str.singularize
27
+ else
28
+ str
29
+ end
30
+ end
31
+
25
32
  def clear_caches
26
33
  @strings = nil
27
34
  @locale_files = nil
@@ -63,17 +70,6 @@ module Sidekiq
63
70
  @head_html.join if defined?(@head_html)
64
71
  end
65
72
 
66
- def poll_path
67
- if current_path != "" && params["poll"]
68
- path = root_path + current_path
69
- query_string = to_query_string(params.slice(*params.keys - %w[page poll]))
70
- path += "?#{query_string}" unless query_string.empty?
71
- path
72
- else
73
- ""
74
- end
75
- end
76
-
77
73
  def text_direction
78
74
  get_locale["TextDirection"] || "ltr"
79
75
  end
@@ -118,7 +114,7 @@ module Sidekiq
118
114
  # within is used by Sidekiq Pro
119
115
  def display_tags(job, within = nil)
120
116
  job.tags.map { |tag|
121
- "<span class='jobtag label label-info'>#{::Rack::Utils.escape_html(tag)}</span>"
117
+ "<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
122
118
  }.join(" ")
123
119
  end
124
120
 
@@ -144,22 +140,44 @@ module Sidekiq
144
140
  params[:direction] == "asc" ? "&uarr;" : "&darr;"
145
141
  end
146
142
 
147
- def workers
148
- @workers ||= Sidekiq::Workers.new
143
+ def workset
144
+ @work ||= Sidekiq::WorkSet.new
149
145
  end
150
146
 
151
147
  def processes
152
148
  @processes ||= Sidekiq::ProcessSet.new
153
149
  end
154
150
 
151
+ # Sorts processes by hostname following the natural sort order so that
152
+ # 'worker.1' < 'worker.2' < 'worker.10' < 'worker.20'
153
+ # '2.1.1.1' < '192.168.0.2' < '192.168.0.10'
154
+ def sorted_processes
155
+ @sorted_processes ||= begin
156
+ return processes unless processes.all? { |p| p["hostname"] }
157
+
158
+ split_characters = /[._-]/
159
+
160
+ padding = processes.flat_map { |p| p["hostname"].split(split_characters) }.map(&:size).max
161
+
162
+ processes.to_a.sort_by do |process|
163
+ process["hostname"].split(split_characters).map do |substring|
164
+ # Left-pad the substring with '0' if it starts with a number or 'a'
165
+ # otherwise, so that '25' < 192' < 'a' ('025' < '192' < 'aaa')
166
+ padding_char = substring[0].match?(/\d/) ? "0" : "a"
167
+
168
+ substring.rjust(padding, padding_char)
169
+ end
170
+ end
171
+ end
172
+ end
173
+
155
174
  def stats
156
175
  @stats ||= Sidekiq::Stats.new
157
176
  end
158
177
 
159
178
  def redis_connection
160
179
  Sidekiq.redis do |conn|
161
- c = conn.connection
162
- "redis://#{c[:location]}/#{c[:db]}"
180
+ conn.connection[:id]
163
181
  end
164
182
  end
165
183
 
@@ -180,7 +198,7 @@ module Sidekiq
180
198
  end
181
199
 
182
200
  def current_status
183
- workers.size == 0 ? "idle" : "active"
201
+ workset.size == 0 ? "idle" : "active"
184
202
  end
185
203
 
186
204
  def relative_time(time)
@@ -197,7 +215,7 @@ module Sidekiq
197
215
  [score.to_f, jid]
198
216
  end
199
217
 
200
- SAFE_QPARAMS = %w[page poll direction]
218
+ SAFE_QPARAMS = %w[page direction]
201
219
 
202
220
  # Merge options with current params, filter safe params, and stringify to query string
203
221
  def qparams(options)
@@ -247,7 +265,7 @@ module Sidekiq
247
265
  queue class args retry_count retried_at failed_at
248
266
  jid error_message error_class backtrace
249
267
  error_backtrace enqueued_at retry wrapped
250
- created_at tags
268
+ created_at tags display_class
251
269
  ])
252
270
 
253
271
  def retry_extra_items(retry_job)
@@ -258,7 +276,21 @@ module Sidekiq
258
276
  end
259
277
  end
260
278
 
279
+ def format_memory(rss_kb)
280
+ return "0" if rss_kb.nil? || rss_kb == 0
281
+
282
+ if rss_kb < 100_000
283
+ "#{number_with_delimiter(rss_kb)} KB"
284
+ elsif rss_kb < 10_000_000
285
+ "#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
286
+ else
287
+ "#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)).round(1))} GB"
288
+ end
289
+ end
290
+
261
291
  def number_with_delimiter(number)
292
+ return "" if number.nil?
293
+
262
294
  begin
263
295
  Float(number)
264
296
  rescue ArgumentError, TypeError
@@ -292,7 +324,7 @@ module Sidekiq
292
324
  end
293
325
 
294
326
  def environment_title_prefix
295
- environment = Sidekiq.options[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
327
+ environment = Sidekiq[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
296
328
 
297
329
  "[#{environment.upcase}] " unless environment == "production"
298
330
  end
@@ -15,6 +15,10 @@ module Sidekiq
15
15
  REQUEST_METHOD = "REQUEST_METHOD"
16
16
  PATH_INFO = "PATH_INFO"
17
17
 
18
+ def head(path, &block)
19
+ route(HEAD, path, &block)
20
+ end
21
+
18
22
  def get(path, &block)
19
23
  route(GET, path, &block)
20
24
  end
@@ -39,7 +43,6 @@ module Sidekiq
39
43
  @routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
40
44
 
41
45
  @routes[method] << WebRoute.new(method, path, block)
42
- @routes[HEAD] << WebRoute.new(method, path, block) if method == GET
43
46
  end
44
47
 
45
48
  def match(env)
data/lib/sidekiq/web.rb CHANGED
@@ -13,10 +13,8 @@ require "sidekiq/web/application"
13
13
  require "sidekiq/web/csrf_protection"
14
14
 
15
15
  require "rack/content_length"
16
-
17
16
  require "rack/builder"
18
- require "rack/file"
19
- require "rack/session/cookie"
17
+ require "rack/static"
20
18
 
21
19
  module Sidekiq
22
20
  class Web
@@ -35,19 +33,15 @@ module Sidekiq
35
33
  "Dead" => "morgue"
36
34
  }
37
35
 
36
+ if ENV["SIDEKIQ_METRICS_BETA"] == "1"
37
+ DEFAULT_TABS["Metrics"] = "metrics"
38
+ end
39
+
38
40
  class << self
39
41
  def settings
40
42
  self
41
43
  end
42
44
 
43
- def middlewares
44
- @middlewares ||= []
45
- end
46
-
47
- def use(*middleware_args, &block)
48
- middlewares << [middleware_args, block]
49
- end
50
-
51
45
  def default_tabs
52
46
  DEFAULT_TABS
53
47
  end
@@ -73,32 +67,45 @@ module Sidekiq
73
67
  opts.each { |key| set(key, false) }
74
68
  end
75
69
 
76
- # Helper for the Sinatra syntax: Sidekiq::Web.set(:session_secret, Rails.application.secrets...)
70
+ def middlewares
71
+ @middlewares ||= []
72
+ end
73
+
74
+ def use(*args, &block)
75
+ middlewares << [args, block]
76
+ end
77
+
77
78
  def set(attribute, value)
78
79
  send(:"#{attribute}=", value)
79
80
  end
80
81
 
81
- attr_accessor :app_url, :session_secret, :redis_pool, :sessions
82
+ def sessions=(val)
83
+ puts "WARNING: Sidekiq::Web.sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
84
+ end
85
+
86
+ def session_secret=(val)
87
+ puts "WARNING: Sidekiq::Web.session_secret= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
88
+ end
89
+
90
+ attr_accessor :app_url, :redis_pool
82
91
  attr_writer :locales, :views
83
92
  end
84
93
 
85
94
  def self.inherited(child)
86
95
  child.app_url = app_url
87
- child.session_secret = session_secret
88
96
  child.redis_pool = redis_pool
89
- child.sessions = sessions
90
97
  end
91
98
 
92
99
  def settings
93
100
  self.class.settings
94
101
  end
95
102
 
96
- def use(*middleware_args, &block)
97
- middlewares << [middleware_args, block]
103
+ def middlewares
104
+ @middlewares ||= self.class.middlewares
98
105
  end
99
106
 
100
- def middlewares
101
- @middlewares ||= Web.middlewares.dup
107
+ def use(*args, &block)
108
+ middlewares << [args, block]
102
109
  end
103
110
 
104
111
  def call(env)
@@ -126,18 +133,8 @@ module Sidekiq
126
133
  send(:"#{attribute}=", value)
127
134
  end
128
135
 
129
- # Default values
130
- set :sessions, true
131
-
132
- attr_writer :sessions
133
-
134
- def sessions
135
- unless instance_variable_defined?("@sessions")
136
- @sessions = self.class.sessions
137
- @sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
138
- end
139
-
140
- @sessions
136
+ def sessions=(val)
137
+ puts "Sidekiq::Web#sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller[2..2].first}"
141
138
  end
142
139
 
143
140
  def self.register(extension)
@@ -146,57 +143,20 @@ module Sidekiq
146
143
 
147
144
  private
148
145
 
149
- def using?(middleware)
150
- middlewares.any? do |(m, _)|
151
- m.is_a?(Array) && (m[0] == middleware || m[0].is_a?(middleware))
152
- end
153
- end
154
-
155
- def build_sessions
156
- middlewares = self.middlewares
157
-
158
- s = sessions
159
-
160
- # turn on CSRF protection if sessions are enabled and this is not the test env
161
- if s && !using?(CsrfProtection) && ENV["RACK_ENV"] != "test"
162
- middlewares.unshift [[CsrfProtection], nil]
163
- end
164
-
165
- if s && !using?(::Rack::Session::Cookie)
166
- unless (secret = Web.session_secret)
167
- require "securerandom"
168
- secret = SecureRandom.hex(64)
169
- end
170
-
171
- options = {secret: secret}
172
- options = options.merge(s.to_hash) if s.respond_to? :to_hash
173
-
174
- middlewares.unshift [[::Rack::Session::Cookie, options], nil]
175
- end
176
-
177
- # Since Sidekiq::WebApplication no longer calculates its own
178
- # Content-Length response header, we must ensure that the Rack middleware
179
- # that does this is loaded
180
- unless using? ::Rack::ContentLength
181
- middlewares.unshift [[::Rack::ContentLength], nil]
182
- end
183
- end
184
-
185
146
  def build
186
- build_sessions
187
-
188
- middlewares = self.middlewares
189
147
  klass = self.class
148
+ m = middlewares
190
149
 
191
- ::Rack::Builder.new do
192
- %w[stylesheets javascripts images].each do |asset_dir|
193
- map "/#{asset_dir}" do
194
- run ::Rack::File.new("#{ASSETS}/#{asset_dir}", {"Cache-Control" => "public, max-age=86400"})
195
- end
196
- end
197
-
198
- middlewares.each { |middleware, block| use(*middleware, &block) }
150
+ rules = []
151
+ rules = [[:all, {"cache-control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
199
152
 
153
+ ::Rack::Builder.new do
154
+ use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
155
+ root: ASSETS,
156
+ cascade: true,
157
+ header_rules: rules
158
+ m.each { |middleware, block| use(*middleware, &block) }
159
+ use Sidekiq::Web::CsrfProtection unless $TESTING
200
160
  run WebApplication.new(klass)
201
161
  end
202
162
  end
@@ -9,6 +9,7 @@ module Sidekiq
9
9
  #
10
10
  # class HardWorker
11
11
  # include Sidekiq::Worker
12
+ # sidekiq_options queue: 'critical', retry: 5
12
13
  #
13
14
  # def perform(*args)
14
15
  # # do some work
@@ -20,6 +21,26 @@ module Sidekiq
20
21
  # HardWorker.perform_async(1, 2, 3)
21
22
  #
22
23
  # Note that perform_async is a class method, perform is an instance method.
24
+ #
25
+ # Sidekiq::Worker also includes several APIs to provide compatibility with
26
+ # ActiveJob.
27
+ #
28
+ # class SomeWorker
29
+ # include Sidekiq::Worker
30
+ # queue_as :critical
31
+ #
32
+ # def perform(...)
33
+ # end
34
+ # end
35
+ #
36
+ # SomeWorker.set(wait_until: 1.hour).perform_async(123)
37
+ #
38
+ # Note that arguments passed to the job must still obey Sidekiq's
39
+ # best practice for simple, JSON-native data types. Sidekiq will not
40
+ # implement ActiveJob's more complex argument serialization. For
41
+ # this reason, we don't implement `perform_later` as our call semantics
42
+ # are very different.
43
+ #
23
44
  module Worker
24
45
  ##
25
46
  # The Options module is extracted so we can include it in ActiveJob::Base
@@ -61,7 +82,7 @@ module Sidekiq
61
82
  end
62
83
 
63
84
  def get_sidekiq_options # :nodoc:
64
- self.sidekiq_options_hash ||= Sidekiq.default_worker_options
85
+ self.sidekiq_options_hash ||= Sidekiq.default_job_options
65
86
  end
66
87
 
67
88
  def sidekiq_class_attribute(*attrs)
@@ -150,33 +171,97 @@ module Sidekiq
150
171
  # SomeWorker.set(queue: 'foo').perform_async(....)
151
172
  #
152
173
  class Setter
174
+ include Sidekiq::JobUtil
175
+
153
176
  def initialize(klass, opts)
154
177
  @klass = klass
155
- @opts = opts
178
+ # NB: the internal hash always has stringified keys
179
+ @opts = opts.transform_keys(&:to_s)
180
+
181
+ # ActiveJob compatibility
182
+ interval = @opts.delete("wait_until") || @opts.delete("wait")
183
+ at(interval) if interval
156
184
  end
157
185
 
158
186
  def set(options)
159
- @opts.merge!(options)
187
+ hash = options.transform_keys(&:to_s)
188
+ interval = hash.delete("wait_until") || @opts.delete("wait")
189
+ @opts.merge!(hash)
190
+ at(interval) if interval
160
191
  self
161
192
  end
162
193
 
163
194
  def perform_async(*args)
164
- @klass.client_push(@opts.merge("args" => args, "class" => @klass))
195
+ if @opts["sync"] == true
196
+ perform_inline(*args)
197
+ else
198
+ @klass.client_push(@opts.merge("args" => args, "class" => @klass))
199
+ end
200
+ end
201
+
202
+ # Explicit inline execution of a job. Returns nil if the job did not
203
+ # execute, true otherwise.
204
+ def perform_inline(*args)
205
+ raw = @opts.merge("args" => args, "class" => @klass)
206
+
207
+ # validate and normalize payload
208
+ item = normalize_item(raw)
209
+ queue = item["queue"]
210
+
211
+ # run client-side middleware
212
+ result = Sidekiq.client_middleware.invoke(item["class"], item, queue, Sidekiq.redis_pool) do
213
+ item
214
+ end
215
+ return nil unless result
216
+
217
+ # round-trip the payload via JSON
218
+ msg = Sidekiq.load_json(Sidekiq.dump_json(item))
219
+
220
+ # prepare the job instance
221
+ klass = msg["class"].constantize
222
+ job = klass.new
223
+ job.jid = msg["jid"]
224
+ job.bid = msg["bid"] if job.respond_to?(:bid)
225
+
226
+ # run the job through server-side middleware
227
+ result = Sidekiq.server_middleware.invoke(job, msg, msg["queue"]) do
228
+ # perform it
229
+ job.perform(*msg["args"])
230
+ true
231
+ end
232
+ return nil unless result
233
+ # jobs do not return a result. they should store any
234
+ # modified state.
235
+ true
236
+ end
237
+ alias_method :perform_sync, :perform_inline
238
+
239
+ def perform_bulk(args, batch_size: 1_000)
240
+ client = @klass.build_client
241
+ result = args.each_slice(batch_size).flat_map do |slice|
242
+ client.push_bulk(@opts.merge("class" => @klass, "args" => slice))
243
+ end
244
+
245
+ result.is_a?(Enumerator::Lazy) ? result.force : result
165
246
  end
166
247
 
167
248
  # +interval+ must be a timestamp, numeric or something that acts
168
249
  # numeric (like an activesupport time interval).
169
250
  def perform_in(interval, *args)
251
+ at(interval).perform_async(*args)
252
+ end
253
+ alias_method :perform_at, :perform_in
254
+
255
+ private
256
+
257
+ def at(interval)
170
258
  int = interval.to_f
171
259
  now = Time.now.to_f
172
260
  ts = (int < 1_000_000_000 ? now + int : int)
173
-
174
- payload = @opts.merge("class" => @klass, "args" => args)
175
261
  # Optimization to enqueue something now that is scheduled to go out now or in the past
176
- payload["at"] = ts if ts > now
177
- @klass.client_push(payload)
262
+ @opts["at"] = ts if ts > now
263
+ self
178
264
  end
179
- alias_method :perform_at, :perform_in
180
265
  end
181
266
 
182
267
  module ClassMethods
@@ -192,12 +277,46 @@ module Sidekiq
192
277
  raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
193
278
  end
194
279
 
280
+ def queue_as(q)
281
+ sidekiq_options("queue" => q.to_s)
282
+ end
283
+
195
284
  def set(options)
196
285
  Setter.new(self, options)
197
286
  end
198
287
 
199
288
  def perform_async(*args)
200
- client_push("class" => self, "args" => args)
289
+ Setter.new(self, {}).perform_async(*args)
290
+ end
291
+
292
+ # Inline execution of job's perform method after passing through Sidekiq.client_middleware and Sidekiq.server_middleware
293
+ def perform_inline(*args)
294
+ Setter.new(self, {}).perform_inline(*args)
295
+ end
296
+ alias_method :perform_sync, :perform_inline
297
+
298
+ ##
299
+ # Push a large number of jobs to Redis, while limiting the batch of
300
+ # each job payload to 1,000. This method helps cut down on the number
301
+ # of round trips to Redis, which can increase the performance of enqueueing
302
+ # large numbers of jobs.
303
+ #
304
+ # +items+ must be an Array of Arrays.
305
+ #
306
+ # For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
307
+ #
308
+ # Example (3 Redis round trips):
309
+ #
310
+ # SomeWorker.perform_async(1)
311
+ # SomeWorker.perform_async(2)
312
+ # SomeWorker.perform_async(3)
313
+ #
314
+ # Would instead become (1 Redis round trip):
315
+ #
316
+ # SomeWorker.perform_bulk([[1], [2], [3]])
317
+ #
318
+ def perform_bulk(*args, **kwargs)
319
+ Setter.new(self, {}).perform_bulk(*args, **kwargs)
201
320
  end
202
321
 
203
322
  # +interval+ must be a timestamp, numeric or something that acts
@@ -234,10 +353,14 @@ module Sidekiq
234
353
  end
235
354
 
236
355
  def client_push(item) # :nodoc:
237
- pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
238
- stringified_item = item.transform_keys(&:to_s)
356
+ raise ArgumentError, "Job payloads should contain no Symbols: #{item}" if item.any? { |k, v| k.is_a?(::Symbol) }
357
+ build_client.push(item)
358
+ end
239
359
 
240
- Sidekiq::Client.new(pool).push(stringified_item)
360
+ def build_client # :nodoc:
361
+ pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
362
+ client_class = get_sidekiq_options["client_class"] || Sidekiq::Client
363
+ client_class.new(pool)
241
364
  end
242
365
  end
243
366
  end