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

Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +290 -2
  3. data/LICENSE +3 -3
  4. data/README.md +7 -9
  5. data/bin/sidekiq +26 -2
  6. data/bin/sidekiqload +8 -4
  7. data/bin/sidekiqmon +4 -5
  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 +222 -145
  13. data/lib/sidekiq/cli.rb +67 -28
  14. data/lib/sidekiq/client.rb +17 -34
  15. data/lib/sidekiq/delay.rb +2 -0
  16. data/lib/sidekiq/extensions/action_mailer.rb +5 -4
  17. data/lib/sidekiq/extensions/active_record.rb +6 -5
  18. data/lib/sidekiq/extensions/class_methods.rb +7 -6
  19. data/lib/sidekiq/extensions/generic_proxy.rb +5 -3
  20. data/lib/sidekiq/fetch.rb +36 -27
  21. data/lib/sidekiq/job.rb +13 -0
  22. data/lib/sidekiq/job_logger.rb +13 -5
  23. data/lib/sidekiq/job_retry.rb +33 -21
  24. data/lib/sidekiq/job_util.rb +65 -0
  25. data/lib/sidekiq/launcher.rb +110 -28
  26. data/lib/sidekiq/logger.rb +109 -12
  27. data/lib/sidekiq/manager.rb +10 -12
  28. data/lib/sidekiq/middleware/chain.rb +17 -6
  29. data/lib/sidekiq/middleware/current_attributes.rb +57 -0
  30. data/lib/sidekiq/monitor.rb +3 -18
  31. data/lib/sidekiq/paginator.rb +7 -2
  32. data/lib/sidekiq/processor.rb +22 -24
  33. data/lib/sidekiq/rails.rb +27 -18
  34. data/lib/sidekiq/redis_connection.rb +19 -13
  35. data/lib/sidekiq/scheduled.rb +48 -12
  36. data/lib/sidekiq/sd_notify.rb +149 -0
  37. data/lib/sidekiq/systemd.rb +24 -0
  38. data/lib/sidekiq/testing.rb +14 -4
  39. data/lib/sidekiq/util.rb +40 -1
  40. data/lib/sidekiq/version.rb +1 -1
  41. data/lib/sidekiq/web/action.rb +2 -2
  42. data/lib/sidekiq/web/application.rb +41 -31
  43. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  44. data/lib/sidekiq/web/helpers.rb +51 -33
  45. data/lib/sidekiq/web/router.rb +6 -5
  46. data/lib/sidekiq/web.rb +37 -73
  47. data/lib/sidekiq/worker.rb +133 -16
  48. data/lib/sidekiq.rb +29 -8
  49. data/sidekiq.gemspec +13 -6
  50. data/web/assets/images/apple-touch-icon.png +0 -0
  51. data/web/assets/javascripts/application.js +83 -64
  52. data/web/assets/javascripts/dashboard.js +53 -53
  53. data/web/assets/stylesheets/application-dark.css +143 -0
  54. data/web/assets/stylesheets/application-rtl.css +0 -4
  55. data/web/assets/stylesheets/application.css +43 -232
  56. data/web/locales/ar.yml +8 -2
  57. data/web/locales/de.yml +14 -2
  58. data/web/locales/en.yml +6 -1
  59. data/web/locales/es.yml +18 -2
  60. data/web/locales/fr.yml +10 -3
  61. data/web/locales/ja.yml +5 -0
  62. data/web/locales/lt.yml +83 -0
  63. data/web/locales/pl.yml +4 -4
  64. data/web/locales/ru.yml +4 -0
  65. data/web/locales/vi.yml +83 -0
  66. data/web/views/_footer.erb +1 -1
  67. data/web/views/_job_info.erb +3 -2
  68. data/web/views/_poll_link.erb +2 -5
  69. data/web/views/_summary.erb +7 -7
  70. data/web/views/busy.erb +54 -20
  71. data/web/views/dashboard.erb +22 -14
  72. data/web/views/dead.erb +3 -3
  73. data/web/views/layout.erb +3 -1
  74. data/web/views/morgue.erb +9 -6
  75. data/web/views/queue.erb +19 -10
  76. data/web/views/queues.erb +10 -2
  77. data/web/views/retries.erb +11 -8
  78. data/web/views/retry.erb +3 -3
  79. data/web/views/scheduled.erb +5 -2
  80. metadata +34 -54
  81. data/.circleci/config.yml +0 -61
  82. data/.github/contributing.md +0 -32
  83. data/.github/issue_template.md +0 -11
  84. data/.gitignore +0 -13
  85. data/.standard.yml +0 -20
  86. data/3.0-Upgrade.md +0 -70
  87. data/4.0-Upgrade.md +0 -53
  88. data/5.0-Upgrade.md +0 -56
  89. data/6.0-Upgrade.md +0 -70
  90. data/COMM-LICENSE +0 -97
  91. data/Ent-2.0-Upgrade.md +0 -37
  92. data/Ent-Changes.md +0 -250
  93. data/Gemfile +0 -24
  94. data/Gemfile.lock +0 -196
  95. data/Pro-2.0-Upgrade.md +0 -138
  96. data/Pro-3.0-Upgrade.md +0 -44
  97. data/Pro-4.0-Upgrade.md +0 -35
  98. data/Pro-5.0-Upgrade.md +0 -25
  99. data/Pro-Changes.md +0 -768
  100. data/Rakefile +0 -10
  101. data/code_of_conduct.md +0 -50
  102. data/lib/generators/sidekiq/worker_generator.rb +0 -47
@@ -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.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,14 +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
- root_path + current_path
69
- else
70
- ""
71
- end
72
- end
73
-
74
73
  def text_direction
75
74
  get_locale["TextDirection"] || "ltr"
76
75
  end
@@ -112,6 +111,13 @@ module Sidekiq
112
111
  end
113
112
  end
114
113
 
114
+ # within is used by Sidekiq Pro
115
+ def display_tags(job, within = nil)
116
+ job.tags.map { |tag|
117
+ "<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
118
+ }.join(" ")
119
+ end
120
+
115
121
  # mperham/sidekiq#3243
116
122
  def unfiltered?
117
123
  yield unless env["PATH_INFO"].start_with?("/filter/")
@@ -130,6 +136,10 @@ module Sidekiq
130
136
  end
131
137
  end
132
138
 
139
+ def sort_direction_label
140
+ params[:direction] == "asc" ? "&uarr;" : "&darr;"
141
+ end
142
+
133
143
  def workers
134
144
  @workers ||= Sidekiq::Workers.new
135
145
  end
@@ -142,16 +152,9 @@ module Sidekiq
142
152
  @stats ||= Sidekiq::Stats.new
143
153
  end
144
154
 
145
- def retries_with_score(score)
146
- Sidekiq.redis { |conn|
147
- conn.zrangebyscore("retry", score, score)
148
- }.map { |msg| Sidekiq.load_json(msg) }
149
- end
150
-
151
155
  def redis_connection
152
156
  Sidekiq.redis do |conn|
153
- c = conn.connection
154
- "redis://#{c[:location]}/#{c[:db]}"
157
+ conn.connection[:id]
155
158
  end
156
159
  end
157
160
 
@@ -189,16 +192,17 @@ module Sidekiq
189
192
  [score.to_f, jid]
190
193
  end
191
194
 
192
- SAFE_QPARAMS = %w[page poll]
195
+ SAFE_QPARAMS = %w[page direction]
193
196
 
194
197
  # Merge options with current params, filter safe params, and stringify to query string
195
198
  def qparams(options)
196
- # stringify
197
- options.keys.each do |key|
198
- options[key.to_s] = options.delete(key)
199
- end
199
+ stringified_options = options.transform_keys(&:to_s)
200
+
201
+ to_query_string(params.merge(stringified_options))
202
+ end
200
203
 
201
- params.merge(options).map { |key, value|
204
+ def to_query_string(params)
205
+ params.map { |key, value|
202
206
  SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
203
207
  }.compact.join("&")
204
208
  end
@@ -221,7 +225,7 @@ module Sidekiq
221
225
  end
222
226
 
223
227
  def csrf_tag
224
- "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
228
+ "<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
225
229
  end
226
230
 
227
231
  def to_display(arg)
@@ -238,7 +242,7 @@ module Sidekiq
238
242
  queue class args retry_count retried_at failed_at
239
243
  jid error_message error_class backtrace
240
244
  error_backtrace enqueued_at retry wrapped
241
- created_at
245
+ created_at tags
242
246
  ])
243
247
 
244
248
  def retry_extra_items(retry_job)
@@ -249,7 +253,21 @@ module Sidekiq
249
253
  end
250
254
  end
251
255
 
256
+ def format_memory(rss_kb)
257
+ return "0" if rss_kb.nil? || rss_kb == 0
258
+
259
+ if rss_kb < 100_000
260
+ "#{number_with_delimiter(rss_kb)} KB"
261
+ elsif rss_kb < 10_000_000
262
+ "#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
263
+ else
264
+ "#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)).round(1))} GB"
265
+ end
266
+ end
267
+
252
268
  def number_with_delimiter(number)
269
+ return "" if number.nil?
270
+
253
271
  begin
254
272
  Float(number)
255
273
  rescue ArgumentError, TypeError
@@ -283,7 +301,7 @@ module Sidekiq
283
301
  end
284
302
 
285
303
  def environment_title_prefix
286
- environment = Sidekiq.options[:environment] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
304
+ environment = Sidekiq.options[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
287
305
 
288
306
  "[#{environment.upcase}] " unless environment == "production"
289
307
  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)
@@ -66,7 +69,7 @@ module Sidekiq
66
69
  class WebRoute
67
70
  attr_accessor :request_method, :pattern, :block, :name
68
71
 
69
- NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/
72
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/
70
73
 
71
74
  def initialize(request_method, pattern, block)
72
75
  @request_method = request_method
@@ -94,9 +97,7 @@ module Sidekiq
94
97
  {} if path == matcher
95
98
  else
96
99
  path_match = path.match(matcher)
97
- if path_match
98
- Hash[path_match.names.map(&:to_sym).zip(path_match.captures)]
99
- end
100
+ path_match&.named_captures&.transform_keys(&:to_sym)
100
101
  end
101
102
  end
102
103
  end
data/lib/sidekiq/web.rb CHANGED
@@ -10,12 +10,11 @@ require "sidekiq/web/helpers"
10
10
  require "sidekiq/web/router"
11
11
  require "sidekiq/web/action"
12
12
  require "sidekiq/web/application"
13
+ require "sidekiq/web/csrf_protection"
13
14
 
14
- require "rack/protection"
15
-
15
+ require "rack/content_length"
16
16
  require "rack/builder"
17
- require "rack/file"
18
- require "rack/session/cookie"
17
+ require "rack/static"
19
18
 
20
19
  module Sidekiq
21
20
  class Web
@@ -31,7 +30,7 @@ module Sidekiq
31
30
  "Queues" => "queues",
32
31
  "Retries" => "retries",
33
32
  "Scheduled" => "scheduled",
34
- "Dead" => "morgue",
33
+ "Dead" => "morgue"
35
34
  }
36
35
 
37
36
  class << self
@@ -39,14 +38,6 @@ module Sidekiq
39
38
  self
40
39
  end
41
40
 
42
- def middlewares
43
- @middlewares ||= []
44
- end
45
-
46
- def use(*middleware_args, &block)
47
- middlewares << [middleware_args, block]
48
- end
49
-
50
41
  def default_tabs
51
42
  DEFAULT_TABS
52
43
  end
@@ -72,32 +63,45 @@ module Sidekiq
72
63
  opts.each { |key| set(key, false) }
73
64
  end
74
65
 
75
- # Helper for the Sinatra syntax: Sidekiq::Web.set(:session_secret, Rails.application.secrets...)
66
+ def middlewares
67
+ @middlewares ||= []
68
+ end
69
+
70
+ def use(*args, &block)
71
+ middlewares << [args, block]
72
+ end
73
+
76
74
  def set(attribute, value)
77
75
  send(:"#{attribute}=", value)
78
76
  end
79
77
 
80
- attr_accessor :app_url, :session_secret, :redis_pool, :sessions
78
+ def sessions=(val)
79
+ puts "WARNING: Sidekiq::Web.sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
80
+ end
81
+
82
+ def session_secret=(val)
83
+ puts "WARNING: Sidekiq::Web.session_secret= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
84
+ end
85
+
86
+ attr_accessor :app_url, :redis_pool
81
87
  attr_writer :locales, :views
82
88
  end
83
89
 
84
90
  def self.inherited(child)
85
91
  child.app_url = app_url
86
- child.session_secret = session_secret
87
92
  child.redis_pool = redis_pool
88
- child.sessions = sessions
89
93
  end
90
94
 
91
95
  def settings
92
96
  self.class.settings
93
97
  end
94
98
 
95
- def use(*middleware_args, &block)
96
- middlewares << [middleware_args, block]
99
+ def middlewares
100
+ @middlewares ||= self.class.middlewares
97
101
  end
98
102
 
99
- def middlewares
100
- @middlewares ||= Web.middlewares.dup
103
+ def use(*args, &block)
104
+ middlewares << [args, block]
101
105
  end
102
106
 
103
107
  def call(env)
@@ -125,18 +129,8 @@ module Sidekiq
125
129
  send(:"#{attribute}=", value)
126
130
  end
127
131
 
128
- # Default values
129
- set :sessions, true
130
-
131
- attr_writer :sessions
132
-
133
- def sessions
134
- unless instance_variable_defined?("@sessions")
135
- @sessions = self.class.sessions
136
- @sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
137
- end
138
-
139
- @sessions
132
+ def sessions=(val)
133
+ puts "Sidekiq::Web#sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller[2..2].first}"
140
134
  end
141
135
 
142
136
  def self.register(extension)
@@ -145,50 +139,20 @@ module Sidekiq
145
139
 
146
140
  private
147
141
 
148
- def using?(middleware)
149
- middlewares.any? do |(m, _)|
150
- m.is_a?(Array) && (m[0] == middleware || m[0].is_a?(middleware))
151
- end
152
- end
153
-
154
- def build_sessions
155
- middlewares = self.middlewares
156
-
157
- unless using?(::Rack::Protection) || ENV["RACK_ENV"] == "test"
158
- middlewares.unshift [[::Rack::Protection, {use: :authenticity_token}], nil]
159
- end
160
-
161
- s = sessions
162
- return unless s
163
-
164
- unless using? ::Rack::Session::Cookie
165
- unless (secret = Web.session_secret)
166
- require "securerandom"
167
- secret = SecureRandom.hex(64)
168
- end
169
-
170
- options = {secret: secret}
171
- options = options.merge(s.to_hash) if s.respond_to? :to_hash
172
-
173
- middlewares.unshift [[::Rack::Session::Cookie, options], nil]
174
- end
175
- end
176
-
177
142
  def build
178
- build_sessions
179
-
180
- middlewares = self.middlewares
181
143
  klass = self.class
144
+ m = middlewares
182
145
 
183
- ::Rack::Builder.new do
184
- %w[stylesheets javascripts images].each do |asset_dir|
185
- map "/#{asset_dir}" do
186
- run ::Rack::File.new("#{ASSETS}/#{asset_dir}", {"Cache-Control" => "public, max-age=86400"})
187
- end
188
- end
189
-
190
- middlewares.each { |middleware, block| use(*middleware, &block) }
146
+ rules = []
147
+ rules = [[:all, {"Cache-Control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
191
148
 
149
+ ::Rack::Builder.new do
150
+ use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
151
+ root: ASSETS,
152
+ cascade: true,
153
+ header_rules: rules
154
+ m.each { |middleware, block| use(*middleware, &block) }
155
+ use Sidekiq::Web::CsrfProtection unless $TESTING
192
156
  run WebApplication.new(klass)
193
157
  end
194
158
  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
@@ -48,8 +69,8 @@ module Sidekiq
48
69
  # In practice, any option is allowed. This is the main mechanism to configure the
49
70
  # options for a specific job.
50
71
  def sidekiq_options(opts = {})
51
- opts = Hash[opts.map { |k, v| [k.to_s, v] }] # stringify
52
- self.sidekiq_options_hash = get_sidekiq_options.merge(Hash[opts.map { |k, v| [k.to_s, v] }])
72
+ opts = opts.transform_keys(&:to_s) # stringify
73
+ self.sidekiq_options_hash = get_sidekiq_options.merge(opts)
53
74
  end
54
75
 
55
76
  def sidekiq_retry_in(&block)
@@ -150,33 +171,95 @@ 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
178
  @opts = opts
179
+
180
+ # ActiveJob compatibility
181
+ interval = @opts.delete(:wait_until) || @opts.delete(:wait)
182
+ at(interval) if interval
156
183
  end
157
184
 
158
185
  def set(options)
186
+ interval = options.delete(:wait_until) || options.delete(:wait)
159
187
  @opts.merge!(options)
188
+ at(interval) if interval
160
189
  self
161
190
  end
162
191
 
163
192
  def perform_async(*args)
164
- @klass.client_push(@opts.merge("args" => args, "class" => @klass))
193
+ if @opts["sync"] == true
194
+ perform_inline(*args)
195
+ else
196
+ @klass.client_push(@opts.merge("args" => args, "class" => @klass))
197
+ end
198
+ end
199
+
200
+ # Explicit inline execution of a job. Returns nil if the job did not
201
+ # execute, true otherwise.
202
+ def perform_inline(*args)
203
+ raw = @opts.merge("args" => args, "class" => @klass).transform_keys(&:to_s)
204
+
205
+ # validate and normalize payload
206
+ item = normalize_item(raw)
207
+ queue = item["queue"]
208
+
209
+ # run client-side middleware
210
+ result = Sidekiq.client_middleware.invoke(item["class"], item, queue, Sidekiq.redis_pool) do
211
+ item
212
+ end
213
+ return nil unless result
214
+
215
+ # round-trip the payload via JSON
216
+ msg = Sidekiq.load_json(Sidekiq.dump_json(item))
217
+
218
+ # prepare the job instance
219
+ klass = msg["class"].constantize
220
+ job = klass.new
221
+ job.jid = msg["jid"]
222
+ job.bid = msg["bid"] if job.respond_to?(:bid)
223
+
224
+ # run the job through server-side middleware
225
+ result = Sidekiq.server_middleware.invoke(job, msg, msg["queue"]) do
226
+ # perform it
227
+ job.perform(*msg["args"])
228
+ true
229
+ end
230
+ return nil unless result
231
+ # jobs do not return a result. they should store any
232
+ # modified state.
233
+ true
234
+ end
235
+ alias_method :perform_sync, :perform_inline
236
+
237
+ def perform_bulk(args, batch_size: 1_000)
238
+ hash = @opts.transform_keys(&:to_s)
239
+ result = args.each_slice(batch_size).flat_map do |slice|
240
+ Sidekiq::Client.push_bulk(hash.merge("class" => @klass, "args" => slice))
241
+ end
242
+
243
+ result.is_a?(Enumerator::Lazy) ? result.force : result
165
244
  end
166
245
 
167
246
  # +interval+ must be a timestamp, numeric or something that acts
168
247
  # numeric (like an activesupport time interval).
169
248
  def perform_in(interval, *args)
249
+ at(interval).perform_async(*args)
250
+ end
251
+ alias_method :perform_at, :perform_in
252
+
253
+ private
254
+
255
+ def at(interval)
170
256
  int = interval.to_f
171
257
  now = Time.now.to_f
172
258
  ts = (int < 1_000_000_000 ? now + int : int)
173
-
174
- payload = @opts.merge("class" => @klass, "args" => args, "at" => ts)
175
259
  # Optimization to enqueue something now that is scheduled to go out now or in the past
176
- payload.delete("at") if ts <= now
177
- @klass.client_push(payload)
260
+ @opts["at"] = ts if ts > now
261
+ self
178
262
  end
179
- alias_method :perform_at, :perform_in
180
263
  end
181
264
 
182
265
  module ClassMethods
@@ -192,12 +275,49 @@ module Sidekiq
192
275
  raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
193
276
  end
194
277
 
278
+ def queue_as(q)
279
+ sidekiq_options("queue" => q.to_s)
280
+ end
281
+
195
282
  def set(options)
196
283
  Setter.new(self, options)
197
284
  end
198
285
 
199
286
  def perform_async(*args)
200
- client_push("class" => self, "args" => args)
287
+ Setter.new(self, {}).perform_async(*args)
288
+ end
289
+
290
+ # Inline execution of job's perform method after passing through Sidekiq.client_middleware and Sidekiq.server_middleware
291
+ def perform_inline(*args)
292
+ Setter.new(self, {}).perform_inline(*args)
293
+ end
294
+
295
+ ##
296
+ # Push a large number of jobs to Redis, while limiting the batch of
297
+ # each job payload to 1,000. This method helps cut down on the number
298
+ # of round trips to Redis, which can increase the performance of enqueueing
299
+ # large numbers of jobs.
300
+ #
301
+ # +items+ must be an Array of Arrays.
302
+ #
303
+ # For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
304
+ #
305
+ # Example (3 Redis round trips):
306
+ #
307
+ # SomeWorker.perform_async(1)
308
+ # SomeWorker.perform_async(2)
309
+ # SomeWorker.perform_async(3)
310
+ #
311
+ # Would instead become (1 Redis round trip):
312
+ #
313
+ # SomeWorker.perform_bulk([[1], [2], [3]])
314
+ #
315
+ def perform_bulk(items, batch_size: 1_000)
316
+ result = items.each_slice(batch_size).flat_map do |slice|
317
+ Sidekiq::Client.push_bulk("class" => self, "args" => slice)
318
+ end
319
+
320
+ result.is_a?(Enumerator::Lazy) ? result.force : result
201
321
  end
202
322
 
203
323
  # +interval+ must be a timestamp, numeric or something that acts
@@ -207,10 +327,10 @@ module Sidekiq
207
327
  now = Time.now.to_f
208
328
  ts = (int < 1_000_000_000 ? now + int : int)
209
329
 
210
- item = {"class" => self, "args" => args, "at" => ts}
330
+ item = {"class" => self, "args" => args}
211
331
 
212
332
  # Optimization to enqueue something now that is scheduled to go out now or in the past
213
- item.delete("at") if ts <= now
333
+ item["at"] = ts if ts > now
214
334
 
215
335
  client_push(item)
216
336
  end
@@ -235,12 +355,9 @@ module Sidekiq
235
355
 
236
356
  def client_push(item) # :nodoc:
237
357
  pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
238
- # stringify
239
- item.keys.each do |key|
240
- item[key.to_s] = item.delete(key)
241
- end
358
+ stringified_item = item.transform_keys(&:to_s)
242
359
 
243
- Sidekiq::Client.new(pool).push(item)
360
+ Sidekiq::Client.new(pool).push(stringified_item)
244
361
  end
245
362
  end
246
363
  end