sidekiq 6.5.10 → 8.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +568 -18
  3. data/README.md +66 -38
  4. data/bin/kiq +17 -0
  5. data/bin/lint-herb +13 -0
  6. data/bin/multi_queue_bench +271 -0
  7. data/bin/sidekiq +3 -8
  8. data/bin/sidekiqload +215 -118
  9. data/bin/sidekiqmon +3 -0
  10. data/bin/webload +69 -0
  11. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +124 -0
  12. data/lib/generators/sidekiq/job_generator.rb +17 -3
  13. data/lib/generators/sidekiq/templates/job.rb.erb +1 -1
  14. data/lib/sidekiq/api.rb +526 -218
  15. data/lib/sidekiq/capsule.rb +135 -0
  16. data/lib/sidekiq/cli.rb +88 -96
  17. data/lib/sidekiq/client.rb +114 -47
  18. data/lib/sidekiq/component.rb +71 -4
  19. data/lib/sidekiq/config.rb +316 -0
  20. data/lib/sidekiq/deploy.rb +64 -0
  21. data/lib/sidekiq/embedded.rb +64 -0
  22. data/lib/sidekiq/fetch.rb +12 -14
  23. data/lib/sidekiq/iterable_job.rb +56 -0
  24. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  25. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  26. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  27. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  28. data/lib/sidekiq/job/iterable.rb +322 -0
  29. data/lib/sidekiq/job.rb +397 -10
  30. data/lib/sidekiq/job_logger.rb +14 -11
  31. data/lib/sidekiq/job_retry.rb +78 -28
  32. data/lib/sidekiq/job_util.rb +58 -16
  33. data/lib/sidekiq/launcher.rb +86 -70
  34. data/lib/sidekiq/loader.rb +57 -0
  35. data/lib/sidekiq/logger.rb +25 -94
  36. data/lib/sidekiq/manager.rb +11 -13
  37. data/lib/sidekiq/metrics/query.rb +84 -48
  38. data/lib/sidekiq/metrics/shared.rb +29 -14
  39. data/lib/sidekiq/metrics/tracking.rb +49 -30
  40. data/lib/sidekiq/middleware/chain.rb +19 -18
  41. data/lib/sidekiq/middleware/current_attributes.rb +78 -21
  42. data/lib/sidekiq/middleware/i18n.rb +2 -0
  43. data/lib/sidekiq/middleware/modules.rb +2 -0
  44. data/lib/sidekiq/monitor.rb +15 -5
  45. data/lib/sidekiq/paginator.rb +26 -4
  46. data/lib/sidekiq/processor.rb +78 -65
  47. data/lib/sidekiq/profiler.rb +73 -0
  48. data/lib/sidekiq/rails.rb +45 -60
  49. data/lib/sidekiq/redis_client_adapter.rb +35 -72
  50. data/lib/sidekiq/redis_connection.rb +54 -114
  51. data/lib/sidekiq/ring_buffer.rb +3 -0
  52. data/lib/sidekiq/scheduled.rb +28 -30
  53. data/lib/sidekiq/systemd.rb +2 -0
  54. data/lib/sidekiq/test_api.rb +331 -0
  55. data/lib/sidekiq/testing/inline.rb +2 -30
  56. data/lib/sidekiq/testing.rb +2 -342
  57. data/lib/sidekiq/transaction_aware_client.rb +23 -9
  58. data/lib/sidekiq/tui/controls.rb +53 -0
  59. data/lib/sidekiq/tui/filtering.rb +53 -0
  60. data/lib/sidekiq/tui/tabs/base_tab.rb +230 -0
  61. data/lib/sidekiq/tui/tabs/busy.rb +104 -0
  62. data/lib/sidekiq/tui/tabs/dead.rb +14 -0
  63. data/lib/sidekiq/tui/tabs/home.rb +115 -0
  64. data/lib/sidekiq/tui/tabs/metrics.rb +114 -0
  65. data/lib/sidekiq/tui/tabs/queues.rb +83 -0
  66. data/lib/sidekiq/tui/tabs/retries.rb +14 -0
  67. data/lib/sidekiq/tui/tabs/scheduled.rb +14 -0
  68. data/lib/sidekiq/tui/tabs/set_tab.rb +91 -0
  69. data/lib/sidekiq/tui/tabs.rb +15 -0
  70. data/lib/sidekiq/tui.rb +390 -0
  71. data/lib/sidekiq/version.rb +6 -1
  72. data/lib/sidekiq/web/action.rb +149 -64
  73. data/lib/sidekiq/web/application.rb +379 -281
  74. data/lib/sidekiq/web/config.rb +117 -0
  75. data/lib/sidekiq/web/helpers.rb +190 -71
  76. data/lib/sidekiq/web/router.rb +62 -75
  77. data/lib/sidekiq/web.rb +68 -101
  78. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  79. data/lib/sidekiq.rb +85 -269
  80. data/sidekiq.gemspec +15 -11
  81. data/web/assets/images/logo.png +0 -0
  82. data/web/assets/images/status.png +0 -0
  83. data/web/assets/javascripts/application.js +84 -24
  84. data/web/assets/javascripts/base-charts.js +120 -0
  85. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  86. data/web/assets/javascripts/dashboard-charts.js +194 -0
  87. data/web/assets/javascripts/dashboard.js +18 -234
  88. data/web/assets/javascripts/metrics.js +138 -120
  89. data/web/assets/stylesheets/style.css +776 -0
  90. data/web/locales/ar.yml +72 -70
  91. data/web/locales/cs.yml +64 -62
  92. data/web/locales/da.yml +62 -53
  93. data/web/locales/de.yml +67 -65
  94. data/web/locales/el.yml +4 -7
  95. data/web/locales/en.yml +89 -71
  96. data/web/locales/es.yml +91 -68
  97. data/web/locales/fa.yml +67 -65
  98. data/web/locales/fr.yml +82 -67
  99. data/web/locales/gd.yml +110 -0
  100. data/web/locales/he.yml +67 -64
  101. data/web/locales/hi.yml +95 -59
  102. data/web/locales/it.yml +94 -54
  103. data/web/locales/ja.yml +69 -70
  104. data/web/locales/ko.yml +54 -52
  105. data/web/locales/lt.yml +68 -66
  106. data/web/locales/nb.yml +63 -61
  107. data/web/locales/nl.yml +54 -52
  108. data/web/locales/pl.yml +47 -45
  109. data/web/locales/{pt-br.yml → pt-BR.yml} +81 -70
  110. data/web/locales/pt.yml +53 -51
  111. data/web/locales/ru.yml +69 -66
  112. data/web/locales/sv.yml +55 -53
  113. data/web/locales/ta.yml +100 -64
  114. data/web/locales/tr.yml +102 -0
  115. data/web/locales/uk.yml +87 -61
  116. data/web/locales/ur.yml +66 -64
  117. data/web/locales/vi.yml +69 -67
  118. data/web/locales/zh-CN.yml +107 -0
  119. data/web/locales/{zh-tw.yml → zh-TW.yml} +13 -3
  120. data/web/views/_footer.html.erb +32 -0
  121. data/web/views/_job_info.html.erb +115 -0
  122. data/web/views/_metrics_period_select.html.erb +15 -0
  123. data/web/views/_nav.html.erb +45 -0
  124. data/web/views/_paging.html.erb +26 -0
  125. data/web/views/_poll_link.html.erb +4 -0
  126. data/web/views/_summary.html.erb +40 -0
  127. data/web/views/busy.html.erb +155 -0
  128. data/web/views/dashboard.html.erb +104 -0
  129. data/web/views/dead.html.erb +38 -0
  130. data/web/views/filtering.html.erb +6 -0
  131. data/web/views/layout.html.erb +26 -0
  132. data/web/views/metrics.html.erb +85 -0
  133. data/web/views/metrics_for_job.html.erb +58 -0
  134. data/web/views/morgue.html.erb +69 -0
  135. data/web/views/profiles.html.erb +43 -0
  136. data/web/views/queue.html.erb +57 -0
  137. data/web/views/queues.html.erb +46 -0
  138. data/web/views/retries.html.erb +77 -0
  139. data/web/views/retry.html.erb +39 -0
  140. data/web/views/scheduled.html.erb +64 -0
  141. data/web/views/{scheduled_job_info.erb → scheduled_job_info.html.erb} +3 -3
  142. metadata +113 -70
  143. data/lib/sidekiq/delay.rb +0 -43
  144. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  145. data/lib/sidekiq/extensions/active_record.rb +0 -43
  146. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  147. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  148. data/lib/sidekiq/metrics/deploy.rb +0 -47
  149. data/lib/sidekiq/web/csrf_protection.rb +0 -180
  150. data/lib/sidekiq/worker.rb +0 -370
  151. data/web/assets/javascripts/graph.js +0 -16
  152. data/web/assets/stylesheets/application-dark.css +0 -143
  153. data/web/assets/stylesheets/application-rtl.css +0 -242
  154. data/web/assets/stylesheets/application.css +0 -999
  155. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  156. data/web/assets/stylesheets/bootstrap.css +0 -5
  157. data/web/locales/zh-cn.yml +0 -93
  158. data/web/views/_footer.erb +0 -20
  159. data/web/views/_job_info.erb +0 -89
  160. data/web/views/_nav.erb +0 -52
  161. data/web/views/_paging.erb +0 -23
  162. data/web/views/_poll_link.erb +0 -4
  163. data/web/views/_status.erb +0 -4
  164. data/web/views/_summary.erb +0 -40
  165. data/web/views/busy.erb +0 -137
  166. data/web/views/dashboard.erb +0 -84
  167. data/web/views/dead.erb +0 -34
  168. data/web/views/layout.erb +0 -42
  169. data/web/views/metrics.erb +0 -69
  170. data/web/views/metrics_for_job.erb +0 -87
  171. data/web/views/morgue.erb +0 -78
  172. data/web/views/queue.erb +0 -59
  173. data/web/views/queues.erb +0 -38
  174. data/web/views/retries.erb +0 -83
  175. data/web/views/retry.erb +0 -34
  176. data/web/views/scheduled.erb +0 -57
  177. /data/{LICENSE → LICENSE.txt} +0 -0
@@ -1,180 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # this file originally based on authenticity_token.rb from the sinatra/rack-protection project
4
- #
5
- # The MIT License (MIT)
6
- #
7
- # Copyright (c) 2011-2017 Konstantin Haase
8
- # Copyright (c) 2015-2017 Zachary Scott
9
- #
10
- # Permission is hereby granted, free of charge, to any person obtaining
11
- # a copy of this software and associated documentation files (the
12
- # 'Software'), to deal in the Software without restriction, including
13
- # without limitation the rights to use, copy, modify, merge, publish,
14
- # distribute, sublicense, and/or sell copies of the Software, and to
15
- # permit persons to whom the Software is furnished to do so, subject to
16
- # the following conditions:
17
- #
18
- # The above copyright notice and this permission notice shall be
19
- # included in all copies or substantial portions of the Software.
20
- #
21
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
- # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
- # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
- # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
- # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
-
29
- require "securerandom"
30
- require "base64"
31
- require "rack/request"
32
-
33
- module Sidekiq
34
- class Web
35
- class CsrfProtection
36
- def initialize(app, options = nil)
37
- @app = app
38
- end
39
-
40
- def call(env)
41
- accept?(env) ? admit(env) : deny(env)
42
- end
43
-
44
- private
45
-
46
- def admit(env)
47
- # On each successful request, we create a fresh masked token
48
- # which will be used in any forms rendered for this request.
49
- s = session(env)
50
- s[:csrf] ||= SecureRandom.base64(TOKEN_LENGTH)
51
- env[:csrf_token] = mask_token(s[:csrf])
52
- @app.call(env)
53
- end
54
-
55
- def safe?(env)
56
- %w[GET HEAD OPTIONS TRACE].include? env["REQUEST_METHOD"]
57
- end
58
-
59
- def logger(env)
60
- @logger ||= (env["rack.logger"] || ::Logger.new(env["rack.errors"]))
61
- end
62
-
63
- def deny(env)
64
- logger(env).warn "attack prevented by #{self.class}"
65
- [403, {"Content-Type" => "text/plain"}, ["Forbidden"]]
66
- end
67
-
68
- def session(env)
69
- env["rack.session"] || fail(<<~EOM)
70
- Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app,
71
- make sure you mount Sidekiq::Web *inside* your application routes:
72
-
73
-
74
- Rails.application.routes.draw do
75
- mount Sidekiq::Web => "/sidekiq"
76
- ....
77
- end
78
-
79
-
80
- If this is a Rails app in API mode, you need to enable sessions.
81
-
82
- https://guides.rubyonrails.org/api_app.html#using-session-middlewares
83
-
84
- If this is a bare Rack app, use a session middleware before Sidekiq::Web:
85
-
86
- # first, use IRB to create a shared secret key for sessions and commit it
87
- require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) }
88
-
89
- # now use the secret with a session cookie middleware
90
- use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400
91
- run Sidekiq::Web
92
-
93
- EOM
94
- end
95
-
96
- def accept?(env)
97
- return true if safe?(env)
98
-
99
- giventoken = ::Rack::Request.new(env).params["authenticity_token"]
100
- valid_token?(env, giventoken)
101
- end
102
-
103
- TOKEN_LENGTH = 32
104
-
105
- # Checks that the token given to us as a parameter matches
106
- # the token stored in the session.
107
- def valid_token?(env, giventoken)
108
- return false if giventoken.nil? || giventoken.empty?
109
-
110
- begin
111
- token = decode_token(giventoken)
112
- rescue ArgumentError # client input is invalid
113
- return false
114
- end
115
-
116
- sess = session(env)
117
- localtoken = sess[:csrf]
118
-
119
- # Checks that Rack::Session::Cookie actualy contains the csrf toekn
120
- return false if localtoken.nil?
121
-
122
- # Rotate the session token after every use
123
- sess[:csrf] = SecureRandom.base64(TOKEN_LENGTH)
124
-
125
- # See if it's actually a masked token or not. We should be able
126
- # to handle any unmasked tokens that we've issued without error.
127
-
128
- if unmasked_token?(token)
129
- compare_with_real_token token, localtoken
130
- elsif masked_token?(token)
131
- unmasked = unmask_token(token)
132
- compare_with_real_token unmasked, localtoken
133
- else
134
- false # Token is malformed
135
- end
136
- end
137
-
138
- # Creates a masked version of the authenticity token that varies
139
- # on each request. The masking is used to mitigate SSL attacks
140
- # like BREACH.
141
- def mask_token(token)
142
- token = decode_token(token)
143
- one_time_pad = SecureRandom.random_bytes(token.length)
144
- encrypted_token = xor_byte_strings(one_time_pad, token)
145
- masked_token = one_time_pad + encrypted_token
146
- Base64.urlsafe_encode64(masked_token)
147
- end
148
-
149
- # Essentially the inverse of +mask_token+.
150
- def unmask_token(masked_token)
151
- # Split the token into the one-time pad and the encrypted
152
- # value and decrypt it
153
- token_length = masked_token.length / 2
154
- one_time_pad = masked_token[0...token_length]
155
- encrypted_token = masked_token[token_length..-1]
156
- xor_byte_strings(one_time_pad, encrypted_token)
157
- end
158
-
159
- def unmasked_token?(token)
160
- token.length == TOKEN_LENGTH
161
- end
162
-
163
- def masked_token?(token)
164
- token.length == TOKEN_LENGTH * 2
165
- end
166
-
167
- def compare_with_real_token(token, local)
168
- ::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s)
169
- end
170
-
171
- def decode_token(token)
172
- Base64.urlsafe_decode64(token)
173
- end
174
-
175
- def xor_byte_strings(s1, s2)
176
- s1.bytes.zip(s2.bytes).map { |(c1, c2)| c1 ^ c2 }.pack("c*")
177
- end
178
- end
179
- end
180
- end
@@ -1,370 +0,0 @@
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
- # sidekiq_options queue: 'critical', retry: 5
13
- #
14
- # def perform(*args)
15
- # # do some work
16
- # end
17
- # end
18
- #
19
- # Then in your Rails app, you can do this:
20
- #
21
- # HardWorker.perform_async(1, 2, 3)
22
- #
23
- # Note that perform_async is a class method, perform is an instance method.
24
- #
25
- # Sidekiq::Worker also includes several APIs to provide compatibility with
26
- # ActiveJob.
27
- #
28
- # class SomeWorker
29
- # include Sidekiq::Worker
30
- # queue_as :critical
31
- #
32
- # def perform(...)
33
- # end
34
- # end
35
- #
36
- # SomeWorker.set(wait_until: 1.hour).perform_async(123)
37
- #
38
- # Note that arguments passed to the job must still obey Sidekiq's
39
- # best practice for simple, JSON-native data types. Sidekiq will not
40
- # implement ActiveJob's more complex argument serialization. For
41
- # this reason, we don't implement `perform_later` as our call semantics
42
- # are very different.
43
- #
44
- module Worker
45
- ##
46
- # The Options module is extracted so we can include it in ActiveJob::Base
47
- # and allow native AJs to configure Sidekiq features/internals.
48
- module Options
49
- def self.included(base)
50
- base.extend(ClassMethods)
51
- base.sidekiq_class_attribute :sidekiq_options_hash
52
- base.sidekiq_class_attribute :sidekiq_retry_in_block
53
- base.sidekiq_class_attribute :sidekiq_retries_exhausted_block
54
- end
55
-
56
- module ClassMethods
57
- ACCESSOR_MUTEX = Mutex.new
58
-
59
- ##
60
- # Allows customization for this type of Worker.
61
- # Legal options:
62
- #
63
- # queue - name of queue to use for this job type, default *default*
64
- # retry - enable retries for this Worker in case of error during execution,
65
- # *true* to use the default or *Integer* count
66
- # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
67
- # can be true, false or an integer number of lines to save, default *false*
68
- #
69
- # In practice, any option is allowed. This is the main mechanism to configure the
70
- # options for a specific job.
71
- def sidekiq_options(opts = {})
72
- opts = opts.transform_keys(&:to_s) # stringify
73
- self.sidekiq_options_hash = get_sidekiq_options.merge(opts)
74
- end
75
-
76
- def sidekiq_retry_in(&block)
77
- self.sidekiq_retry_in_block = block
78
- end
79
-
80
- def sidekiq_retries_exhausted(&block)
81
- self.sidekiq_retries_exhausted_block = block
82
- end
83
-
84
- def get_sidekiq_options # :nodoc:
85
- self.sidekiq_options_hash ||= Sidekiq.default_job_options
86
- end
87
-
88
- def sidekiq_class_attribute(*attrs)
89
- instance_reader = true
90
- instance_writer = true
91
-
92
- attrs.each do |name|
93
- synchronized_getter = "__synchronized_#{name}"
94
-
95
- singleton_class.instance_eval do
96
- undef_method(name) if method_defined?(name) || private_method_defined?(name)
97
- end
98
-
99
- define_singleton_method(synchronized_getter) { nil }
100
- singleton_class.class_eval do
101
- private(synchronized_getter)
102
- end
103
-
104
- define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } }
105
-
106
- ivar = "@#{name}"
107
-
108
- singleton_class.instance_eval do
109
- m = "#{name}="
110
- undef_method(m) if method_defined?(m) || private_method_defined?(m)
111
- end
112
- define_singleton_method("#{name}=") do |val|
113
- singleton_class.class_eval do
114
- ACCESSOR_MUTEX.synchronize do
115
- undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
116
- define_method(synchronized_getter) { val }
117
- end
118
- end
119
-
120
- if singleton_class?
121
- class_eval do
122
- undef_method(name) if method_defined?(name) || private_method_defined?(name)
123
- define_method(name) do
124
- if instance_variable_defined? ivar
125
- instance_variable_get ivar
126
- else
127
- singleton_class.send name
128
- end
129
- end
130
- end
131
- end
132
- val
133
- end
134
-
135
- if instance_reader
136
- undef_method(name) if method_defined?(name) || private_method_defined?(name)
137
- define_method(name) do
138
- if instance_variable_defined?(ivar)
139
- instance_variable_get ivar
140
- else
141
- self.class.public_send name
142
- end
143
- end
144
- end
145
-
146
- if instance_writer
147
- m = "#{name}="
148
- undef_method(m) if method_defined?(m) || private_method_defined?(m)
149
- attr_writer name
150
- end
151
- end
152
- end
153
- end
154
- end
155
-
156
- attr_accessor :jid
157
-
158
- def self.included(base)
159
- raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
160
-
161
- base.include(Options)
162
- base.extend(ClassMethods)
163
- end
164
-
165
- def logger
166
- Sidekiq.logger
167
- end
168
-
169
- # This helper class encapsulates the set options for `set`, e.g.
170
- #
171
- # SomeWorker.set(queue: 'foo').perform_async(....)
172
- #
173
- class Setter
174
- include Sidekiq::JobUtil
175
-
176
- def initialize(klass, opts)
177
- @klass = klass
178
- # NB: the internal hash always has stringified keys
179
- @opts = opts.transform_keys(&:to_s)
180
-
181
- # ActiveJob compatibility
182
- interval = @opts.delete("wait_until") || @opts.delete("wait")
183
- at(interval) if interval
184
- end
185
-
186
- def set(options)
187
- hash = options.transform_keys(&:to_s)
188
- interval = hash.delete("wait_until") || @opts.delete("wait")
189
- @opts.merge!(hash)
190
- at(interval) if interval
191
- self
192
- end
193
-
194
- def perform_async(*args)
195
- if @opts["sync"] == true
196
- perform_inline(*args)
197
- else
198
- @klass.client_push(@opts.merge("args" => args, "class" => @klass))
199
- end
200
- end
201
-
202
- # Explicit inline execution of a job. Returns nil if the job did not
203
- # execute, true otherwise.
204
- def perform_inline(*args)
205
- raw = @opts.merge("args" => args, "class" => @klass)
206
-
207
- # validate and normalize payload
208
- item = normalize_item(raw)
209
- queue = item["queue"]
210
-
211
- # run client-side middleware
212
- result = Sidekiq.client_middleware.invoke(item["class"], item, queue, Sidekiq.redis_pool) do
213
- item
214
- end
215
- return nil unless result
216
-
217
- # round-trip the payload via JSON
218
- msg = Sidekiq.load_json(Sidekiq.dump_json(item))
219
-
220
- # prepare the job instance
221
- klass = msg["class"].constantize
222
- job = klass.new
223
- job.jid = msg["jid"]
224
- job.bid = msg["bid"] if job.respond_to?(:bid)
225
-
226
- # run the job through server-side middleware
227
- result = Sidekiq.server_middleware.invoke(job, msg, msg["queue"]) do
228
- # perform it
229
- job.perform(*msg["args"])
230
- true
231
- end
232
- return nil unless result
233
- # jobs do not return a result. they should store any
234
- # modified state.
235
- true
236
- end
237
- alias_method :perform_sync, :perform_inline
238
-
239
- def perform_bulk(args, batch_size: 1_000)
240
- client = @klass.build_client
241
- result = args.each_slice(batch_size).flat_map do |slice|
242
- client.push_bulk(@opts.merge("class" => @klass, "args" => slice))
243
- end
244
-
245
- result.is_a?(Enumerator::Lazy) ? result.force : result
246
- end
247
-
248
- # +interval+ must be a timestamp, numeric or something that acts
249
- # numeric (like an activesupport time interval).
250
- def perform_in(interval, *args)
251
- at(interval).perform_async(*args)
252
- end
253
- alias_method :perform_at, :perform_in
254
-
255
- private
256
-
257
- def at(interval)
258
- int = interval.to_f
259
- now = Time.now.to_f
260
- ts = ((int < 1_000_000_000) ? now + int : int)
261
- # Optimization to enqueue something now that is scheduled to go out now or in the past
262
- @opts["at"] = ts if ts > now
263
- self
264
- end
265
- end
266
-
267
- module ClassMethods
268
- def delay(*args)
269
- raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
270
- end
271
-
272
- def delay_for(*args)
273
- raise ArgumentError, "Do not call .delay_for on a Sidekiq::Worker class, call .perform_in"
274
- end
275
-
276
- def delay_until(*args)
277
- raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
278
- end
279
-
280
- def queue_as(q)
281
- sidekiq_options("queue" => q.to_s)
282
- end
283
-
284
- def set(options)
285
- Setter.new(self, options)
286
- end
287
-
288
- def perform_async(*args)
289
- Setter.new(self, {}).perform_async(*args)
290
- end
291
-
292
- # Inline execution of job's perform method after passing through Sidekiq.client_middleware and Sidekiq.server_middleware
293
- def perform_inline(*args)
294
- Setter.new(self, {}).perform_inline(*args)
295
- end
296
- alias_method :perform_sync, :perform_inline
297
-
298
- ##
299
- # Push a large number of jobs to Redis, while limiting the batch of
300
- # each job payload to 1,000. This method helps cut down on the number
301
- # of round trips to Redis, which can increase the performance of enqueueing
302
- # large numbers of jobs.
303
- #
304
- # +items+ must be an Array of Arrays.
305
- #
306
- # For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
307
- #
308
- # Example (3 Redis round trips):
309
- #
310
- # SomeWorker.perform_async(1)
311
- # SomeWorker.perform_async(2)
312
- # SomeWorker.perform_async(3)
313
- #
314
- # Would instead become (1 Redis round trip):
315
- #
316
- # SomeWorker.perform_bulk([[1], [2], [3]])
317
- #
318
- def perform_bulk(*args, **kwargs)
319
- Setter.new(self, {}).perform_bulk(*args, **kwargs)
320
- end
321
-
322
- # +interval+ must be a timestamp, numeric or something that acts
323
- # numeric (like an activesupport time interval).
324
- def perform_in(interval, *args)
325
- int = interval.to_f
326
- now = Time.now.to_f
327
- ts = ((int < 1_000_000_000) ? now + int : int)
328
-
329
- item = {"class" => self, "args" => args}
330
-
331
- # Optimization to enqueue something now that is scheduled to go out now or in the past
332
- item["at"] = ts if ts > now
333
-
334
- client_push(item)
335
- end
336
- alias_method :perform_at, :perform_in
337
-
338
- ##
339
- # Allows customization for this type of Worker.
340
- # Legal options:
341
- #
342
- # queue - use a named queue for this Worker, default 'default'
343
- # retry - enable retries via JobRetry, *true* to use the default
344
- # or *Integer* count
345
- # backtrace - whether to save any error backtrace in the retry payload to display in web UI,
346
- # can be true, false or an integer number of lines to save, default *false*
347
- # pool - use the given Redis connection pool to push this type of job to a given shard.
348
- #
349
- # In practice, any option is allowed. This is the main mechanism to configure the
350
- # options for a specific job.
351
- #
352
- # These options will be saved into the serialized job when enqueued by
353
- # the client.
354
- def sidekiq_options(opts = {})
355
- super
356
- end
357
-
358
- def client_push(item) # :nodoc:
359
- raise ArgumentError, "Job payloads should contain no Symbols: #{item}" if item.any? { |k, v| k.is_a?(::Symbol) }
360
- build_client.push(item)
361
- end
362
-
363
- def build_client # :nodoc:
364
- pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
365
- client_class = get_sidekiq_options["client_class"] || Sidekiq::Client
366
- client_class.new(pool)
367
- end
368
- end
369
- end
370
- end