sidekiq 4.2.10 → 5.2.7

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 (75) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +61 -0
  3. data/.github/issue_template.md +3 -1
  4. data/.gitignore +3 -0
  5. data/.travis.yml +6 -13
  6. data/5.0-Upgrade.md +56 -0
  7. data/COMM-LICENSE +12 -10
  8. data/Changes.md +158 -1
  9. data/Ent-Changes.md +67 -2
  10. data/Gemfile +14 -20
  11. data/LICENSE +1 -1
  12. data/Pro-4.0-Upgrade.md +35 -0
  13. data/Pro-Changes.md +133 -2
  14. data/README.md +8 -6
  15. data/Rakefile +2 -5
  16. data/bin/sidekiqctl +13 -92
  17. data/bin/sidekiqload +5 -10
  18. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
  19. data/lib/sidekiq.rb +27 -27
  20. data/lib/sidekiq/api.rb +145 -57
  21. data/lib/sidekiq/cli.rb +120 -81
  22. data/lib/sidekiq/client.rb +25 -18
  23. data/lib/sidekiq/core_ext.rb +1 -119
  24. data/lib/sidekiq/ctl.rb +221 -0
  25. data/lib/sidekiq/delay.rb +42 -0
  26. data/lib/sidekiq/exception_handler.rb +2 -4
  27. data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
  28. data/lib/sidekiq/fetch.rb +1 -1
  29. data/lib/sidekiq/job_logger.rb +25 -0
  30. data/lib/sidekiq/job_retry.rb +262 -0
  31. data/lib/sidekiq/launcher.rb +19 -19
  32. data/lib/sidekiq/logging.rb +18 -2
  33. data/lib/sidekiq/manager.rb +5 -6
  34. data/lib/sidekiq/middleware/server/active_record.rb +10 -0
  35. data/lib/sidekiq/processor.rb +126 -48
  36. data/lib/sidekiq/rails.rb +8 -73
  37. data/lib/sidekiq/redis_connection.rb +43 -5
  38. data/lib/sidekiq/scheduled.rb +35 -8
  39. data/lib/sidekiq/testing.rb +16 -7
  40. data/lib/sidekiq/util.rb +5 -2
  41. data/lib/sidekiq/version.rb +1 -1
  42. data/lib/sidekiq/web.rb +4 -4
  43. data/lib/sidekiq/web/action.rb +2 -6
  44. data/lib/sidekiq/web/application.rb +33 -16
  45. data/lib/sidekiq/web/helpers.rb +69 -22
  46. data/lib/sidekiq/web/router.rb +10 -10
  47. data/lib/sidekiq/worker.rb +118 -19
  48. data/sidekiq.gemspec +6 -17
  49. data/web/assets/javascripts/application.js +0 -0
  50. data/web/assets/javascripts/dashboard.js +32 -17
  51. data/web/assets/stylesheets/application-rtl.css +246 -0
  52. data/web/assets/stylesheets/application.css +371 -6
  53. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  54. data/web/assets/stylesheets/bootstrap.css +2 -2
  55. data/web/locales/ar.yml +81 -0
  56. data/web/locales/en.yml +2 -0
  57. data/web/locales/es.yml +4 -3
  58. data/web/locales/fa.yml +1 -0
  59. data/web/locales/he.yml +79 -0
  60. data/web/locales/ja.yml +5 -3
  61. data/web/locales/ur.yml +80 -0
  62. data/web/views/_footer.erb +5 -2
  63. data/web/views/_nav.erb +4 -18
  64. data/web/views/_paging.erb +1 -1
  65. data/web/views/busy.erb +9 -5
  66. data/web/views/dashboard.erb +1 -1
  67. data/web/views/layout.erb +11 -2
  68. data/web/views/morgue.erb +4 -4
  69. data/web/views/queue.erb +8 -7
  70. data/web/views/queues.erb +2 -0
  71. data/web/views/retries.erb +9 -5
  72. data/web/views/scheduled.erb +2 -2
  73. metadata +31 -160
  74. data/lib/sidekiq/middleware/server/logging.rb +0 -31
  75. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
@@ -55,6 +55,15 @@ module Sidekiq
55
55
  yield @server_chain if block_given?
56
56
  @server_chain
57
57
  end
58
+
59
+ def constantize(str)
60
+ names = str.split('::')
61
+ names.shift if names.empty? || names.first.empty?
62
+
63
+ names.inject(Object) do |constant, name|
64
+ constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
65
+ end
66
+ end
58
67
  end
59
68
  end
60
69
 
@@ -63,9 +72,7 @@ module Sidekiq
63
72
 
64
73
  class EmptyQueueError < RuntimeError; end
65
74
 
66
- class Client
67
- alias_method :raw_push_real, :raw_push
68
-
75
+ module TestingClient
69
76
  def raw_push(payloads)
70
77
  if Sidekiq::Testing.fake?
71
78
  payloads.each do |job|
@@ -76,18 +83,20 @@ module Sidekiq
76
83
  true
77
84
  elsif Sidekiq::Testing.inline?
78
85
  payloads.each do |job|
79
- klass = job['class'].constantize
86
+ klass = Sidekiq::Testing.constantize(job['class'])
80
87
  job['id'] ||= SecureRandom.hex(12)
81
88
  job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
82
89
  klass.process_job(job_hash)
83
90
  end
84
91
  true
85
92
  else
86
- raw_push_real(payloads)
93
+ super
87
94
  end
88
95
  end
89
96
  end
90
97
 
98
+ Sidekiq::Client.prepend TestingClient
99
+
91
100
  module Queues
92
101
  ##
93
102
  # The Queues class is only for testing the fake queue implementation.
@@ -309,7 +318,7 @@ module Sidekiq
309
318
  worker_classes = jobs.map { |job| job["class"] }.uniq
310
319
 
311
320
  worker_classes.each do |worker_class|
312
- worker_class.constantize.drain
321
+ Sidekiq::Testing.constantize(worker_class).drain
313
322
  end
314
323
  end
315
324
  end
@@ -317,7 +326,7 @@ module Sidekiq
317
326
  end
318
327
  end
319
328
 
320
- if defined?(::Rails) && !Rails.env.test?
329
+ if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
321
330
  puts("**************************************************")
322
331
  puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
323
332
  puts("**************************************************")
@@ -2,7 +2,6 @@
2
2
  require 'socket'
3
3
  require 'securerandom'
4
4
  require 'sidekiq/exception_handler'
5
- require 'sidekiq/core_ext'
6
5
 
7
6
  module Sidekiq
8
7
  ##
@@ -47,7 +46,10 @@ module Sidekiq
47
46
  @@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
48
47
  end
49
48
 
50
- def fire_event(event, reverse=false)
49
+ def fire_event(event, options={})
50
+ reverse = options[:reverse]
51
+ reraise = options[:reraise]
52
+
51
53
  arr = Sidekiq.options[:lifecycle_events][event]
52
54
  arr.reverse! if reverse
53
55
  arr.each do |block|
@@ -55,6 +57,7 @@ module Sidekiq
55
57
  block.call
56
58
  rescue => ex
57
59
  handle_exception(ex, { context: "Exception during Sidekiq lifecycle event.", event: event })
60
+ raise ex if reraise
58
61
  end
59
62
  end
60
63
  arr.clear
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sidekiq
3
- VERSION = "4.2.10"
3
+ VERSION = "5.2.7"
4
4
  end
@@ -19,10 +19,10 @@ require 'rack/session/cookie'
19
19
  module Sidekiq
20
20
  class Web
21
21
  ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
22
- VIEWS = "#{ROOT}/views".freeze
23
- LOCALES = ["#{ROOT}/locales".freeze]
24
- LAYOUT = "#{VIEWS}/layout.erb".freeze
25
- ASSETS = "#{ROOT}/assets".freeze
22
+ VIEWS = "#{ROOT}/views"
23
+ LOCALES = ["#{ROOT}/locales"]
24
+ LAYOUT = "#{VIEWS}/layout.erb"
25
+ ASSETS = "#{ROOT}/assets"
26
26
 
27
27
  DEFAULT_TABS = {
28
28
  "Dashboard" => '',
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sidekiq
4
4
  class WebAction
5
- RACK_SESSION = 'rack.session'.freeze
5
+ RACK_SESSION = 'rack.session'
6
6
 
7
7
  attr_accessor :env, :block, :type
8
8
 
@@ -39,10 +39,6 @@ module Sidekiq
39
39
  env[RACK_SESSION]
40
40
  end
41
41
 
42
- def content_type(type)
43
- @type = type
44
- end
45
-
46
42
  def erb(content, options = {})
47
43
  if content.kind_of? Symbol
48
44
  unless respond_to?(:"_erb_#{content}")
@@ -81,7 +77,7 @@ module Sidekiq
81
77
  private
82
78
 
83
79
  def _erb(file, locals)
84
- locals.each {|k, v| define_singleton_method(k){ v } } if locals
80
+ locals.each {|k, v| define_singleton_method(k){ v } unless (singleton_methods.include? k)} if locals
85
81
 
86
82
  if file.kind_of?(String)
87
83
  ERB.new(file).result(binding)
@@ -4,9 +4,24 @@ module Sidekiq
4
4
  class WebApplication
5
5
  extend WebRouter
6
6
 
7
- CONTENT_LENGTH = "Content-Length".freeze
8
- CONTENT_TYPE = "Content-Type".freeze
7
+ CONTENT_LENGTH = "Content-Length"
8
+ CONTENT_TYPE = "Content-Type"
9
9
  REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
10
+ CSP_HEADER = [
11
+ "default-src 'self' https: http:",
12
+ "child-src 'self'",
13
+ "connect-src 'self' https: http: wss: ws:",
14
+ "font-src 'self' https: http:",
15
+ "frame-src 'self'",
16
+ "img-src 'self' https: http: data:",
17
+ "manifest-src 'self'",
18
+ "media-src 'self'",
19
+ "object-src 'none'",
20
+ "script-src 'self' https: http: 'unsafe-inline'",
21
+ "style-src 'self' https: http: 'unsafe-inline'",
22
+ "worker-src 'self'",
23
+ "base-uri 'self'"
24
+ ].join('; ').freeze
10
25
 
11
26
  def initialize(klass)
12
27
  @klass = klass
@@ -181,6 +196,12 @@ module Sidekiq
181
196
  redirect "#{root_path}retries"
182
197
  end
183
198
 
199
+ post "/retries/all/kill" do
200
+ Sidekiq::RetrySet.new.kill_all
201
+
202
+ redirect "#{root_path}retries"
203
+ end
204
+
184
205
  post "/retries/:key" do
185
206
  job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
186
207
 
@@ -234,7 +255,6 @@ module Sidekiq
234
255
  get '/stats' do
235
256
  sidekiq_stats = Sidekiq::Stats.new
236
257
  redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
237
-
238
258
  json(
239
259
  sidekiq: {
240
260
  processed: sidekiq_stats.processed,
@@ -247,7 +267,8 @@ module Sidekiq
247
267
  dead: sidekiq_stats.dead_size,
248
268
  default_latency: sidekiq_stats.default_queue_latency
249
269
  },
250
- redis: redis_stats
270
+ redis: redis_stats,
271
+ server_utc_time: server_utc_time
251
272
  )
252
273
  end
253
274
 
@@ -274,19 +295,15 @@ module Sidekiq
274
295
  resp = case resp
275
296
  when Array
276
297
  resp
277
- when Integer
278
- [resp, {}, []]
279
298
  else
280
- type_header = case action.type
281
- when :json
282
- { "Content-Type" => "application/json", "Cache-Control" => "no-cache" }
283
- when String
284
- { "Content-Type" => (action.type || "text/html"), "Cache-Control" => "no-cache" }
285
- else
286
- { "Content-Type" => "text/html", "Cache-Control" => "no-cache" }
287
- end
288
-
289
- [200, type_header, [resp]]
299
+ headers = {
300
+ "Content-Type" => "text/html",
301
+ "Cache-Control" => "no-cache",
302
+ "Content-Language" => action.locale,
303
+ "Content-Security-Policy" => CSP_HEADER
304
+ }
305
+
306
+ [200, headers, [resp]]
290
307
  end
291
308
 
292
309
  resp[1] = resp[1].dup
@@ -15,7 +15,7 @@ module Sidekiq
15
15
  settings.locales.each_with_object({}) do |path, global|
16
16
  find_locale_files(lang).each do |file|
17
17
  strs = YAML.load(File.open(file))
18
- global.deep_merge!(strs[lang])
18
+ global.merge!(strs[lang])
19
19
  end
20
20
  end
21
21
  end
@@ -24,6 +24,7 @@ module Sidekiq
24
24
  def clear_caches
25
25
  @@strings = nil
26
26
  @@locale_files = nil
27
+ @@available_locales = nil
27
28
  end
28
29
 
29
30
  def locale_files
@@ -32,6 +33,10 @@ module Sidekiq
32
33
  end
33
34
  end
34
35
 
36
+ def available_locales
37
+ @@available_locales ||= locale_files.map { |path| File.basename(path, '.yml') }.uniq
38
+ end
39
+
35
40
  def find_locale_files(lang)
36
41
  locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
37
42
  end
@@ -65,20 +70,44 @@ module Sidekiq
65
70
  end
66
71
  end
67
72
 
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
73
+ def text_direction
74
+ get_locale['TextDirection'] || 'ltr'
75
+ end
76
+
77
+ def rtl?
78
+ text_direction == 'rtl'
79
+ end
80
+
81
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
82
+ def user_preferred_languages
83
+ languages = env['HTTP_ACCEPT_LANGUAGE']
84
+ languages.to_s.downcase.gsub(/\s+/, '').split(',').map do |language|
85
+ locale, quality = language.split(';q=', 2)
86
+ locale = nil if locale == '*' # Ignore wildcards
87
+ quality = quality ? quality.to_f : 1.0
88
+ [locale, quality]
89
+ end.sort do |(_, left), (_, right)|
90
+ right <=> left
91
+ end.map(&:first).compact
92
+ end
93
+
94
+ # 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"
95
+ # this method will try to best match the available locales to the user's preferred languages.
96
+ #
97
+ # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
72
98
  def locale
73
99
  @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
100
+ matched_locale = user_preferred_languages.map do |preferred|
101
+ preferred_language = preferred.split('-', 2).first
102
+
103
+ lang_group = available_locales.select do |available|
104
+ preferred_language == available.split('-', 2).first
105
+ end
106
+
107
+ lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
108
+ end.compact.first
109
+
110
+ matched_locale || 'en'
82
111
  end
83
112
  end
84
113
 
@@ -92,7 +121,7 @@ module Sidekiq
92
121
  end
93
122
 
94
123
  def t(msg, options={})
95
- string = get_locale[msg] || msg
124
+ string = get_locale[msg] || strings('en')[msg] || msg
96
125
  if options.empty?
97
126
  string
98
127
  else
@@ -119,11 +148,14 @@ module Sidekiq
119
148
  end
120
149
 
121
150
  def redis_connection
122
- Sidekiq.redis { |conn| conn.client.id }
151
+ Sidekiq.redis do |conn|
152
+ c = conn.connection
153
+ "redis://#{c[:location]}/#{c[:db]}"
154
+ end
123
155
  end
124
156
 
125
157
  def namespace
126
- @@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
158
+ @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
127
159
  end
128
160
 
129
161
  def redis_info
@@ -144,7 +176,7 @@ module Sidekiq
144
176
 
145
177
  def relative_time(time)
146
178
  stamp = time.getutc.iso8601
147
- %{<time title="#{stamp}" datetime="#{stamp}">#{time}</time>}
179
+ %{<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>}
148
180
  end
149
181
 
150
182
  def job_params(job, score)
@@ -152,7 +184,7 @@ module Sidekiq
152
184
  end
153
185
 
154
186
  def parse_params(params)
155
- score, jid = params.split("-")
187
+ score, jid = params.split("-", 2)
156
188
  [score.to_f, jid]
157
189
  end
158
190
 
@@ -160,7 +192,11 @@ module Sidekiq
160
192
 
161
193
  # Merge options with current params, filter safe params, and stringify to query string
162
194
  def qparams(options)
163
- options = options.stringify_keys
195
+ # stringify
196
+ options.keys.each do |key|
197
+ options[key.to_s] = options.delete(key)
198
+ end
199
+
164
200
  params.merge(options).map do |key, value|
165
201
  SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
166
202
  end.compact.join("&")
@@ -171,9 +207,16 @@ module Sidekiq
171
207
  end
172
208
 
173
209
  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(", ")
210
+ return "Invalid job payload, args is nil" if args == nil
211
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" if !args.is_a?(Array)
212
+
213
+ begin
214
+ args.map do |arg|
215
+ h(truncate(to_display(arg), truncate_after_chars))
216
+ end.join(", ")
217
+ rescue
218
+ "Illegal job arguments: #{h args.inspect}"
219
+ end
177
220
  end
178
221
 
179
222
  def csrf_tag
@@ -250,6 +293,10 @@ module Sidekiq
250
293
  "Sidekiq v#{Sidekiq::VERSION}"
251
294
  end
252
295
 
296
+ def server_utc_time
297
+ Time.now.utc.strftime('%H:%M:%S UTC')
298
+ end
299
+
253
300
  def redis_connection_and_namespace
254
301
  @redis_connection_and_namespace ||= begin
255
302
  namespace_suffix = namespace == nil ? '' : "##{namespace}"
@@ -3,16 +3,16 @@ require 'rack'
3
3
 
4
4
  module Sidekiq
5
5
  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
6
+ GET = 'GET'
7
+ DELETE = 'DELETE'
8
+ POST = 'POST'
9
+ PUT = 'PUT'
10
+ PATCH = 'PATCH'
11
+ HEAD = 'HEAD'
12
12
 
13
- ROUTE_PARAMS = 'rack.route_params'.freeze
14
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
15
- PATH_INFO = 'PATH_INFO'.freeze
13
+ ROUTE_PARAMS = 'rack.route_params'
14
+ REQUEST_METHOD = 'REQUEST_METHOD'
15
+ PATH_INFO = 'PATH_INFO'
16
16
 
17
17
  def get(path, &block)
18
18
  route(GET, path, &block)
@@ -64,7 +64,7 @@ module Sidekiq
64
64
  class WebRoute
65
65
  attr_accessor :request_method, :pattern, :block, :name
66
66
 
67
- NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/.freeze
67
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/
68
68
 
69
69
  def initialize(request_method, pattern, block)
70
70
  @request_method = request_method
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require 'sidekiq/client'
3
- require 'sidekiq/core_ext'
4
3
 
5
4
  module Sidekiq
6
5
 
@@ -8,13 +7,13 @@ module Sidekiq
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
  #
@@ -28,16 +27,51 @@ module Sidekiq
28
27
  raise ArgumentError, "You cannot include Sidekiq::Worker in an ActiveJob: #{base.name}" if base.ancestors.any? {|c| c.name == 'ActiveJob::Base' }
29
28
 
30
29
  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
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
34
33
  end
35
34
 
36
35
  def logger
37
36
  Sidekiq.logger
38
37
  end
39
38
 
39
+ # This helper class encapsulates the set options for `set`, e.g.
40
+ #
41
+ # SomeWorker.set(queue: 'foo').perform_async(....)
42
+ #
43
+ class Setter
44
+ def initialize(klass, opts)
45
+ @klass = klass
46
+ @opts = opts
47
+ end
48
+
49
+ def set(options)
50
+ @opts.merge!(options)
51
+ self
52
+ end
53
+
54
+ def perform_async(*args)
55
+ @klass.client_push(@opts.merge('args' => args, 'class' => @klass))
56
+ end
57
+
58
+ # +interval+ must be a timestamp, numeric or something that acts
59
+ # numeric (like an activesupport time interval).
60
+ def perform_in(interval, *args)
61
+ int = interval.to_f
62
+ now = Time.now.to_f
63
+ ts = (int < 1_000_000_000 ? now + int : int)
64
+
65
+ payload = @opts.merge('class' => @klass, 'args' => args, 'at' => ts)
66
+ # Optimization to enqueue something now that is scheduled to go out now or in the past
67
+ payload.delete('at') if ts <= now
68
+ @klass.client_push(payload)
69
+ end
70
+ alias_method :perform_at, :perform_in
71
+ end
72
+
40
73
  module ClassMethods
74
+ ACCESSOR_MUTEX = Mutex.new
41
75
 
42
76
  def delay(*args)
43
77
  raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
@@ -52,8 +86,7 @@ module Sidekiq
52
86
  end
53
87
 
54
88
  def set(options)
55
- Thread.current[:sidekiq_worker_set] = options
56
- self
89
+ Setter.new(self, options)
57
90
  end
58
91
 
59
92
  def perform_async(*args)
@@ -70,7 +103,7 @@ module Sidekiq
70
103
  item = { 'class' => self, 'args' => args, 'at' => ts }
71
104
 
72
105
  # Optimization to enqueue something now that is scheduled to go out now or in the past
73
- item.delete('at'.freeze) if ts <= now
106
+ item.delete('at') if ts <= now
74
107
 
75
108
  client_push(item)
76
109
  end
@@ -90,7 +123,8 @@ module Sidekiq
90
123
  # In practice, any option is allowed. This is the main mechanism to configure the
91
124
  # options for a specific job.
92
125
  def sidekiq_options(opts={})
93
- self.sidekiq_options_hash = get_sidekiq_options.merge(opts.stringify_keys)
126
+ # stringify
127
+ self.sidekiq_options_hash = get_sidekiq_options.merge(Hash[opts.map{|k, v| [k.to_s, v]}])
94
128
  end
95
129
 
96
130
  def sidekiq_retry_in(&block)
@@ -107,13 +141,78 @@ module Sidekiq
107
141
 
108
142
  def client_push(item) # :nodoc:
109
143
  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
144
+ # stringify
145
+ item.keys.each do |key|
146
+ item[key.to_s] = item.delete(key)
147
+ end
148
+
149
+ Sidekiq::Client.new(pool).push(item)
150
+ end
151
+
152
+ def sidekiq_class_attribute(*attrs)
153
+ instance_reader = true
154
+ instance_writer = true
155
+
156
+ attrs.each do |name|
157
+ synchronized_getter = "__synchronized_#{name}"
158
+
159
+ singleton_class.instance_eval do
160
+ undef_method(name) if method_defined?(name) || private_method_defined?(name)
161
+ end
162
+
163
+ define_singleton_method(synchronized_getter) { nil }
164
+ singleton_class.class_eval do
165
+ private(synchronized_getter)
166
+ end
167
+
168
+ define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } }
169
+
170
+ ivar = "@#{name}"
171
+
172
+ singleton_class.instance_eval do
173
+ m = "#{name}="
174
+ undef_method(m) if method_defined?(m) || private_method_defined?(m)
175
+ end
176
+ define_singleton_method("#{name}=") do |val|
177
+ singleton_class.class_eval do
178
+ ACCESSOR_MUTEX.synchronize do
179
+ undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
180
+ define_method(synchronized_getter) { val }
181
+ end
182
+ end
183
+
184
+ if singleton_class?
185
+ class_eval do
186
+ undef_method(name) if method_defined?(name) || private_method_defined?(name)
187
+ define_method(name) do
188
+ if instance_variable_defined? ivar
189
+ instance_variable_get ivar
190
+ else
191
+ singleton_class.send name
192
+ end
193
+ end
194
+ end
195
+ end
196
+ val
197
+ end
198
+
199
+ if instance_reader
200
+ undef_method(name) if method_defined?(name) || private_method_defined?(name)
201
+ define_method(name) do
202
+ if instance_variable_defined?(ivar)
203
+ instance_variable_get ivar
204
+ else
205
+ self.class.public_send name
206
+ end
207
+ end
208
+ end
209
+
210
+ if instance_writer
211
+ m = "#{name}="
212
+ undef_method(m) if method_defined?(m) || private_method_defined?(m)
213
+ attr_writer name
214
+ end
115
215
  end
116
- Sidekiq::Client.new(pool).push(hash)
117
216
  end
118
217
 
119
218
  end