sidekiq 4.2.10 → 6.1.2

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 (106) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  3. data/.github/workflows/ci.yml +41 -0
  4. data/.gitignore +2 -1
  5. data/.standard.yml +20 -0
  6. data/5.0-Upgrade.md +56 -0
  7. data/6.0-Upgrade.md +72 -0
  8. data/COMM-LICENSE +12 -10
  9. data/Changes.md +354 -1
  10. data/Ent-2.0-Upgrade.md +37 -0
  11. data/Ent-Changes.md +111 -3
  12. data/Gemfile +16 -21
  13. data/Gemfile.lock +192 -0
  14. data/LICENSE +1 -1
  15. data/Pro-4.0-Upgrade.md +35 -0
  16. data/Pro-5.0-Upgrade.md +25 -0
  17. data/Pro-Changes.md +181 -4
  18. data/README.md +19 -33
  19. data/Rakefile +6 -8
  20. data/bin/sidekiq +26 -2
  21. data/bin/sidekiqload +37 -34
  22. data/bin/sidekiqmon +8 -0
  23. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
  24. data/lib/generators/sidekiq/templates/worker_test.rb.erb +1 -1
  25. data/lib/generators/sidekiq/worker_generator.rb +21 -13
  26. data/lib/sidekiq.rb +86 -61
  27. data/lib/sidekiq/api.rb +320 -209
  28. data/lib/sidekiq/cli.rb +207 -217
  29. data/lib/sidekiq/client.rb +78 -51
  30. data/lib/sidekiq/delay.rb +41 -0
  31. data/lib/sidekiq/exception_handler.rb +12 -16
  32. data/lib/sidekiq/extensions/action_mailer.rb +13 -22
  33. data/lib/sidekiq/extensions/active_record.rb +13 -10
  34. data/lib/sidekiq/extensions/class_methods.rb +14 -11
  35. data/lib/sidekiq/extensions/generic_proxy.rb +10 -4
  36. data/lib/sidekiq/fetch.rb +29 -30
  37. data/lib/sidekiq/job_logger.rb +63 -0
  38. data/lib/sidekiq/job_retry.rb +262 -0
  39. data/lib/sidekiq/launcher.rb +102 -69
  40. data/lib/sidekiq/logger.rb +165 -0
  41. data/lib/sidekiq/manager.rb +16 -19
  42. data/lib/sidekiq/middleware/chain.rb +15 -5
  43. data/lib/sidekiq/middleware/i18n.rb +5 -7
  44. data/lib/sidekiq/monitor.rb +133 -0
  45. data/lib/sidekiq/paginator.rb +18 -14
  46. data/lib/sidekiq/processor.rb +161 -82
  47. data/lib/sidekiq/rails.rb +27 -100
  48. data/lib/sidekiq/redis_connection.rb +60 -20
  49. data/lib/sidekiq/scheduled.rb +61 -35
  50. data/lib/sidekiq/sd_notify.rb +149 -0
  51. data/lib/sidekiq/systemd.rb +24 -0
  52. data/lib/sidekiq/testing.rb +48 -28
  53. data/lib/sidekiq/testing/inline.rb +2 -1
  54. data/lib/sidekiq/util.rb +20 -16
  55. data/lib/sidekiq/version.rb +2 -1
  56. data/lib/sidekiq/web.rb +57 -57
  57. data/lib/sidekiq/web/action.rb +14 -14
  58. data/lib/sidekiq/web/application.rb +103 -84
  59. data/lib/sidekiq/web/csrf_protection.rb +158 -0
  60. data/lib/sidekiq/web/helpers.rb +126 -71
  61. data/lib/sidekiq/web/router.rb +18 -17
  62. data/lib/sidekiq/worker.rb +164 -41
  63. data/sidekiq.gemspec +15 -27
  64. data/web/assets/javascripts/application.js +25 -27
  65. data/web/assets/javascripts/dashboard.js +33 -37
  66. data/web/assets/stylesheets/application-dark.css +143 -0
  67. data/web/assets/stylesheets/application-rtl.css +246 -0
  68. data/web/assets/stylesheets/application.css +385 -10
  69. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  70. data/web/assets/stylesheets/bootstrap.css +2 -2
  71. data/web/locales/ar.yml +81 -0
  72. data/web/locales/de.yml +14 -2
  73. data/web/locales/en.yml +4 -0
  74. data/web/locales/es.yml +4 -3
  75. data/web/locales/fa.yml +1 -0
  76. data/web/locales/fr.yml +2 -2
  77. data/web/locales/he.yml +79 -0
  78. data/web/locales/ja.yml +9 -4
  79. data/web/locales/lt.yml +83 -0
  80. data/web/locales/pl.yml +4 -4
  81. data/web/locales/ru.yml +4 -0
  82. data/web/locales/ur.yml +80 -0
  83. data/web/locales/vi.yml +83 -0
  84. data/web/views/_footer.erb +5 -2
  85. data/web/views/_job_info.erb +2 -1
  86. data/web/views/_nav.erb +4 -18
  87. data/web/views/_paging.erb +1 -1
  88. data/web/views/busy.erb +15 -8
  89. data/web/views/dashboard.erb +1 -1
  90. data/web/views/dead.erb +2 -2
  91. data/web/views/layout.erb +12 -2
  92. data/web/views/morgue.erb +9 -6
  93. data/web/views/queue.erb +18 -8
  94. data/web/views/queues.erb +11 -1
  95. data/web/views/retries.erb +14 -7
  96. data/web/views/retry.erb +2 -2
  97. data/web/views/scheduled.erb +7 -4
  98. metadata +41 -188
  99. data/.github/issue_template.md +0 -9
  100. data/.travis.yml +0 -18
  101. data/bin/sidekiqctl +0 -99
  102. data/lib/sidekiq/core_ext.rb +0 -119
  103. data/lib/sidekiq/logging.rb +0 -106
  104. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  105. data/lib/sidekiq/middleware/server/logging.rb +0 -31
  106. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
@@ -1,35 +1,41 @@
1
1
  # frozen_string_literal: true
2
- require 'uri'
3
- require 'set'
4
- require 'yaml'
5
- require 'cgi'
2
+
3
+ require "uri"
4
+ require "set"
5
+ require "yaml"
6
+ require "cgi"
6
7
 
7
8
  module Sidekiq
8
9
  # This is not a public API
9
10
  module WebHelpers
10
11
  def strings(lang)
11
- @@strings ||= {}
12
- @@strings[lang] ||= begin
12
+ @strings ||= {}
13
+ @strings[lang] ||= begin
13
14
  # Allow sidekiq-web extensions to add locale paths
14
15
  # so extensions can be localized
15
16
  settings.locales.each_with_object({}) do |path, global|
16
17
  find_locale_files(lang).each do |file|
17
18
  strs = YAML.load(File.open(file))
18
- global.deep_merge!(strs[lang])
19
+ global.merge!(strs[lang])
19
20
  end
20
21
  end
21
22
  end
22
23
  end
23
24
 
24
25
  def clear_caches
25
- @@strings = nil
26
- @@locale_files = nil
26
+ @strings = nil
27
+ @locale_files = nil
28
+ @available_locales = nil
27
29
  end
28
30
 
29
31
  def locale_files
30
- @@locale_files ||= settings.locales.flat_map do |path|
32
+ @locale_files ||= settings.locales.flat_map { |path|
31
33
  Dir["#{path}/*.yml"]
32
- end
34
+ }
35
+ end
36
+
37
+ def available_locales
38
+ @available_locales ||= locale_files.map { |path| File.basename(path, ".yml") }.uniq
33
39
  end
34
40
 
35
41
  def find_locale_files(lang)
@@ -58,41 +64,75 @@ module Sidekiq
58
64
  end
59
65
 
60
66
  def poll_path
61
- if current_path != '' && params['poll']
62
- root_path + current_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
63
72
  else
64
73
  ""
65
74
  end
66
75
  end
67
76
 
68
- # Given a browser request Accept-Language header like
69
- # "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2", this function
70
- # will return "fr" since that's the first code with a matching
71
- # locale in web/locales
77
+ def text_direction
78
+ get_locale["TextDirection"] || "ltr"
79
+ end
80
+
81
+ def rtl?
82
+ text_direction == "rtl"
83
+ end
84
+
85
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
86
+ def user_preferred_languages
87
+ languages = env["HTTP_ACCEPT_LANGUAGE"]
88
+ languages.to_s.downcase.gsub(/\s+/, "").split(",").map { |language|
89
+ locale, quality = language.split(";q=", 2)
90
+ locale = nil if locale == "*" # Ignore wildcards
91
+ quality = quality ? quality.to_f : 1.0
92
+ [locale, quality]
93
+ }.sort { |(_, left), (_, right)|
94
+ right <=> left
95
+ }.map(&:first).compact
96
+ end
97
+
98
+ # Given an Accept-Language header like "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2"
99
+ # this method will try to best match the available locales to the user's preferred languages.
100
+ #
101
+ # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
72
102
  def locale
73
103
  @locale ||= begin
74
- locale = 'en'.freeze
75
- languages = env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze
76
- languages.downcase.split(','.freeze).each do |lang|
77
- next if lang == '*'.freeze
78
- lang = lang.split(';'.freeze)[0]
79
- break locale = lang if find_locale_files(lang).any?
80
- end
81
- locale
104
+ matched_locale = user_preferred_languages.map { |preferred|
105
+ preferred_language = preferred.split("-", 2).first
106
+
107
+ lang_group = available_locales.select { |available|
108
+ preferred_language == available.split("-", 2).first
109
+ }
110
+
111
+ lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
112
+ }.compact.first
113
+
114
+ matched_locale || "en"
82
115
  end
83
116
  end
84
117
 
118
+ # within is used by Sidekiq Pro
119
+ def display_tags(job, within = nil)
120
+ job.tags.map { |tag|
121
+ "<span class='jobtag label label-info'>#{::Rack::Utils.escape_html(tag)}</span>"
122
+ }.join(" ")
123
+ end
124
+
85
125
  # mperham/sidekiq#3243
86
126
  def unfiltered?
87
- yield unless env['PATH_INFO'].start_with?("/filter/")
127
+ yield unless env["PATH_INFO"].start_with?("/filter/")
88
128
  end
89
129
 
90
130
  def get_locale
91
131
  strings(locale)
92
132
  end
93
133
 
94
- def t(msg, options={})
95
- string = get_locale[msg] || msg
134
+ def t(msg, options = {})
135
+ string = get_locale[msg] || strings("en")[msg] || msg
96
136
  if options.empty?
97
137
  string
98
138
  else
@@ -100,6 +140,10 @@ module Sidekiq
100
140
  end
101
141
  end
102
142
 
143
+ def sort_direction_label
144
+ params[:direction] == "asc" ? "&uarr;" : "&darr;"
145
+ end
146
+
103
147
  def workers
104
148
  @workers ||= Sidekiq::Workers.new
105
149
  end
@@ -112,18 +156,15 @@ module Sidekiq
112
156
  @stats ||= Sidekiq::Stats.new
113
157
  end
114
158
 
115
- def retries_with_score(score)
116
- Sidekiq.redis do |conn|
117
- conn.zrangebyscore('retry', score, score)
118
- end.map { |msg| Sidekiq.load_json(msg) }
119
- end
120
-
121
159
  def redis_connection
122
- Sidekiq.redis { |conn| conn.client.id }
160
+ Sidekiq.redis do |conn|
161
+ c = conn.connection
162
+ "redis://#{c[:location]}/#{c[:db]}"
163
+ end
123
164
  end
124
165
 
125
166
  def namespace
126
- @@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
167
+ @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
127
168
  end
128
169
 
129
170
  def redis_info
@@ -131,39 +172,44 @@ module Sidekiq
131
172
  end
132
173
 
133
174
  def root_path
134
- "#{env['SCRIPT_NAME']}/"
175
+ "#{env["SCRIPT_NAME"]}/"
135
176
  end
136
177
 
137
178
  def current_path
138
- @current_path ||= request.path_info.gsub(/^\//,'')
179
+ @current_path ||= request.path_info.gsub(/^\//, "")
139
180
  end
140
181
 
141
182
  def current_status
142
- workers.size == 0 ? 'idle' : 'active'
183
+ workers.size == 0 ? "idle" : "active"
143
184
  end
144
185
 
145
186
  def relative_time(time)
146
187
  stamp = time.getutc.iso8601
147
- %{<time title="#{stamp}" datetime="#{stamp}">#{time}</time>}
188
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
148
189
  end
149
190
 
150
191
  def job_params(job, score)
151
- "#{score}-#{job['jid']}"
192
+ "#{score}-#{job["jid"]}"
152
193
  end
153
194
 
154
195
  def parse_params(params)
155
- score, jid = params.split("-")
196
+ score, jid = params.split("-", 2)
156
197
  [score.to_f, jid]
157
198
  end
158
199
 
159
- SAFE_QPARAMS = %w(page poll)
200
+ SAFE_QPARAMS = %w[page poll direction]
160
201
 
161
202
  # Merge options with current params, filter safe params, and stringify to query string
162
203
  def qparams(options)
163
- options = options.stringify_keys
164
- params.merge(options).map do |key, value|
204
+ stringified_options = options.transform_keys(&:to_s)
205
+
206
+ to_query_string(params.merge(stringified_options))
207
+ end
208
+
209
+ def to_query_string(params)
210
+ params.map { |key, value|
165
211
  SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
166
- end.compact.join("&")
212
+ }.compact.join("&")
167
213
  end
168
214
 
169
215
  def truncate(text, truncate_after_chars = 2000)
@@ -171,33 +217,38 @@ module Sidekiq
171
217
  end
172
218
 
173
219
  def display_args(args, truncate_after_chars = 2000)
174
- args.map do |arg|
175
- h(truncate(to_display(arg), truncate_after_chars))
176
- end.join(", ")
220
+ return "Invalid job payload, args is nil" if args.nil?
221
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
222
+
223
+ begin
224
+ args.map { |arg|
225
+ h(truncate(to_display(arg), truncate_after_chars))
226
+ }.join(", ")
227
+ rescue
228
+ "Illegal job arguments: #{h args.inspect}"
229
+ end
177
230
  end
178
231
 
179
232
  def csrf_tag
180
- "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
233
+ "<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
181
234
  end
182
235
 
183
236
  def to_display(arg)
237
+ arg.inspect
238
+ rescue
184
239
  begin
185
- arg.inspect
186
- rescue
187
- begin
188
- arg.to_s
189
- rescue => ex
190
- "Cannot display argument: [#{ex.class.name}] #{ex.message}"
191
- end
240
+ arg.to_s
241
+ rescue => ex
242
+ "Cannot display argument: [#{ex.class.name}] #{ex.message}"
192
243
  end
193
244
  end
194
245
 
195
- RETRY_JOB_KEYS = Set.new(%w(
246
+ RETRY_JOB_KEYS = Set.new(%w[
196
247
  queue class args retry_count retried_at failed_at
197
248
  jid error_message error_class backtrace
198
249
  error_backtrace enqueued_at retry wrapped
199
- created_at
200
- ))
250
+ created_at tags
251
+ ])
201
252
 
202
253
  def retry_extra_items(retry_job)
203
254
  @retry_extra_items ||= {}.tap do |extra|
@@ -214,8 +265,8 @@ module Sidekiq
214
265
  return number
215
266
  end
216
267
 
217
- options = {delimiter: ',', separator: '.'}
218
- parts = number.to_s.to_str.split('.')
268
+ options = {delimiter: ",", separator: "."}
269
+ parts = number.to_s.to_str.split(".")
219
270
  parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
220
271
  parts.join(options[:separator])
221
272
  end
@@ -223,8 +274,8 @@ module Sidekiq
223
274
  def h(text)
224
275
  ::Rack::Utils.escape_html(text)
225
276
  rescue ArgumentError => e
226
- raise unless e.message.eql?('invalid byte sequence in UTF-8')
227
- text.encode!('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode!('UTF-8', 'UTF-16')
277
+ raise unless e.message.eql?("invalid byte sequence in UTF-8")
278
+ text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
228
279
  retry
229
280
  end
230
281
 
@@ -241,7 +292,7 @@ module Sidekiq
241
292
  end
242
293
 
243
294
  def environment_title_prefix
244
- environment = Sidekiq.options[:environment] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
295
+ environment = Sidekiq.options[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
245
296
 
246
297
  "[#{environment.upcase}] " unless environment == "production"
247
298
  end
@@ -250,27 +301,31 @@ module Sidekiq
250
301
  "Sidekiq v#{Sidekiq::VERSION}"
251
302
  end
252
303
 
304
+ def server_utc_time
305
+ Time.now.utc.strftime("%H:%M:%S UTC")
306
+ end
307
+
253
308
  def redis_connection_and_namespace
254
309
  @redis_connection_and_namespace ||= begin
255
- namespace_suffix = namespace == nil ? '' : "##{namespace}"
310
+ namespace_suffix = namespace.nil? ? "" : "##{namespace}"
256
311
  "#{redis_connection}#{namespace_suffix}"
257
312
  end
258
313
  end
259
314
 
260
315
  def retry_or_delete_or_kill(job, params)
261
- if params['retry']
316
+ if params["retry"]
262
317
  job.retry
263
- elsif params['delete']
318
+ elsif params["delete"]
264
319
  job.delete
265
- elsif params['kill']
320
+ elsif params["kill"]
266
321
  job.kill
267
322
  end
268
323
  end
269
324
 
270
325
  def delete_or_add_queue(job, params)
271
- if params['delete']
326
+ if params["delete"]
272
327
  job.delete
273
- elsif params['add_to_queue']
328
+ elsif params["add_to_queue"]
274
329
  job.add_to_queue
275
330
  end
276
331
  end
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
- require 'rack'
2
+
3
+ require "rack"
3
4
 
4
5
  module Sidekiq
5
6
  module WebRouter
6
- GET = 'GET'.freeze
7
- DELETE = 'DELETE'.freeze
8
- POST = 'POST'.freeze
9
- PUT = 'PUT'.freeze
10
- PATCH = 'PATCH'.freeze
11
- HEAD = 'HEAD'.freeze
7
+ GET = "GET"
8
+ DELETE = "DELETE"
9
+ POST = "POST"
10
+ PUT = "PUT"
11
+ PATCH = "PATCH"
12
+ HEAD = "HEAD"
12
13
 
13
- ROUTE_PARAMS = 'rack.route_params'.freeze
14
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
15
- PATH_INFO = 'PATH_INFO'.freeze
14
+ ROUTE_PARAMS = "rack.route_params"
15
+ REQUEST_METHOD = "REQUEST_METHOD"
16
+ PATH_INFO = "PATH_INFO"
16
17
 
17
18
  def get(path, &block)
18
19
  route(GET, path, &block)
@@ -35,7 +36,7 @@ module Sidekiq
35
36
  end
36
37
 
37
38
  def route(method, path, &block)
38
- @routes ||= { GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => [] }
39
+ @routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
39
40
 
40
41
  @routes[method] << WebRoute.new(method, path, block)
41
42
  @routes[HEAD] << WebRoute.new(method, path, block) if method == GET
@@ -50,7 +51,8 @@ module Sidekiq
50
51
  path_info = "/" if path_info == ""
51
52
 
52
53
  @routes[request_method].each do |route|
53
- if params = route.match(request_method, path_info)
54
+ params = route.match(request_method, path_info)
55
+ if params
54
56
  env[ROUTE_PARAMS] = params
55
57
 
56
58
  return WebAction.new(env, route.block)
@@ -64,7 +66,7 @@ module Sidekiq
64
66
  class WebRoute
65
67
  attr_accessor :request_method, :pattern, :block, :name
66
68
 
67
- NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/.freeze
69
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/
68
70
 
69
71
  def initialize(request_method, pattern, block)
70
72
  @request_method = request_method
@@ -77,7 +79,7 @@ module Sidekiq
77
79
  end
78
80
 
79
81
  def compile
80
- if pattern.match(NAMED_SEGMENTS_PATTERN)
82
+ if pattern.match?(NAMED_SEGMENTS_PATTERN)
81
83
  p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
82
84
 
83
85
  Regexp.new("\\A#{p}\\Z")
@@ -91,9 +93,8 @@ module Sidekiq
91
93
  when String
92
94
  {} if path == matcher
93
95
  else
94
- if path_match = path.match(matcher)
95
- Hash[path_match.names.map(&:to_sym).zip(path_match.captures)]
96
- end
96
+ path_match = path.match(matcher)
97
+ path_match&.named_captures&.transform_keys(&:to_sym)
97
98
  end
98
99
  end
99
100
  end
@@ -1,20 +1,19 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/client'
3
- require 'sidekiq/core_ext'
4
2
 
5
- module Sidekiq
3
+ require "sidekiq/client"
6
4
 
5
+ module Sidekiq
7
6
  ##
8
7
  # Include this module in your worker class and you can easily create
9
8
  # asynchronous jobs:
10
9
  #
11
- # class HardWorker
12
- # include Sidekiq::Worker
10
+ # class HardWorker
11
+ # include Sidekiq::Worker
13
12
  #
14
- # def perform(*args)
15
- # # do some work
13
+ # def perform(*args)
14
+ # # do some work
15
+ # end
16
16
  # end
17
- # end
18
17
  #
19
18
  # Then in your Rails app, you can do this:
20
19
  #
@@ -22,23 +21,165 @@ module Sidekiq
22
21
  #
23
22
  # Note that perform_async is a class method, perform is an instance method.
24
23
  module Worker
24
+ ##
25
+ # The Options module is extracted so we can include it in ActiveJob::Base
26
+ # and allow native AJs to configure Sidekiq features/internals.
27
+ module Options
28
+ def self.included(base)
29
+ base.extend(ClassMethods)
30
+ base.sidekiq_class_attribute :sidekiq_options_hash
31
+ base.sidekiq_class_attribute :sidekiq_retry_in_block
32
+ base.sidekiq_class_attribute :sidekiq_retries_exhausted_block
33
+ end
34
+
35
+ module ClassMethods
36
+ ACCESSOR_MUTEX = Mutex.new
37
+
38
+ ##
39
+ # Allows customization for this type of Worker.
40
+ # Legal options:
41
+ #
42
+ # queue - name of queue to use for this job type, default *default*
43
+ # retry - enable retries for this Worker in case of error during execution,
44
+ # *true* to use the default or *Integer* count
45
+ # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
46
+ # can be true, false or an integer number of lines to save, default *false*
47
+ #
48
+ # In practice, any option is allowed. This is the main mechanism to configure the
49
+ # options for a specific job.
50
+ def sidekiq_options(opts = {})
51
+ opts = opts.transform_keys(&:to_s) # stringify
52
+ self.sidekiq_options_hash = get_sidekiq_options.merge(opts)
53
+ end
54
+
55
+ def sidekiq_retry_in(&block)
56
+ self.sidekiq_retry_in_block = block
57
+ end
58
+
59
+ def sidekiq_retries_exhausted(&block)
60
+ self.sidekiq_retries_exhausted_block = block
61
+ end
62
+
63
+ def get_sidekiq_options # :nodoc:
64
+ self.sidekiq_options_hash ||= Sidekiq.default_worker_options
65
+ end
66
+
67
+ def sidekiq_class_attribute(*attrs)
68
+ instance_reader = true
69
+ instance_writer = true
70
+
71
+ attrs.each do |name|
72
+ synchronized_getter = "__synchronized_#{name}"
73
+
74
+ singleton_class.instance_eval do
75
+ undef_method(name) if method_defined?(name) || private_method_defined?(name)
76
+ end
77
+
78
+ define_singleton_method(synchronized_getter) { nil }
79
+ singleton_class.class_eval do
80
+ private(synchronized_getter)
81
+ end
82
+
83
+ define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } }
84
+
85
+ ivar = "@#{name}"
86
+
87
+ singleton_class.instance_eval do
88
+ m = "#{name}="
89
+ undef_method(m) if method_defined?(m) || private_method_defined?(m)
90
+ end
91
+ define_singleton_method("#{name}=") do |val|
92
+ singleton_class.class_eval do
93
+ ACCESSOR_MUTEX.synchronize do
94
+ undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
95
+ define_method(synchronized_getter) { val }
96
+ end
97
+ end
98
+
99
+ if singleton_class?
100
+ class_eval do
101
+ undef_method(name) if method_defined?(name) || private_method_defined?(name)
102
+ define_method(name) do
103
+ if instance_variable_defined? ivar
104
+ instance_variable_get ivar
105
+ else
106
+ singleton_class.send name
107
+ end
108
+ end
109
+ end
110
+ end
111
+ val
112
+ end
113
+
114
+ if instance_reader
115
+ undef_method(name) if method_defined?(name) || private_method_defined?(name)
116
+ define_method(name) do
117
+ if instance_variable_defined?(ivar)
118
+ instance_variable_get ivar
119
+ else
120
+ self.class.public_send name
121
+ end
122
+ end
123
+ end
124
+
125
+ if instance_writer
126
+ m = "#{name}="
127
+ undef_method(m) if method_defined?(m) || private_method_defined?(m)
128
+ attr_writer name
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+
25
135
  attr_accessor :jid
26
136
 
27
137
  def self.included(base)
28
- raise ArgumentError, "You cannot include Sidekiq::Worker in an ActiveJob: #{base.name}" if base.ancestors.any? {|c| c.name == 'ActiveJob::Base' }
138
+ raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
29
139
 
140
+ base.include(Options)
30
141
  base.extend(ClassMethods)
31
- base.class_attribute :sidekiq_options_hash
32
- base.class_attribute :sidekiq_retry_in_block
33
- base.class_attribute :sidekiq_retries_exhausted_block
34
142
  end
35
143
 
36
144
  def logger
37
145
  Sidekiq.logger
38
146
  end
39
147
 
40
- module ClassMethods
148
+ # This helper class encapsulates the set options for `set`, e.g.
149
+ #
150
+ # SomeWorker.set(queue: 'foo').perform_async(....)
151
+ #
152
+ class Setter
153
+ def initialize(klass, opts)
154
+ @klass = klass
155
+ @opts = opts
156
+ end
157
+
158
+ def set(options)
159
+ @opts.merge!(options)
160
+ self
161
+ end
162
+
163
+ def perform_async(*args)
164
+ @klass.client_push(@opts.merge("args" => args, "class" => @klass))
165
+ end
166
+
167
+ # +interval+ must be a timestamp, numeric or something that acts
168
+ # numeric (like an activesupport time interval).
169
+ def perform_in(interval, *args)
170
+ int = interval.to_f
171
+ now = Time.now.to_f
172
+ ts = (int < 1_000_000_000 ? now + int : int)
173
+
174
+ payload = @opts.merge("class" => @klass, "args" => args)
175
+ # 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)
178
+ end
179
+ alias_method :perform_at, :perform_in
180
+ end
41
181
 
182
+ module ClassMethods
42
183
  def delay(*args)
43
184
  raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
44
185
  end
@@ -52,12 +193,11 @@ module Sidekiq
52
193
  end
53
194
 
54
195
  def set(options)
55
- Thread.current[:sidekiq_worker_set] = options
56
- self
196
+ Setter.new(self, options)
57
197
  end
58
198
 
59
199
  def perform_async(*args)
60
- client_push('class' => self, 'args' => args)
200
+ client_push("class" => self, "args" => args)
61
201
  end
62
202
 
63
203
  # +interval+ must be a timestamp, numeric or something that acts
@@ -67,10 +207,10 @@ module Sidekiq
67
207
  now = Time.now.to_f
68
208
  ts = (int < 1_000_000_000 ? now + int : int)
69
209
 
70
- item = { 'class' => self, 'args' => args, 'at' => ts }
210
+ item = {"class" => self, "args" => args}
71
211
 
72
212
  # Optimization to enqueue something now that is scheduled to go out now or in the past
73
- item.delete('at'.freeze) if ts <= now
213
+ item["at"] = ts if ts > now
74
214
 
75
215
  client_push(item)
76
216
  end
@@ -89,33 +229,16 @@ module Sidekiq
89
229
  #
90
230
  # In practice, any option is allowed. This is the main mechanism to configure the
91
231
  # options for a specific job.
92
- def sidekiq_options(opts={})
93
- self.sidekiq_options_hash = get_sidekiq_options.merge(opts.stringify_keys)
94
- end
95
-
96
- def sidekiq_retry_in(&block)
97
- self.sidekiq_retry_in_block = block
98
- end
99
-
100
- def sidekiq_retries_exhausted(&block)
101
- self.sidekiq_retries_exhausted_block = block
102
- end
103
-
104
- def get_sidekiq_options # :nodoc:
105
- self.sidekiq_options_hash ||= Sidekiq.default_worker_options
232
+ def sidekiq_options(opts = {})
233
+ super
106
234
  end
107
235
 
108
236
  def client_push(item) # :nodoc:
109
- pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'] || Sidekiq.redis_pool
110
- hash = if Thread.current[:sidekiq_worker_set]
111
- x, Thread.current[:sidekiq_worker_set] = Thread.current[:sidekiq_worker_set], nil
112
- x.stringify_keys.merge(item.stringify_keys)
113
- else
114
- item.stringify_keys
115
- end
116
- Sidekiq::Client.new(pool).push(hash)
117
- end
237
+ pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
238
+ stringified_item = item.transform_keys(&:to_s)
118
239
 
240
+ Sidekiq::Client.new(pool).push(stringified_item)
241
+ end
119
242
  end
120
243
  end
121
244
  end