sidekiq 5.1.3 → 6.4.1

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 (106) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +391 -1
  3. data/LICENSE +3 -3
  4. data/README.md +23 -34
  5. data/bin/sidekiq +27 -3
  6. data/bin/sidekiqload +68 -62
  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/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +344 -243
  13. data/lib/sidekiq/cli.rb +209 -221
  14. data/lib/sidekiq/client.rb +62 -64
  15. data/lib/sidekiq/delay.rb +7 -6
  16. data/lib/sidekiq/exception_handler.rb +10 -12
  17. data/lib/sidekiq/extensions/action_mailer.rb +13 -22
  18. data/lib/sidekiq/extensions/active_record.rb +13 -10
  19. data/lib/sidekiq/extensions/class_methods.rb +14 -11
  20. data/lib/sidekiq/extensions/generic_proxy.rb +6 -4
  21. data/lib/sidekiq/fetch.rb +40 -32
  22. data/lib/sidekiq/job.rb +13 -0
  23. data/lib/sidekiq/job_logger.rb +35 -9
  24. data/lib/sidekiq/job_retry.rb +88 -68
  25. data/lib/sidekiq/job_util.rb +65 -0
  26. data/lib/sidekiq/launcher.rb +170 -73
  27. data/lib/sidekiq/logger.rb +170 -0
  28. data/lib/sidekiq/manager.rb +18 -22
  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 +20 -16
  34. data/lib/sidekiq/processor.rb +135 -83
  35. data/lib/sidekiq/rails.rb +42 -38
  36. data/lib/sidekiq/redis_connection.rb +49 -30
  37. data/lib/sidekiq/scheduled.rb +94 -31
  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 +40 -31
  42. data/lib/sidekiq/util.rb +57 -15
  43. data/lib/sidekiq/version.rb +2 -1
  44. data/lib/sidekiq/web/action.rb +15 -11
  45. data/lib/sidekiq/web/application.rb +109 -74
  46. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  47. data/lib/sidekiq/web/helpers.rb +117 -93
  48. data/lib/sidekiq/web/router.rb +23 -19
  49. data/lib/sidekiq/web.rb +61 -105
  50. data/lib/sidekiq/worker.rb +257 -99
  51. data/lib/sidekiq.rb +81 -46
  52. data/sidekiq.gemspec +23 -23
  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 +66 -75
  56. data/web/assets/stylesheets/application-dark.css +143 -0
  57. data/web/assets/stylesheets/application-rtl.css +0 -4
  58. data/web/assets/stylesheets/application.css +77 -231
  59. data/web/assets/stylesheets/bootstrap.css +2 -2
  60. data/web/locales/ar.yml +9 -2
  61. data/web/locales/de.yml +14 -2
  62. data/web/locales/en.yml +7 -1
  63. data/web/locales/es.yml +21 -5
  64. data/web/locales/fr.yml +10 -3
  65. data/web/locales/ja.yml +7 -1
  66. data/web/locales/lt.yml +83 -0
  67. data/web/locales/pl.yml +4 -4
  68. data/web/locales/ru.yml +4 -0
  69. data/web/locales/vi.yml +83 -0
  70. data/web/views/_footer.erb +1 -1
  71. data/web/views/_job_info.erb +3 -2
  72. data/web/views/_nav.erb +3 -17
  73. data/web/views/_poll_link.erb +2 -5
  74. data/web/views/_summary.erb +7 -7
  75. data/web/views/busy.erb +54 -20
  76. data/web/views/dashboard.erb +22 -14
  77. data/web/views/dead.erb +3 -3
  78. data/web/views/layout.erb +4 -2
  79. data/web/views/morgue.erb +9 -6
  80. data/web/views/queue.erb +20 -10
  81. data/web/views/queues.erb +11 -3
  82. data/web/views/retries.erb +14 -7
  83. data/web/views/retry.erb +3 -3
  84. data/web/views/scheduled.erb +5 -2
  85. metadata +39 -136
  86. data/.github/contributing.md +0 -32
  87. data/.github/issue_template.md +0 -11
  88. data/.gitignore +0 -13
  89. data/.travis.yml +0 -14
  90. data/3.0-Upgrade.md +0 -70
  91. data/4.0-Upgrade.md +0 -53
  92. data/5.0-Upgrade.md +0 -56
  93. data/COMM-LICENSE +0 -95
  94. data/Ent-Changes.md +0 -216
  95. data/Gemfile +0 -8
  96. data/Pro-2.0-Upgrade.md +0 -138
  97. data/Pro-3.0-Upgrade.md +0 -44
  98. data/Pro-4.0-Upgrade.md +0 -35
  99. data/Pro-Changes.md +0 -729
  100. data/Rakefile +0 -8
  101. data/bin/sidekiqctl +0 -99
  102. data/code_of_conduct.md +0 -50
  103. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  104. data/lib/sidekiq/core_ext.rb +0 -1
  105. data/lib/sidekiq/logging.rb +0 -122
  106. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
data/lib/sidekiq/web.rb CHANGED
@@ -1,20 +1,20 @@
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
@@ -25,12 +25,12 @@ module Sidekiq
25
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,35 +1,165 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/client'
3
2
 
4
- module Sidekiq
3
+ require "sidekiq/client"
5
4
 
5
+ module Sidekiq
6
6
  ##
7
7
  # Include this module in your worker class and you can easily create
8
8
  # asynchronous jobs:
9
9
  #
10
- # class HardWorker
11
- # include Sidekiq::Worker
10
+ # class HardWorker
11
+ # include Sidekiq::Worker
12
+ # sidekiq_options queue: 'critical', retry: 5
12
13
  #
13
- # def perform(*args)
14
- # # do some work
14
+ # def perform(*args)
15
+ # # do some work
16
+ # end
15
17
  # end
16
- # end
17
18
  #
18
19
  # Then in your Rails app, you can do this:
19
20
  #
20
21
  # HardWorker.perform_async(1, 2, 3)
21
22
  #
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
+ #
23
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
+
24
156
  attr_accessor :jid
25
157
 
26
158
  def self.included(base)
27
- 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" }
28
160
 
161
+ base.include(Options)
29
162
  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
163
  end
34
164
 
35
165
  def logger
@@ -41,32 +171,100 @@ module Sidekiq
41
171
  # SomeWorker.set(queue: 'foo').perform_async(....)
42
172
  #
43
173
  class Setter
174
+ include Sidekiq::JobUtil
175
+
44
176
  def initialize(klass, opts)
45
177
  @klass = klass
46
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
47
190
  end
48
191
 
49
192
  def perform_async(*args)
50
- @klass.client_push(@opts.merge('args' => args, 'class' => @klass))
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))
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
+ pool = Thread.current[:sidekiq_via_pool] || @klass.get_sidekiq_options["pool"] || Sidekiq.redis_pool
240
+ client = Sidekiq::Client.new(pool)
241
+ result = args.each_slice(batch_size).flat_map do |slice|
242
+ client.push_bulk(hash.merge("class" => @klass, "args" => slice))
243
+ end
244
+
245
+ result.is_a?(Enumerator::Lazy) ? result.force : result
51
246
  end
52
247
 
53
248
  # +interval+ must be a timestamp, numeric or something that acts
54
249
  # numeric (like an activesupport time interval).
55
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)
56
258
  int = interval.to_f
57
259
  now = Time.now.to_f
58
260
  ts = (int < 1_000_000_000 ? now + int : int)
59
-
60
- payload = @opts.merge('class' => @klass, 'args' => args, 'at' => ts)
61
261
  # Optimization to enqueue something now that is scheduled to go out now or in the past
62
- payload.delete('at') if ts <= now
63
- @klass.client_push(payload)
262
+ @opts["at"] = ts if ts > now
263
+ self
64
264
  end
65
- alias_method :perform_at, :perform_in
66
265
  end
67
266
 
68
267
  module ClassMethods
69
-
70
268
  def delay(*args)
71
269
  raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
72
270
  end
@@ -79,12 +277,45 @@ module Sidekiq
79
277
  raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at"
80
278
  end
81
279
 
280
+ def queue_as(q)
281
+ sidekiq_options("queue" => q.to_s)
282
+ end
283
+
82
284
  def set(options)
83
285
  Setter.new(self, options)
84
286
  end
85
287
 
86
288
  def perform_async(*args)
87
- client_push('class' => self, 'args' => 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
+
297
+ ##
298
+ # Push a large number of jobs to Redis, while limiting the batch of
299
+ # each job payload to 1,000. This method helps cut down on the number
300
+ # of round trips to Redis, which can increase the performance of enqueueing
301
+ # large numbers of jobs.
302
+ #
303
+ # +items+ must be an Array of Arrays.
304
+ #
305
+ # For finer-grained control, use `Sidekiq::Client.push_bulk` directly.
306
+ #
307
+ # Example (3 Redis round trips):
308
+ #
309
+ # SomeWorker.perform_async(1)
310
+ # SomeWorker.perform_async(2)
311
+ # SomeWorker.perform_async(3)
312
+ #
313
+ # Would instead become (1 Redis round trip):
314
+ #
315
+ # SomeWorker.perform_bulk([[1], [2], [3]])
316
+ #
317
+ def perform_bulk(*args, **kwargs)
318
+ Setter.new(self, {}).perform_bulk(*args, **kwargs)
88
319
  end
89
320
 
90
321
  # +interval+ must be a timestamp, numeric or something that acts
@@ -94,10 +325,10 @@ module Sidekiq
94
325
  now = Time.now.to_f
95
326
  ts = (int < 1_000_000_000 ? now + int : int)
96
327
 
97
- item = { 'class' => self, 'args' => args, 'at' => ts }
328
+ item = {"class" => self, "args" => args}
98
329
 
99
330
  # Optimization to enqueue something now that is scheduled to go out now or in the past
100
- item.delete('at') if ts <= now
331
+ item["at"] = ts if ts > now
101
332
 
102
333
  client_push(item)
103
334
  end
@@ -116,89 +347,16 @@ module Sidekiq
116
347
  #
117
348
  # In practice, any option is allowed. This is the main mechanism to configure the
118
349
  # options for a specific job.
119
- def sidekiq_options(opts={})
120
- # stringify
121
- self.sidekiq_options_hash = get_sidekiq_options.merge(Hash[opts.map{|k, v| [k.to_s, v]}])
122
- end
123
-
124
- def sidekiq_retry_in(&block)
125
- self.sidekiq_retry_in_block = block
126
- end
127
-
128
- def sidekiq_retries_exhausted(&block)
129
- self.sidekiq_retries_exhausted_block = block
130
- end
131
-
132
- def get_sidekiq_options # :nodoc:
133
- self.sidekiq_options_hash ||= Sidekiq.default_worker_options
350
+ def sidekiq_options(opts = {})
351
+ super
134
352
  end
135
353
 
136
354
  def client_push(item) # :nodoc:
137
- pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'] || Sidekiq.redis_pool
138
- # stringify
139
- item.keys.each do |key|
140
- item[key.to_s] = item.delete(key)
141
- end
355
+ pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool
356
+ stringified_item = item.transform_keys(&:to_s)
142
357
 
143
- Sidekiq::Client.new(pool).push(item)
358
+ Sidekiq::Client.new(pool).push(stringified_item)
144
359
  end
145
-
146
- def sidekiq_class_attribute(*attrs)
147
- instance_reader = true
148
- instance_writer = true
149
-
150
- attrs.each do |name|
151
- singleton_class.instance_eval do
152
- undef_method(name) if method_defined?(name) || private_method_defined?(name)
153
- end
154
- define_singleton_method(name) { nil }
155
-
156
- ivar = "@#{name}"
157
-
158
- singleton_class.instance_eval do
159
- m = "#{name}="
160
- undef_method(m) if method_defined?(m) || private_method_defined?(m)
161
- end
162
- define_singleton_method("#{name}=") do |val|
163
- singleton_class.class_eval do
164
- undef_method(name) if method_defined?(name) || private_method_defined?(name)
165
- define_method(name) { val }
166
- end
167
-
168
- if singleton_class?
169
- class_eval do
170
- undef_method(name) if method_defined?(name) || private_method_defined?(name)
171
- define_method(name) do
172
- if instance_variable_defined? ivar
173
- instance_variable_get ivar
174
- else
175
- singleton_class.send name
176
- end
177
- end
178
- end
179
- end
180
- val
181
- end
182
-
183
- if instance_reader
184
- undef_method(name) if method_defined?(name) || private_method_defined?(name)
185
- define_method(name) do
186
- if instance_variable_defined?(ivar)
187
- instance_variable_get ivar
188
- else
189
- self.class.public_send name
190
- end
191
- end
192
- end
193
-
194
- if instance_writer
195
- m = "#{name}="
196
- undef_method(m) if method_defined?(m) || private_method_defined?(m)
197
- attr_writer name
198
- end
199
- end
200
- end
201
-
202
360
  end
203
361
  end
204
362
  end