sidekiq 4.2.4 → 6.4.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 (143) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +523 -0
  3. data/LICENSE +3 -3
  4. data/README.md +23 -36
  5. data/bin/sidekiq +26 -2
  6. data/bin/sidekiqload +28 -38
  7. data/bin/sidekiqmon +8 -0
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  11. data/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  12. data/lib/sidekiq/api.rb +403 -243
  13. data/lib/sidekiq/cli.rb +230 -211
  14. data/lib/sidekiq/client.rb +53 -64
  15. data/lib/sidekiq/delay.rb +43 -0
  16. data/lib/sidekiq/exception_handler.rb +12 -16
  17. data/lib/sidekiq/extensions/action_mailer.rb +15 -24
  18. data/lib/sidekiq/extensions/active_record.rb +15 -12
  19. data/lib/sidekiq/extensions/class_methods.rb +16 -13
  20. data/lib/sidekiq/extensions/generic_proxy.rb +14 -6
  21. data/lib/sidekiq/fetch.rb +39 -31
  22. data/lib/sidekiq/job.rb +13 -0
  23. data/lib/sidekiq/job_logger.rb +63 -0
  24. data/lib/sidekiq/job_retry.rb +261 -0
  25. data/lib/sidekiq/job_util.rb +65 -0
  26. data/lib/sidekiq/launcher.rb +170 -71
  27. data/lib/sidekiq/logger.rb +166 -0
  28. data/lib/sidekiq/manager.rb +21 -26
  29. data/lib/sidekiq/middleware/chain.rb +20 -8
  30. data/lib/sidekiq/middleware/current_attributes.rb +57 -0
  31. data/lib/sidekiq/middleware/i18n.rb +5 -7
  32. data/lib/sidekiq/monitor.rb +133 -0
  33. data/lib/sidekiq/paginator.rb +18 -14
  34. data/lib/sidekiq/processor.rb +161 -70
  35. data/lib/sidekiq/rails.rb +41 -73
  36. data/lib/sidekiq/redis_connection.rb +65 -20
  37. data/lib/sidekiq/scheduled.rb +95 -34
  38. data/lib/sidekiq/sd_notify.rb +149 -0
  39. data/lib/sidekiq/systemd.rb +24 -0
  40. data/lib/sidekiq/testing/inline.rb +2 -1
  41. data/lib/sidekiq/testing.rb +52 -26
  42. data/lib/sidekiq/util.rb +60 -14
  43. data/lib/sidekiq/version.rb +2 -1
  44. data/lib/sidekiq/web/action.rb +15 -15
  45. data/lib/sidekiq/web/application.rb +115 -89
  46. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  47. data/lib/sidekiq/web/helpers.rb +151 -83
  48. data/lib/sidekiq/web/router.rb +27 -19
  49. data/lib/sidekiq/web.rb +65 -109
  50. data/lib/sidekiq/worker.rb +284 -41
  51. data/lib/sidekiq.rb +93 -60
  52. data/sidekiq.gemspec +24 -22
  53. data/web/assets/images/apple-touch-icon.png +0 -0
  54. data/web/assets/javascripts/application.js +83 -64
  55. data/web/assets/javascripts/dashboard.js +81 -85
  56. data/web/assets/stylesheets/application-dark.css +143 -0
  57. data/web/assets/stylesheets/application-rtl.css +242 -0
  58. data/web/assets/stylesheets/application.css +319 -143
  59. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  60. data/web/assets/stylesheets/bootstrap.css +2 -2
  61. data/web/locales/ar.yml +87 -0
  62. data/web/locales/de.yml +14 -2
  63. data/web/locales/en.yml +8 -1
  64. data/web/locales/es.yml +22 -5
  65. data/web/locales/fa.yml +80 -0
  66. data/web/locales/fr.yml +10 -3
  67. data/web/locales/he.yml +79 -0
  68. data/web/locales/ja.yml +12 -4
  69. data/web/locales/lt.yml +83 -0
  70. data/web/locales/pl.yml +4 -4
  71. data/web/locales/ru.yml +4 -0
  72. data/web/locales/ur.yml +80 -0
  73. data/web/locales/vi.yml +83 -0
  74. data/web/views/_footer.erb +5 -2
  75. data/web/views/_job_info.erb +4 -3
  76. data/web/views/_nav.erb +4 -18
  77. data/web/views/_paging.erb +1 -1
  78. data/web/views/_poll_link.erb +2 -5
  79. data/web/views/_summary.erb +7 -7
  80. data/web/views/busy.erb +60 -22
  81. data/web/views/dashboard.erb +23 -15
  82. data/web/views/dead.erb +3 -3
  83. data/web/views/layout.erb +14 -3
  84. data/web/views/morgue.erb +19 -12
  85. data/web/views/queue.erb +24 -14
  86. data/web/views/queues.erb +14 -4
  87. data/web/views/retries.erb +22 -13
  88. data/web/views/retry.erb +4 -4
  89. data/web/views/scheduled.erb +7 -4
  90. metadata +49 -198
  91. data/.github/contributing.md +0 -32
  92. data/.github/issue_template.md +0 -4
  93. data/.gitignore +0 -12
  94. data/.travis.yml +0 -12
  95. data/3.0-Upgrade.md +0 -70
  96. data/4.0-Upgrade.md +0 -53
  97. data/COMM-LICENSE +0 -95
  98. data/Ent-Changes.md +0 -146
  99. data/Gemfile +0 -29
  100. data/Pro-2.0-Upgrade.md +0 -138
  101. data/Pro-3.0-Upgrade.md +0 -44
  102. data/Pro-Changes.md +0 -585
  103. data/Rakefile +0 -9
  104. data/bin/sidekiqctl +0 -99
  105. data/code_of_conduct.md +0 -50
  106. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
  107. data/lib/generators/sidekiq/templates/worker_test.rb.erb +0 -8
  108. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  109. data/lib/sidekiq/core_ext.rb +0 -106
  110. data/lib/sidekiq/logging.rb +0 -106
  111. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  112. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  113. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
  114. data/test/config.yml +0 -9
  115. data/test/env_based_config.yml +0 -11
  116. data/test/fake_env.rb +0 -1
  117. data/test/fixtures/en.yml +0 -2
  118. data/test/helper.rb +0 -75
  119. data/test/test_actors.rb +0 -138
  120. data/test/test_api.rb +0 -528
  121. data/test/test_cli.rb +0 -418
  122. data/test/test_client.rb +0 -266
  123. data/test/test_exception_handler.rb +0 -56
  124. data/test/test_extensions.rb +0 -127
  125. data/test/test_fetch.rb +0 -50
  126. data/test/test_launcher.rb +0 -95
  127. data/test/test_logging.rb +0 -35
  128. data/test/test_manager.rb +0 -50
  129. data/test/test_middleware.rb +0 -158
  130. data/test/test_processor.rb +0 -235
  131. data/test/test_rails.rb +0 -22
  132. data/test/test_redis_connection.rb +0 -132
  133. data/test/test_retry.rb +0 -326
  134. data/test/test_retry_exhausted.rb +0 -149
  135. data/test/test_scheduled.rb +0 -115
  136. data/test/test_scheduling.rb +0 -58
  137. data/test/test_sidekiq.rb +0 -107
  138. data/test/test_testing.rb +0 -143
  139. data/test/test_testing_fake.rb +0 -357
  140. data/test/test_testing_inline.rb +0 -94
  141. data/test/test_util.rb +0 -13
  142. data/test/test_web.rb +0 -726
  143. data/test/test_web_helpers.rb +0 -54
data/lib/sidekiq/web.rb CHANGED
@@ -1,36 +1,36 @@
1
1
  # frozen_string_literal: true
2
- require 'erb'
3
2
 
4
- require 'sidekiq'
5
- require 'sidekiq/api'
6
- require 'sidekiq/paginator'
7
- require 'sidekiq/web/helpers'
3
+ require "erb"
8
4
 
9
- require 'sidekiq/web/router'
10
- require 'sidekiq/web/action'
11
- require 'sidekiq/web/application'
5
+ require "sidekiq"
6
+ require "sidekiq/api"
7
+ require "sidekiq/paginator"
8
+ require "sidekiq/web/helpers"
12
9
 
13
- require 'rack/protection'
10
+ require "sidekiq/web/router"
11
+ require "sidekiq/web/action"
12
+ require "sidekiq/web/application"
13
+ require "sidekiq/web/csrf_protection"
14
14
 
15
- require 'rack/builder'
16
- require 'rack/file'
17
- require 'rack/session/cookie'
15
+ require "rack/content_length"
16
+ require "rack/builder"
17
+ require "rack/static"
18
18
 
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
- "Dashboard" => '',
29
- "Busy" => 'busy',
30
- "Queues" => 'queues',
31
- "Retries" => 'retries',
32
- "Scheduled" => 'scheduled',
33
- "Dead" => 'morgue',
28
+ "Dashboard" => "",
29
+ "Busy" => "busy",
30
+ "Queues" => "queues",
31
+ "Retries" => "retries",
32
+ "Scheduled" => "scheduled",
33
+ "Dead" => "morgue"
34
34
  }
35
35
 
36
36
  class << self
@@ -38,14 +38,6 @@ module Sidekiq
38
38
  self
39
39
  end
40
40
 
41
- def middlewares
42
- @middlewares ||= []
43
- end
44
-
45
- def use(*middleware_args, &block)
46
- middlewares << [middleware_args, block]
47
- end
48
-
49
41
  def default_tabs
50
42
  DEFAULT_TABS
51
43
  end
@@ -64,39 +56,52 @@ module Sidekiq
64
56
  end
65
57
 
66
58
  def enable(*opts)
67
- opts.each {|key| set(key, true) }
59
+ opts.each { |key| set(key, true) }
68
60
  end
69
61
 
70
62
  def disable(*opts)
71
- opts.each {|key| set(key, false) }
63
+ opts.each { |key| set(key, false) }
64
+ end
65
+
66
+ def middlewares
67
+ @middlewares ||= []
68
+ end
69
+
70
+ def use(*args, &block)
71
+ middlewares << [args, block]
72
72
  end
73
73
 
74
- # Helper for the Sinatra syntax: Sidekiq::Web.set(:session_secret, Rails.application.secrets...)
75
74
  def set(attribute, value)
76
75
  send(:"#{attribute}=", value)
77
76
  end
78
77
 
79
- attr_accessor :app_url, :session_secret, :redis_pool, :sessions
78
+ def sessions=(val)
79
+ puts "WARNING: Sidekiq::Web.sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
80
+ end
81
+
82
+ def session_secret=(val)
83
+ puts "WARNING: Sidekiq::Web.session_secret= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
84
+ end
85
+
86
+ attr_accessor :app_url, :redis_pool
80
87
  attr_writer :locales, :views
81
88
  end
82
89
 
83
90
  def self.inherited(child)
84
- child.app_url = self.app_url
85
- child.session_secret = self.session_secret
86
- child.redis_pool = self.redis_pool
87
- child.sessions = self.sessions
91
+ child.app_url = app_url
92
+ child.redis_pool = redis_pool
88
93
  end
89
94
 
90
95
  def settings
91
96
  self.class.settings
92
97
  end
93
98
 
94
- def use(*middleware_args, &block)
95
- middlewares << [middleware_args, block]
99
+ def middlewares
100
+ @middlewares ||= self.class.middlewares
96
101
  end
97
102
 
98
- def middlewares
99
- @middlewares ||= Web.middlewares.dup
103
+ def use(*args, &block)
104
+ middlewares << [args, block]
100
105
  end
101
106
 
102
107
  def call(env)
@@ -113,29 +118,19 @@ module Sidekiq
113
118
  end
114
119
 
115
120
  def enable(*opts)
116
- opts.each {|key| set(key, true) }
121
+ opts.each { |key| set(key, true) }
117
122
  end
118
123
 
119
124
  def disable(*opts)
120
- opts.each {|key| set(key, false) }
125
+ opts.each { |key| set(key, false) }
121
126
  end
122
127
 
123
128
  def set(attribute, value)
124
129
  send(:"#{attribute}=", value)
125
130
  end
126
131
 
127
- # Default values
128
- set :sessions, true
129
-
130
- attr_writer :sessions
131
-
132
- def sessions
133
- unless instance_variable_defined?("@sessions")
134
- @sessions = self.class.sessions
135
- @sessions = @sessions.to_hash.dup if @sessions.respond_to?(:to_hash)
136
- end
137
-
138
- @sessions
132
+ def sessions=(val)
133
+ puts "Sidekiq::Web#sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller[2..2].first}"
139
134
  end
140
135
 
141
136
  def self.register(extension)
@@ -144,50 +139,20 @@ module Sidekiq
144
139
 
145
140
  private
146
141
 
147
- def using?(middleware)
148
- middlewares.any? do |(m,_)|
149
- m.kind_of?(Array) && (m[0] == middleware || m[0].kind_of?(middleware))
150
- end
151
- end
152
-
153
- def build_sessions
154
- middlewares = self.middlewares
155
-
156
- unless using?(::Rack::Protection) || ENV['RACK_ENV'] == 'test'
157
- middlewares.unshift [[::Rack::Protection, { use: :authenticity_token }], nil]
158
- end
159
-
160
- s = sessions
161
- return unless s
162
-
163
- unless using? ::Rack::Session::Cookie
164
- unless secret = Web.session_secret
165
- require 'securerandom'
166
- secret = SecureRandom.hex(64)
167
- end
168
-
169
- options = { secret: secret }
170
- options = options.merge(s.to_hash) if s.respond_to? :to_hash
171
-
172
- middlewares.unshift [[::Rack::Session::Cookie, options], nil]
173
- end
174
- end
175
-
176
142
  def build
177
- build_sessions
178
-
179
- middlewares = self.middlewares
180
143
  klass = self.class
144
+ m = middlewares
181
145
 
182
- ::Rack::Builder.new do
183
- %w(stylesheets javascripts images).each do |asset_dir|
184
- map "/#{asset_dir}" do
185
- run ::Rack::File.new("#{ASSETS}/#{asset_dir}", { 'Cache-Control' => 'public, max-age=86400' })
186
- end
187
- end
188
-
189
- middlewares.each {|middleware, block| use(*middleware, &block) }
146
+ rules = []
147
+ rules = [[:all, {"Cache-Control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
190
148
 
149
+ ::Rack::Builder.new do
150
+ use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
151
+ root: ASSETS,
152
+ cascade: true,
153
+ header_rules: rules
154
+ m.each { |middleware, block| use(*middleware, &block) }
155
+ use Sidekiq::Web::CsrfProtection unless $TESTING
191
156
  run WebApplication.new(klass)
192
157
  end
193
158
  end
@@ -196,18 +161,9 @@ module Sidekiq
196
161
  Sidekiq::WebApplication.helpers WebHelpers
197
162
  Sidekiq::WebApplication.helpers Sidekiq::Paginator
198
163
 
199
- Sidekiq::WebAction.class_eval "def _render\n#{ERB.new(File.read(Web::LAYOUT)).src}\nend"
200
- end
201
-
202
- if defined?(::ActionDispatch::Request::Session) &&
203
- !::ActionDispatch::Request::Session.method_defined?(:each)
204
- # mperham/sidekiq#2460
205
- # Rack apps can't reuse the Rails session store without
206
- # this monkeypatch, fixed in Rails 5.
207
- class ActionDispatch::Request::Session
208
- def each(&block)
209
- hash = self.to_hash
210
- hash.each(&block)
164
+ Sidekiq::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
165
+ def _render
166
+ #{ERB.new(File.read(Web::LAYOUT)).src}
211
167
  end
212
- end
168
+ RUBY
213
169
  end
@@ -1,44 +1,268 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/client'
3
- require 'sidekiq/core_ext'
4
2
 
5
- module Sidekiq
3
+ require "sidekiq/client"
6
4
 
5
+ module Sidekiq
7
6
  ##
8
7
  # Include this module in your worker class and you can easily create
9
8
  # asynchronous jobs:
10
9
  #
11
- # class HardWorker
12
- # include Sidekiq::Worker
10
+ # class HardWorker
11
+ # include Sidekiq::Worker
12
+ # sidekiq_options queue: 'critical', retry: 5
13
13
  #
14
- # def perform(*args)
15
- # # do some work
14
+ # def perform(*args)
15
+ # # do some work
16
+ # end
16
17
  # end
17
- # end
18
18
  #
19
19
  # Then in your Rails app, you can do this:
20
20
  #
21
21
  # HardWorker.perform_async(1, 2, 3)
22
22
  #
23
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
+ #
24
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_worker_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
+
25
156
  attr_accessor :jid
26
157
 
27
158
  def self.included(base)
28
- raise ArgumentError, "You cannot include Sidekiq::Worker in an ActiveJob: #{base.name}" if base.ancestors.any? {|c| c.name == 'ActiveJob::Base' }
159
+ raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" }
29
160
 
161
+ base.include(Options)
30
162
  base.extend(ClassMethods)
31
- base.class_attribute :sidekiq_options_hash
32
- base.class_attribute :sidekiq_retry_in_block
33
- base.class_attribute :sidekiq_retries_exhausted_block
34
163
  end
35
164
 
36
165
  def logger
37
166
  Sidekiq.logger
38
167
  end
39
168
 
40
- module ClassMethods
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
+ @opts = opts
179
+
180
+ # ActiveJob compatibility
181
+ interval = @opts.delete(:wait_until) || @opts.delete(:wait)
182
+ at(interval) if interval
183
+ end
184
+
185
+ def set(options)
186
+ interval = options.delete(:wait_until) || options.delete(:wait)
187
+ @opts.merge!(options)
188
+ at(interval) if interval
189
+ self
190
+ end
191
+
192
+ def perform_async(*args)
193
+ if @opts["sync"] == true
194
+ perform_inline(*args)
195
+ else
196
+ @klass.client_push(@opts.merge("args" => args, "class" => @klass))
197
+ end
198
+ end
199
+
200
+ # Explicit inline execution of a job. Returns nil if the job did not
201
+ # execute, true otherwise.
202
+ def perform_inline(*args)
203
+ raw = @opts.merge("args" => args, "class" => @klass).transform_keys(&:to_s)
204
+
205
+ # validate and normalize payload
206
+ item = normalize_item(raw)
207
+ queue = item["queue"]
208
+
209
+ # run client-side middleware
210
+ result = Sidekiq.client_middleware.invoke(item["class"], item, queue, Sidekiq.redis_pool) do
211
+ item
212
+ end
213
+ return nil unless result
214
+
215
+ # round-trip the payload via JSON
216
+ msg = Sidekiq.load_json(Sidekiq.dump_json(item))
41
217
 
218
+ # prepare the job instance
219
+ klass = msg["class"].constantize
220
+ job = klass.new
221
+ job.jid = msg["jid"]
222
+ job.bid = msg["bid"] if job.respond_to?(:bid)
223
+
224
+ # run the job through server-side middleware
225
+ result = Sidekiq.server_middleware.invoke(job, msg, msg["queue"]) do
226
+ # perform it
227
+ job.perform(*msg["args"])
228
+ true
229
+ end
230
+ return nil unless result
231
+ # jobs do not return a result. they should store any
232
+ # modified state.
233
+ true
234
+ end
235
+ alias_method :perform_sync, :perform_inline
236
+
237
+ def perform_bulk(args, batch_size: 1_000)
238
+ hash = @opts.transform_keys(&:to_s)
239
+ result = args.each_slice(batch_size).flat_map do |slice|
240
+ Sidekiq::Client.push_bulk(hash.merge("class" => @klass, "args" => slice))
241
+ end
242
+
243
+ result.is_a?(Enumerator::Lazy) ? result.force : result
244
+ end
245
+
246
+ # +interval+ must be a timestamp, numeric or something that acts
247
+ # numeric (like an activesupport time interval).
248
+ def perform_in(interval, *args)
249
+ at(interval).perform_async(*args)
250
+ end
251
+ alias_method :perform_at, :perform_in
252
+
253
+ private
254
+
255
+ def at(interval)
256
+ int = interval.to_f
257
+ now = Time.now.to_f
258
+ ts = (int < 1_000_000_000 ? now + int : int)
259
+ # Optimization to enqueue something now that is scheduled to go out now or in the past
260
+ @opts["at"] = ts if ts > now
261
+ self
262
+ end
263
+ end
264
+
265
+ module ClassMethods
42
266
  def delay(*args)
43
267
  raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
44
268
  end
@@ -51,13 +275,49 @@ module Sidekiq
51
275
  raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
52
276
  end
53
277
 
278
+ def queue_as(q)
279
+ sidekiq_options("queue" => q.to_s)
280
+ end
281
+
54
282
  def set(options)
55
- Thread.current[:sidekiq_worker_set] = options
56
- self
283
+ Setter.new(self, options)
57
284
  end
58
285
 
59
286
  def perform_async(*args)
60
- client_push('class' => self, 'args' => args)
287
+ Setter.new(self, {}).perform_async(*args)
288
+ end
289
+
290
+ # Inline execution of job's perform method after passing through Sidekiq.client_middleware and Sidekiq.server_middleware
291
+ def perform_inline(*args)
292
+ Setter.new(self, {}).perform_inline(*args)
293
+ end
294
+
295
+ ##
296
+ # Push a large number of jobs to Redis, while limiting the batch of
297
+ # each job payload to 1,000. This method helps cut down on the number
298
+ # of round trips to Redis, which can increase the performance of enqueueing
299
+ # large numbers of jobs.
300
+ #
301
+ # +items+ must be an Array of Arrays.
302
+ #
303
+ # For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
304
+ #
305
+ # Example (3 Redis round trips):
306
+ #
307
+ # SomeWorker.perform_async(1)
308
+ # SomeWorker.perform_async(2)
309
+ # SomeWorker.perform_async(3)
310
+ #
311
+ # Would instead become (1 Redis round trip):
312
+ #
313
+ # SomeWorker.perform_bulk([[1], [2], [3]])
314
+ #
315
+ def perform_bulk(items, batch_size: 1_000)
316
+ result = items.each_slice(batch_size).flat_map do |slice|
317
+ Sidekiq::Client.push_bulk("class" => self, "args" => slice)
318
+ end
319
+
320
+ result.is_a?(Enumerator::Lazy) ? result.force : result
61
321
  end
62
322
 
63
323
  # +interval+ must be a timestamp, numeric or something that acts
@@ -67,10 +327,10 @@ module Sidekiq
67
327
  now = Time.now.to_f
68
328
  ts = (int < 1_000_000_000 ? now + int : int)
69
329
 
70
- item = { 'class' => self, 'args' => args, 'at' => ts }
330
+ item = {"class" => self, "args" => args}
71
331
 
72
332
  # Optimization to enqueue something now that is scheduled to go out now or in the past
73
- item.delete('at'.freeze) if ts <= now
333
+ item["at"] = ts if ts > now
74
334
 
75
335
  client_push(item)
76
336
  end
@@ -89,33 +349,16 @@ module Sidekiq
89
349
  #
90
350
  # In practice, any option is allowed. This is the main mechanism to configure the
91
351
  # options for a specific job.
92
- def sidekiq_options(opts={})
93
- self.sidekiq_options_hash = get_sidekiq_options.merge(opts.stringify_keys)
94
- end
95
-
96
- def sidekiq_retry_in(&block)
97
- self.sidekiq_retry_in_block = block
98
- end
99
-
100
- def sidekiq_retries_exhausted(&block)
101
- self.sidekiq_retries_exhausted_block = block
102
- end
103
-
104
- def get_sidekiq_options # :nodoc:
105
- self.sidekiq_options_hash ||= Sidekiq.default_worker_options
352
+ def sidekiq_options(opts = {})
353
+ super
106
354
  end
107
355
 
108
356
  def client_push(item) # :nodoc:
109
- pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'] || Sidekiq.redis_pool
110
- hash = if Thread.current[:sidekiq_worker_set]
111
- x, Thread.current[:sidekiq_worker_set] = Thread.current[:sidekiq_worker_set], nil
112
- x.stringify_keys.merge(item.stringify_keys)
113
- else
114
- item.stringify_keys
115
- end
116
- Sidekiq::Client.new(pool).push(hash)
117
- end
357
+ pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
358
+ stringified_item = item.transform_keys(&:to_s)
118
359
 
360
+ Sidekiq::Client.new(pool).push(stringified_item)
361
+ end
119
362
  end
120
363
  end
121
364
  end