sidekiq 6.0.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 (121) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +61 -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 +70 -0
  11. data/COMM-LICENSE +97 -0
  12. data/Changes.md +1570 -0
  13. data/Ent-2.0-Upgrade.md +37 -0
  14. data/Ent-Changes.md +250 -0
  15. data/Gemfile +24 -0
  16. data/Gemfile.lock +196 -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 +768 -0
  23. data/README.md +95 -0
  24. data/Rakefile +10 -0
  25. data/bin/sidekiq +18 -0
  26. data/bin/sidekiqload +153 -0
  27. data/bin/sidekiqmon +9 -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 +47 -0
  33. data/lib/sidekiq.rb +248 -0
  34. data/lib/sidekiq/api.rb +927 -0
  35. data/lib/sidekiq/cli.rb +380 -0
  36. data/lib/sidekiq/client.rb +242 -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 +55 -0
  45. data/lib/sidekiq/job_retry.rb +249 -0
  46. data/lib/sidekiq/launcher.rb +181 -0
  47. data/lib/sidekiq/logger.rb +69 -0
  48. data/lib/sidekiq/manager.rb +135 -0
  49. data/lib/sidekiq/middleware/chain.rb +151 -0
  50. data/lib/sidekiq/middleware/i18n.rb +40 -0
  51. data/lib/sidekiq/monitor.rb +148 -0
  52. data/lib/sidekiq/paginator.rb +42 -0
  53. data/lib/sidekiq/processor.rb +282 -0
  54. data/lib/sidekiq/rails.rb +52 -0
  55. data/lib/sidekiq/redis_connection.rb +138 -0
  56. data/lib/sidekiq/scheduled.rb +172 -0
  57. data/lib/sidekiq/testing.rb +332 -0
  58. data/lib/sidekiq/testing/inline.rb +30 -0
  59. data/lib/sidekiq/util.rb +69 -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 +356 -0
  64. data/lib/sidekiq/web/helpers.rb +324 -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-rtl.css +246 -0
  74. data/web/assets/stylesheets/application.css +1144 -0
  75. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  76. data/web/assets/stylesheets/bootstrap.css +5 -0
  77. data/web/locales/ar.yml +81 -0
  78. data/web/locales/cs.yml +78 -0
  79. data/web/locales/da.yml +68 -0
  80. data/web/locales/de.yml +69 -0
  81. data/web/locales/el.yml +68 -0
  82. data/web/locales/en.yml +81 -0
  83. data/web/locales/es.yml +70 -0
  84. data/web/locales/fa.yml +80 -0
  85. data/web/locales/fr.yml +78 -0
  86. data/web/locales/he.yml +79 -0
  87. data/web/locales/hi.yml +75 -0
  88. data/web/locales/it.yml +69 -0
  89. data/web/locales/ja.yml +81 -0
  90. data/web/locales/ko.yml +68 -0
  91. data/web/locales/nb.yml +77 -0
  92. data/web/locales/nl.yml +68 -0
  93. data/web/locales/pl.yml +59 -0
  94. data/web/locales/pt-br.yml +68 -0
  95. data/web/locales/pt.yml +67 -0
  96. data/web/locales/ru.yml +78 -0
  97. data/web/locales/sv.yml +68 -0
  98. data/web/locales/ta.yml +75 -0
  99. data/web/locales/uk.yml +76 -0
  100. data/web/locales/ur.yml +80 -0
  101. data/web/locales/zh-cn.yml +68 -0
  102. data/web/locales/zh-tw.yml +68 -0
  103. data/web/views/_footer.erb +20 -0
  104. data/web/views/_job_info.erb +88 -0
  105. data/web/views/_nav.erb +52 -0
  106. data/web/views/_paging.erb +23 -0
  107. data/web/views/_poll_link.erb +7 -0
  108. data/web/views/_status.erb +4 -0
  109. data/web/views/_summary.erb +40 -0
  110. data/web/views/busy.erb +98 -0
  111. data/web/views/dashboard.erb +75 -0
  112. data/web/views/dead.erb +34 -0
  113. data/web/views/layout.erb +40 -0
  114. data/web/views/morgue.erb +75 -0
  115. data/web/views/queue.erb +46 -0
  116. data/web/views/queues.erb +30 -0
  117. data/web/views/retries.erb +80 -0
  118. data/web/views/retry.erb +34 -0
  119. data/web/views/scheduled.erb +54 -0
  120. data/web/views/scheduled_job_info.erb +8 -0
  121. metadata +220 -0
@@ -0,0 +1,324 @@
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
+ root_path + current_path
69
+ else
70
+ ""
71
+ end
72
+ end
73
+
74
+ def text_direction
75
+ get_locale["TextDirection"] || "ltr"
76
+ end
77
+
78
+ def rtl?
79
+ text_direction == "rtl"
80
+ end
81
+
82
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
83
+ def user_preferred_languages
84
+ languages = env["HTTP_ACCEPT_LANGUAGE"]
85
+ languages.to_s.downcase.gsub(/\s+/, "").split(",").map { |language|
86
+ locale, quality = language.split(";q=", 2)
87
+ locale = nil if locale == "*" # Ignore wildcards
88
+ quality = quality ? quality.to_f : 1.0
89
+ [locale, quality]
90
+ }.sort { |(_, left), (_, right)|
91
+ right <=> left
92
+ }.map(&:first).compact
93
+ end
94
+
95
+ # 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"
96
+ # this method will try to best match the available locales to the user's preferred languages.
97
+ #
98
+ # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
99
+ def locale
100
+ @locale ||= begin
101
+ matched_locale = user_preferred_languages.map { |preferred|
102
+ preferred_language = preferred.split("-", 2).first
103
+
104
+ lang_group = available_locales.select { |available|
105
+ preferred_language == available.split("-", 2).first
106
+ }
107
+
108
+ lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
109
+ }.compact.first
110
+
111
+ matched_locale || "en"
112
+ end
113
+ end
114
+
115
+ # mperham/sidekiq#3243
116
+ def unfiltered?
117
+ yield unless env["PATH_INFO"].start_with?("/filter/")
118
+ end
119
+
120
+ def get_locale
121
+ strings(locale)
122
+ end
123
+
124
+ def t(msg, options = {})
125
+ string = get_locale[msg] || strings("en")[msg] || msg
126
+ if options.empty?
127
+ string
128
+ else
129
+ string % options
130
+ end
131
+ end
132
+
133
+ def workers
134
+ @workers ||= Sidekiq::Workers.new
135
+ end
136
+
137
+ def processes
138
+ @processes ||= Sidekiq::ProcessSet.new
139
+ end
140
+
141
+ def stats
142
+ @stats ||= Sidekiq::Stats.new
143
+ end
144
+
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
+ def redis_connection
152
+ Sidekiq.redis do |conn|
153
+ c = conn.connection
154
+ "redis://#{c[:location]}/#{c[:db]}"
155
+ end
156
+ end
157
+
158
+ def namespace
159
+ @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
160
+ end
161
+
162
+ def redis_info
163
+ Sidekiq.redis_info
164
+ end
165
+
166
+ def root_path
167
+ "#{env["SCRIPT_NAME"]}/"
168
+ end
169
+
170
+ def current_path
171
+ @current_path ||= request.path_info.gsub(/^\//, "")
172
+ end
173
+
174
+ def current_status
175
+ workers.size == 0 ? "idle" : "active"
176
+ end
177
+
178
+ def relative_time(time)
179
+ stamp = time.getutc.iso8601
180
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
181
+ end
182
+
183
+ def job_params(job, score)
184
+ "#{score}-#{job["jid"]}"
185
+ end
186
+
187
+ def parse_params(params)
188
+ score, jid = params.split("-", 2)
189
+ [score.to_f, jid]
190
+ end
191
+
192
+ SAFE_QPARAMS = %w[page poll]
193
+
194
+ # Merge options with current params, filter safe params, and stringify to query string
195
+ def qparams(options)
196
+ # stringify
197
+ options.keys.each do |key|
198
+ options[key.to_s] = options.delete(key)
199
+ end
200
+
201
+ params.merge(options).map { |key, value|
202
+ SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
203
+ }.compact.join("&")
204
+ end
205
+
206
+ def truncate(text, truncate_after_chars = 2000)
207
+ truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
208
+ end
209
+
210
+ def display_args(args, truncate_after_chars = 2000)
211
+ return "Invalid job payload, args is nil" if args.nil?
212
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
213
+
214
+ begin
215
+ args.map { |arg|
216
+ h(truncate(to_display(arg), truncate_after_chars))
217
+ }.join(", ")
218
+ rescue
219
+ "Illegal job arguments: #{h args.inspect}"
220
+ end
221
+ end
222
+
223
+ def csrf_tag
224
+ "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
225
+ end
226
+
227
+ def to_display(arg)
228
+ arg.inspect
229
+ rescue
230
+ begin
231
+ arg.to_s
232
+ rescue => ex
233
+ "Cannot display argument: [#{ex.class.name}] #{ex.message}"
234
+ end
235
+ end
236
+
237
+ RETRY_JOB_KEYS = Set.new(%w[
238
+ queue class args retry_count retried_at failed_at
239
+ jid error_message error_class backtrace
240
+ error_backtrace enqueued_at retry wrapped
241
+ created_at
242
+ ])
243
+
244
+ def retry_extra_items(retry_job)
245
+ @retry_extra_items ||= {}.tap do |extra|
246
+ retry_job.item.each do |key, value|
247
+ extra[key] = value unless RETRY_JOB_KEYS.include?(key)
248
+ end
249
+ end
250
+ end
251
+
252
+ def number_with_delimiter(number)
253
+ begin
254
+ Float(number)
255
+ rescue ArgumentError, TypeError
256
+ return number
257
+ end
258
+
259
+ options = {delimiter: ",", separator: "."}
260
+ parts = number.to_s.to_str.split(".")
261
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
262
+ parts.join(options[:separator])
263
+ end
264
+
265
+ def h(text)
266
+ ::Rack::Utils.escape_html(text)
267
+ rescue ArgumentError => e
268
+ raise unless e.message.eql?("invalid byte sequence in UTF-8")
269
+ text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
270
+ retry
271
+ end
272
+
273
+ # Any paginated list that performs an action needs to redirect
274
+ # back to the proper page after performing that action.
275
+ def redirect_with_query(url)
276
+ r = request.referer
277
+ if r && r =~ /\?/
278
+ ref = URI(r)
279
+ redirect("#{url}?#{ref.query}")
280
+ else
281
+ redirect url
282
+ end
283
+ end
284
+
285
+ def environment_title_prefix
286
+ environment = Sidekiq.options[:environment] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
287
+
288
+ "[#{environment.upcase}] " unless environment == "production"
289
+ end
290
+
291
+ def product_version
292
+ "Sidekiq v#{Sidekiq::VERSION}"
293
+ end
294
+
295
+ def server_utc_time
296
+ Time.now.utc.strftime("%H:%M:%S UTC")
297
+ end
298
+
299
+ def redis_connection_and_namespace
300
+ @redis_connection_and_namespace ||= begin
301
+ namespace_suffix = namespace.nil? ? "" : "##{namespace}"
302
+ "#{redis_connection}#{namespace_suffix}"
303
+ end
304
+ end
305
+
306
+ def retry_or_delete_or_kill(job, params)
307
+ if params["retry"]
308
+ job.retry
309
+ elsif params["delete"]
310
+ job.delete
311
+ elsif params["kill"]
312
+ job.kill
313
+ end
314
+ end
315
+
316
+ def delete_or_add_queue(job, params)
317
+ if params["delete"]
318
+ job.delete
319
+ elsif params["add_to_queue"]
320
+ job.add_to_queue
321
+ end
322
+ end
323
+ end
324
+ 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, "at" => ts)
175
+ # 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)
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, "at" => ts}
211
+
212
+ # Optimization to enqueue something now that is scheduled to go out now or in the past
213
+ item.delete("at") 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