sidekiq 6.0.4

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 (122) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +82 -0
  3. data/.github/contributing.md +32 -0
  4. data/.github/issue_template.md +11 -0
  5. data/.gitignore +13 -0
  6. data/.standard.yml +20 -0
  7. data/3.0-Upgrade.md +70 -0
  8. data/4.0-Upgrade.md +53 -0
  9. data/5.0-Upgrade.md +56 -0
  10. data/6.0-Upgrade.md +72 -0
  11. data/COMM-LICENSE +97 -0
  12. data/Changes.md +1666 -0
  13. data/Ent-2.0-Upgrade.md +37 -0
  14. data/Ent-Changes.md +256 -0
  15. data/Gemfile +24 -0
  16. data/Gemfile.lock +199 -0
  17. data/LICENSE +9 -0
  18. data/Pro-2.0-Upgrade.md +138 -0
  19. data/Pro-3.0-Upgrade.md +44 -0
  20. data/Pro-4.0-Upgrade.md +35 -0
  21. data/Pro-5.0-Upgrade.md +25 -0
  22. data/Pro-Changes.md +776 -0
  23. data/README.md +97 -0
  24. data/Rakefile +10 -0
  25. data/bin/sidekiq +18 -0
  26. data/bin/sidekiqload +157 -0
  27. data/bin/sidekiqmon +8 -0
  28. data/code_of_conduct.md +50 -0
  29. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  30. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  31. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  32. data/lib/generators/sidekiq/worker_generator.rb +57 -0
  33. data/lib/sidekiq.rb +260 -0
  34. data/lib/sidekiq/api.rb +960 -0
  35. data/lib/sidekiq/cli.rb +387 -0
  36. data/lib/sidekiq/client.rb +256 -0
  37. data/lib/sidekiq/delay.rb +41 -0
  38. data/lib/sidekiq/exception_handler.rb +27 -0
  39. data/lib/sidekiq/extensions/action_mailer.rb +47 -0
  40. data/lib/sidekiq/extensions/active_record.rb +42 -0
  41. data/lib/sidekiq/extensions/class_methods.rb +42 -0
  42. data/lib/sidekiq/extensions/generic_proxy.rb +31 -0
  43. data/lib/sidekiq/fetch.rb +80 -0
  44. data/lib/sidekiq/job_logger.rb +63 -0
  45. data/lib/sidekiq/job_retry.rb +262 -0
  46. data/lib/sidekiq/launcher.rb +179 -0
  47. data/lib/sidekiq/logger.rb +165 -0
  48. data/lib/sidekiq/manager.rb +135 -0
  49. data/lib/sidekiq/middleware/chain.rb +160 -0
  50. data/lib/sidekiq/middleware/i18n.rb +40 -0
  51. data/lib/sidekiq/monitor.rb +133 -0
  52. data/lib/sidekiq/paginator.rb +47 -0
  53. data/lib/sidekiq/processor.rb +280 -0
  54. data/lib/sidekiq/rails.rb +52 -0
  55. data/lib/sidekiq/redis_connection.rb +141 -0
  56. data/lib/sidekiq/scheduled.rb +173 -0
  57. data/lib/sidekiq/testing.rb +344 -0
  58. data/lib/sidekiq/testing/inline.rb +30 -0
  59. data/lib/sidekiq/util.rb +67 -0
  60. data/lib/sidekiq/version.rb +5 -0
  61. data/lib/sidekiq/web.rb +205 -0
  62. data/lib/sidekiq/web/action.rb +93 -0
  63. data/lib/sidekiq/web/application.rb +359 -0
  64. data/lib/sidekiq/web/helpers.rb +336 -0
  65. data/lib/sidekiq/web/router.rb +103 -0
  66. data/lib/sidekiq/worker.rb +247 -0
  67. data/sidekiq.gemspec +21 -0
  68. data/web/assets/images/favicon.ico +0 -0
  69. data/web/assets/images/logo.png +0 -0
  70. data/web/assets/images/status.png +0 -0
  71. data/web/assets/javascripts/application.js +92 -0
  72. data/web/assets/javascripts/dashboard.js +296 -0
  73. data/web/assets/stylesheets/application-dark.css +125 -0
  74. data/web/assets/stylesheets/application-rtl.css +246 -0
  75. data/web/assets/stylesheets/application.css +1153 -0
  76. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  77. data/web/assets/stylesheets/bootstrap.css +5 -0
  78. data/web/locales/ar.yml +81 -0
  79. data/web/locales/cs.yml +78 -0
  80. data/web/locales/da.yml +68 -0
  81. data/web/locales/de.yml +81 -0
  82. data/web/locales/el.yml +68 -0
  83. data/web/locales/en.yml +83 -0
  84. data/web/locales/es.yml +70 -0
  85. data/web/locales/fa.yml +80 -0
  86. data/web/locales/fr.yml +78 -0
  87. data/web/locales/he.yml +79 -0
  88. data/web/locales/hi.yml +75 -0
  89. data/web/locales/it.yml +69 -0
  90. data/web/locales/ja.yml +81 -0
  91. data/web/locales/ko.yml +68 -0
  92. data/web/locales/nb.yml +77 -0
  93. data/web/locales/nl.yml +68 -0
  94. data/web/locales/pl.yml +59 -0
  95. data/web/locales/pt-br.yml +68 -0
  96. data/web/locales/pt.yml +67 -0
  97. data/web/locales/ru.yml +78 -0
  98. data/web/locales/sv.yml +68 -0
  99. data/web/locales/ta.yml +75 -0
  100. data/web/locales/uk.yml +76 -0
  101. data/web/locales/ur.yml +80 -0
  102. data/web/locales/zh-cn.yml +68 -0
  103. data/web/locales/zh-tw.yml +68 -0
  104. data/web/views/_footer.erb +20 -0
  105. data/web/views/_job_info.erb +89 -0
  106. data/web/views/_nav.erb +52 -0
  107. data/web/views/_paging.erb +23 -0
  108. data/web/views/_poll_link.erb +7 -0
  109. data/web/views/_status.erb +4 -0
  110. data/web/views/_summary.erb +40 -0
  111. data/web/views/busy.erb +101 -0
  112. data/web/views/dashboard.erb +75 -0
  113. data/web/views/dead.erb +34 -0
  114. data/web/views/layout.erb +41 -0
  115. data/web/views/morgue.erb +78 -0
  116. data/web/views/queue.erb +55 -0
  117. data/web/views/queues.erb +38 -0
  118. data/web/views/retries.erb +83 -0
  119. data/web/views/retry.erb +34 -0
  120. data/web/views/scheduled.erb +57 -0
  121. data/web/views/scheduled_job_info.erb +8 -0
  122. metadata +221 -0
@@ -0,0 +1,336 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "set"
5
+ require "yaml"
6
+ require "cgi"
7
+
8
+ module Sidekiq
9
+ # This is not a public API
10
+ module WebHelpers
11
+ def strings(lang)
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
21
+ end
22
+ end
23
+ end
24
+
25
+ def clear_caches
26
+ @strings = nil
27
+ @locale_files = nil
28
+ @available_locales = nil
29
+ end
30
+
31
+ def locale_files
32
+ @locale_files ||= settings.locales.flat_map { |path|
33
+ Dir["#{path}/*.yml"]
34
+ }
35
+ end
36
+
37
+ def available_locales
38
+ @available_locales ||= locale_files.map { |path| File.basename(path, ".yml") }.uniq
39
+ end
40
+
41
+ def find_locale_files(lang)
42
+ locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
43
+ end
44
+
45
+ # This is a hook for a Sidekiq Pro feature. Please don't touch.
46
+ def filtering(*)
47
+ end
48
+
49
+ # This view helper provide ability display you html code in
50
+ # to head of page. Example:
51
+ #
52
+ # <% add_to_head do %>
53
+ # <link rel="stylesheet" .../>
54
+ # <meta .../>
55
+ # <% end %>
56
+ #
57
+ def add_to_head
58
+ @head_html ||= []
59
+ @head_html << yield.dup if block_given?
60
+ end
61
+
62
+ def display_custom_head
63
+ @head_html.join if defined?(@head_html)
64
+ end
65
+
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
+ 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
102
+ def locale
103
+ @locale ||= begin
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"
115
+ end
116
+ end
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
+
125
+ # mperham/sidekiq#3243
126
+ def unfiltered?
127
+ yield unless env["PATH_INFO"].start_with?("/filter/")
128
+ end
129
+
130
+ def get_locale
131
+ strings(locale)
132
+ end
133
+
134
+ def t(msg, options = {})
135
+ string = get_locale[msg] || strings("en")[msg] || msg
136
+ if options.empty?
137
+ string
138
+ else
139
+ string % options
140
+ end
141
+ end
142
+
143
+ def sort_direction_label
144
+ params[:direction] == "asc" ? "&uarr;" : "&darr;"
145
+ end
146
+
147
+ def workers
148
+ @workers ||= Sidekiq::Workers.new
149
+ end
150
+
151
+ def processes
152
+ @processes ||= Sidekiq::ProcessSet.new
153
+ end
154
+
155
+ def stats
156
+ @stats ||= Sidekiq::Stats.new
157
+ end
158
+
159
+ def redis_connection
160
+ Sidekiq.redis do |conn|
161
+ c = conn.connection
162
+ "redis://#{c[:location]}/#{c[:db]}"
163
+ end
164
+ end
165
+
166
+ def namespace
167
+ @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
168
+ end
169
+
170
+ def redis_info
171
+ Sidekiq.redis_info
172
+ end
173
+
174
+ def root_path
175
+ "#{env["SCRIPT_NAME"]}/"
176
+ end
177
+
178
+ def current_path
179
+ @current_path ||= request.path_info.gsub(/^\//, "")
180
+ end
181
+
182
+ def current_status
183
+ workers.size == 0 ? "idle" : "active"
184
+ end
185
+
186
+ def relative_time(time)
187
+ stamp = time.getutc.iso8601
188
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
189
+ end
190
+
191
+ def job_params(job, score)
192
+ "#{score}-#{job["jid"]}"
193
+ end
194
+
195
+ def parse_params(params)
196
+ score, jid = params.split("-", 2)
197
+ [score.to_f, jid]
198
+ end
199
+
200
+ SAFE_QPARAMS = %w[page poll direction]
201
+
202
+ # Merge options with current params, filter safe params, and stringify to query string
203
+ def qparams(options)
204
+ # stringify
205
+ options.keys.each do |key|
206
+ options[key.to_s] = options.delete(key)
207
+ end
208
+
209
+ to_query_string(params.merge(options))
210
+ end
211
+
212
+ def to_query_string(params)
213
+ params.map { |key, value|
214
+ SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
215
+ }.compact.join("&")
216
+ end
217
+
218
+ def truncate(text, truncate_after_chars = 2000)
219
+ truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
220
+ end
221
+
222
+ def display_args(args, truncate_after_chars = 2000)
223
+ return "Invalid job payload, args is nil" if args.nil?
224
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
225
+
226
+ begin
227
+ args.map { |arg|
228
+ h(truncate(to_display(arg), truncate_after_chars))
229
+ }.join(", ")
230
+ rescue
231
+ "Illegal job arguments: #{h args.inspect}"
232
+ end
233
+ end
234
+
235
+ def csrf_tag
236
+ "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
237
+ end
238
+
239
+ def to_display(arg)
240
+ arg.inspect
241
+ rescue
242
+ begin
243
+ arg.to_s
244
+ rescue => ex
245
+ "Cannot display argument: [#{ex.class.name}] #{ex.message}"
246
+ end
247
+ end
248
+
249
+ RETRY_JOB_KEYS = Set.new(%w[
250
+ queue class args retry_count retried_at failed_at
251
+ jid error_message error_class backtrace
252
+ error_backtrace enqueued_at retry wrapped
253
+ created_at tags
254
+ ])
255
+
256
+ def retry_extra_items(retry_job)
257
+ @retry_extra_items ||= {}.tap do |extra|
258
+ retry_job.item.each do |key, value|
259
+ extra[key] = value unless RETRY_JOB_KEYS.include?(key)
260
+ end
261
+ end
262
+ end
263
+
264
+ def number_with_delimiter(number)
265
+ begin
266
+ Float(number)
267
+ rescue ArgumentError, TypeError
268
+ return number
269
+ end
270
+
271
+ options = {delimiter: ",", separator: "."}
272
+ parts = number.to_s.to_str.split(".")
273
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
274
+ parts.join(options[:separator])
275
+ end
276
+
277
+ def h(text)
278
+ ::Rack::Utils.escape_html(text)
279
+ rescue ArgumentError => e
280
+ raise unless e.message.eql?("invalid byte sequence in UTF-8")
281
+ text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
282
+ retry
283
+ end
284
+
285
+ # Any paginated list that performs an action needs to redirect
286
+ # back to the proper page after performing that action.
287
+ def redirect_with_query(url)
288
+ r = request.referer
289
+ if r && r =~ /\?/
290
+ ref = URI(r)
291
+ redirect("#{url}?#{ref.query}")
292
+ else
293
+ redirect url
294
+ end
295
+ end
296
+
297
+ def environment_title_prefix
298
+ environment = Sidekiq.options[:environment] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
299
+
300
+ "[#{environment.upcase}] " unless environment == "production"
301
+ end
302
+
303
+ def product_version
304
+ "Sidekiq v#{Sidekiq::VERSION}"
305
+ end
306
+
307
+ def server_utc_time
308
+ Time.now.utc.strftime("%H:%M:%S UTC")
309
+ end
310
+
311
+ def redis_connection_and_namespace
312
+ @redis_connection_and_namespace ||= begin
313
+ namespace_suffix = namespace.nil? ? "" : "##{namespace}"
314
+ "#{redis_connection}#{namespace_suffix}"
315
+ end
316
+ end
317
+
318
+ def retry_or_delete_or_kill(job, params)
319
+ if params["retry"]
320
+ job.retry
321
+ elsif params["delete"]
322
+ job.delete
323
+ elsif params["kill"]
324
+ job.kill
325
+ end
326
+ end
327
+
328
+ def delete_or_add_queue(job, params)
329
+ if params["delete"]
330
+ job.delete
331
+ elsif params["add_to_queue"]
332
+ job.add_to_queue
333
+ end
334
+ end
335
+ end
336
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+
5
+ module Sidekiq
6
+ module WebRouter
7
+ GET = "GET"
8
+ DELETE = "DELETE"
9
+ POST = "POST"
10
+ PUT = "PUT"
11
+ PATCH = "PATCH"
12
+ HEAD = "HEAD"
13
+
14
+ ROUTE_PARAMS = "rack.route_params"
15
+ REQUEST_METHOD = "REQUEST_METHOD"
16
+ PATH_INFO = "PATH_INFO"
17
+
18
+ def get(path, &block)
19
+ route(GET, path, &block)
20
+ end
21
+
22
+ def post(path, &block)
23
+ route(POST, path, &block)
24
+ end
25
+
26
+ def put(path, &block)
27
+ route(PUT, path, &block)
28
+ end
29
+
30
+ def patch(path, &block)
31
+ route(PATCH, path, &block)
32
+ end
33
+
34
+ def delete(path, &block)
35
+ route(DELETE, path, &block)
36
+ end
37
+
38
+ def route(method, path, &block)
39
+ @routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
40
+
41
+ @routes[method] << WebRoute.new(method, path, block)
42
+ @routes[HEAD] << WebRoute.new(method, path, block) if method == GET
43
+ end
44
+
45
+ def match(env)
46
+ request_method = env[REQUEST_METHOD]
47
+ path_info = ::Rack::Utils.unescape env[PATH_INFO]
48
+
49
+ # There are servers which send an empty string when requesting the root.
50
+ # These servers should be ashamed of themselves.
51
+ path_info = "/" if path_info == ""
52
+
53
+ @routes[request_method].each do |route|
54
+ params = route.match(request_method, path_info)
55
+ if params
56
+ env[ROUTE_PARAMS] = params
57
+
58
+ return WebAction.new(env, route.block)
59
+ end
60
+ end
61
+
62
+ nil
63
+ end
64
+ end
65
+
66
+ class WebRoute
67
+ attr_accessor :request_method, :pattern, :block, :name
68
+
69
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/
70
+
71
+ def initialize(request_method, pattern, block)
72
+ @request_method = request_method
73
+ @pattern = pattern
74
+ @block = block
75
+ end
76
+
77
+ def matcher
78
+ @matcher ||= compile
79
+ end
80
+
81
+ def compile
82
+ if pattern.match?(NAMED_SEGMENTS_PATTERN)
83
+ p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
84
+
85
+ Regexp.new("\\A#{p}\\Z")
86
+ else
87
+ pattern
88
+ end
89
+ end
90
+
91
+ def match(request_method, path)
92
+ case matcher
93
+ when String
94
+ {} if path == matcher
95
+ else
96
+ path_match = path.match(matcher)
97
+ if path_match
98
+ Hash[path_match.names.map(&:to_sym).zip(path_match.captures)]
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/client"
4
+
5
+ module Sidekiq
6
+ ##
7
+ # Include this module in your worker class and you can easily create
8
+ # asynchronous jobs:
9
+ #
10
+ # class HardWorker
11
+ # include Sidekiq::Worker
12
+ #
13
+ # def perform(*args)
14
+ # # do some work
15
+ # end
16
+ # end
17
+ #
18
+ # Then in your Rails app, you can do this:
19
+ #
20
+ # HardWorker.perform_async(1, 2, 3)
21
+ #
22
+ # Note that perform_async is a class method, perform is an instance method.
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 = 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] }])
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
+
135
+ attr_accessor :jid
136
+
137
+ def self.included(base)
138
+ raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
139
+
140
+ base.include(Options)
141
+ base.extend(ClassMethods)
142
+ end
143
+
144
+ def logger
145
+ Sidekiq.logger
146
+ end
147
+
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
181
+
182
+ module ClassMethods
183
+ def delay(*args)
184
+ raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
185
+ end
186
+
187
+ def delay_for(*args)
188
+ raise ArgumentError, "Do not call .delay_for on a Sidekiq::Worker class, call .perform_in"
189
+ end
190
+
191
+ def delay_until(*args)
192
+ raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
193
+ end
194
+
195
+ def set(options)
196
+ Setter.new(self, options)
197
+ end
198
+
199
+ def perform_async(*args)
200
+ client_push("class" => self, "args" => args)
201
+ end
202
+
203
+ # +interval+ must be a timestamp, numeric or something that acts
204
+ # numeric (like an activesupport time interval).
205
+ def perform_in(interval, *args)
206
+ int = interval.to_f
207
+ now = Time.now.to_f
208
+ ts = (int < 1_000_000_000 ? now + int : int)
209
+
210
+ item = {"class" => self, "args" => args}
211
+
212
+ # Optimization to enqueue something now that is scheduled to go out now or in the past
213
+ item["at"] = ts if ts > now
214
+
215
+ client_push(item)
216
+ end
217
+ alias_method :perform_at, :perform_in
218
+
219
+ ##
220
+ # Allows customization for this type of Worker.
221
+ # Legal options:
222
+ #
223
+ # queue - use a named queue for this Worker, default 'default'
224
+ # retry - enable the RetryJobs middleware for this Worker, *true* to use the default
225
+ # or *Integer* count
226
+ # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
227
+ # can be true, false or an integer number of lines to save, default *false*
228
+ # pool - use the given Redis connection pool to push this type of job to a given shard.
229
+ #
230
+ # In practice, any option is allowed. This is the main mechanism to configure the
231
+ # options for a specific job.
232
+ def sidekiq_options(opts = {})
233
+ super
234
+ end
235
+
236
+ def client_push(item) # :nodoc:
237
+ 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
242
+
243
+ Sidekiq::Client.new(pool).push(item)
244
+ end
245
+ end
246
+ end
247
+ end