sidekiq 4.2.10 → 5.0.5

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -10
  4. data/5.0-Upgrade.md +56 -0
  5. data/COMM-LICENSE +1 -1
  6. data/Changes.md +60 -0
  7. data/Ent-Changes.md +29 -2
  8. data/Gemfile +3 -0
  9. data/Pro-Changes.md +28 -3
  10. data/README.md +3 -3
  11. data/bin/sidekiqctl +1 -1
  12. data/bin/sidekiqload +3 -8
  13. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
  14. data/lib/sidekiq.rb +6 -15
  15. data/lib/sidekiq/api.rb +77 -31
  16. data/lib/sidekiq/cli.rb +15 -6
  17. data/lib/sidekiq/client.rb +20 -13
  18. data/lib/sidekiq/core_ext.rb +1 -119
  19. data/lib/sidekiq/delay.rb +41 -0
  20. data/lib/sidekiq/extensions/generic_proxy.rb +7 -1
  21. data/lib/sidekiq/job_logger.rb +24 -0
  22. data/lib/sidekiq/job_retry.rb +228 -0
  23. data/lib/sidekiq/launcher.rb +1 -7
  24. data/lib/sidekiq/logging.rb +12 -0
  25. data/lib/sidekiq/middleware/server/active_record.rb +9 -0
  26. data/lib/sidekiq/processor.rb +63 -33
  27. data/lib/sidekiq/rails.rb +1 -73
  28. data/lib/sidekiq/redis_connection.rb +14 -3
  29. data/lib/sidekiq/testing.rb +12 -3
  30. data/lib/sidekiq/util.rb +1 -2
  31. data/lib/sidekiq/version.rb +1 -1
  32. data/lib/sidekiq/web/action.rb +0 -4
  33. data/lib/sidekiq/web/application.rb +8 -13
  34. data/lib/sidekiq/web/helpers.rb +54 -16
  35. data/lib/sidekiq/worker.rb +99 -16
  36. data/sidekiq.gemspec +3 -7
  37. data/web/assets/javascripts/dashboard.js +17 -12
  38. data/web/assets/stylesheets/application-rtl.css +246 -0
  39. data/web/assets/stylesheets/application.css +336 -4
  40. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  41. data/web/locales/ar.yml +80 -0
  42. data/web/locales/fa.yml +1 -0
  43. data/web/locales/he.yml +79 -0
  44. data/web/locales/ur.yml +80 -0
  45. data/web/views/_footer.erb +2 -2
  46. data/web/views/_nav.erb +1 -1
  47. data/web/views/_paging.erb +1 -1
  48. data/web/views/busy.erb +9 -5
  49. data/web/views/dashboard.erb +1 -1
  50. data/web/views/layout.erb +10 -1
  51. data/web/views/morgue.erb +4 -4
  52. data/web/views/queue.erb +7 -7
  53. data/web/views/retries.erb +5 -5
  54. data/web/views/scheduled.erb +2 -2
  55. metadata +20 -83
  56. data/lib/sidekiq/middleware/server/logging.rb +0 -31
  57. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
@@ -61,8 +61,6 @@ module Sidekiq
61
61
 
62
62
  private unless $TESTING
63
63
 
64
- JVM_RESERVED_SIGNALS = ['USR1', 'USR2'] # Don't Process#kill if we get these signals via the API
65
-
66
64
  def heartbeat
67
65
  results = Sidekiq::CLI::PROCTITLES.map {|x| x.(self, to_data) }
68
66
  results.compact!
@@ -110,11 +108,7 @@ module Sidekiq
110
108
 
111
109
  return unless msg
112
110
 
113
- if JVM_RESERVED_SIGNALS.include?(msg)
114
- Sidekiq::CLI.instance.handle_signal(msg)
115
- else
116
- ::Process.kill(msg, $$)
117
- end
111
+ ::Process.kill(msg, $$)
118
112
  rescue => e
119
113
  # ignore all redis/network issues
120
114
  logger.error("heartbeat: #{e.message}")
@@ -26,6 +26,18 @@ module Sidekiq
26
26
  end
27
27
  end
28
28
 
29
+ def self.job_hash_context(job_hash)
30
+ # If we're using a wrapper class, like ActiveJob, use the "wrapped"
31
+ # attribute to expose the underlying thing.
32
+ klass = job_hash['wrapped'.freeze] || job_hash["class".freeze]
33
+ bid = job_hash['bid'.freeze]
34
+ "#{klass} JID-#{job_hash['jid'.freeze]}#{" BID-#{bid}" if bid}"
35
+ end
36
+
37
+ def self.with_job_hash_context(job_hash, &block)
38
+ with_context(job_hash_context(job_hash), &block)
39
+ end
40
+
29
41
  def self.with_context(msg)
30
42
  Thread.current[:sidekiq_context] ||= []
31
43
  Thread.current[:sidekiq_context] << msg
@@ -2,6 +2,15 @@ module Sidekiq
2
2
  module Middleware
3
3
  module Server
4
4
  class ActiveRecord
5
+
6
+ def initialize
7
+ # With Rails 5+ we must use the Reloader **always**.
8
+ # The reloader handles code loading and db connection management.
9
+ if ::Rails::VERSION::MAJOR >= 5
10
+ raise ArgumentError, "Rails 5 no longer needs or uses the ActiveRecord middleware."
11
+ end
12
+ end
13
+
5
14
  def call(*args)
6
15
  yield
7
16
  ensure
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  require 'sidekiq/util'
3
3
  require 'sidekiq/fetch'
4
+ require 'sidekiq/job_logger'
5
+ require 'sidekiq/job_retry'
4
6
  require 'thread'
5
7
  require 'concurrent/map'
6
8
  require 'concurrent/atomic/atomic_fixnum'
@@ -37,7 +39,8 @@ module Sidekiq
37
39
  @thread = nil
38
40
  @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
39
41
  @reloader = Sidekiq.options[:reloader]
40
- @executor = Sidekiq.options[:executor]
42
+ @logging = (mgr.options[:job_logger] || Sidekiq::JobLogger).new
43
+ @retrier = Sidekiq::JobRetry.new
41
44
  end
42
45
 
43
46
  def terminate(wait=false)
@@ -116,32 +119,56 @@ module Sidekiq
116
119
  nil
117
120
  end
118
121
 
122
+ def dispatch(job_hash, queue)
123
+ # since middleware can mutate the job hash
124
+ # we clone here so we report the original
125
+ # job structure to the Web UI
126
+ pristine = cloned(job_hash)
127
+
128
+ Sidekiq::Logging.with_job_hash_context(job_hash) do
129
+ @retrier.global(pristine, queue) do
130
+ @logging.call(job_hash, queue) do
131
+ stats(pristine, queue) do
132
+ # Rails 5 requires a Reloader to wrap code execution. In order to
133
+ # constantize the worker and instantiate an instance, we have to call
134
+ # the Reloader. It handles code loading, db connection management, etc.
135
+ # Effectively this block denotes a "unit of work" to Rails.
136
+ @reloader.call do
137
+ klass = constantize(job_hash['class'.freeze])
138
+ worker = klass.new
139
+ worker.jid = job_hash['jid'.freeze]
140
+ @retrier.local(worker, pristine, queue) do
141
+ yield worker
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+
119
150
  def process(work)
120
151
  jobstr = work.job
121
152
  queue = work.queue_name
122
153
 
123
154
  ack = false
124
155
  begin
125
- job_hash = Sidekiq.load_json(jobstr)
126
- @reloader.call do
127
- klass = job_hash['class'.freeze].constantize
128
- worker = klass.new
129
- worker.jid = job_hash['jid'.freeze]
130
-
131
- stats(worker, job_hash, queue) do
132
- Sidekiq::Logging.with_context(log_context(job_hash)) do
133
- ack = true
134
- Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
135
- @executor.call do
136
- # Only ack if we either attempted to start this job or
137
- # successfully completed it. This prevents us from
138
- # losing jobs if a middleware raises an exception before yielding
139
- execute_job(worker, cloned(job_hash['args'.freeze]))
140
- end
141
- end
142
- end
143
- end
156
+ # Treat malformed JSON as a special case: job goes straight to the morgue.
157
+ job_hash = nil
158
+ begin
159
+ job_hash = Sidekiq.load_json(jobstr)
160
+ rescue => ex
161
+ handle_exception(ex, { :context => "Invalid JSON for job", :jobstr => jobstr })
162
+ DeadSet.new.kill(jobstr)
144
163
  ack = true
164
+ raise
165
+ end
166
+
167
+ ack = true
168
+ dispatch(job_hash, queue) do |worker|
169
+ Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
170
+ execute_job(worker, cloned(job_hash['args'.freeze]))
171
+ end
145
172
  end
146
173
  rescue Sidekiq::Shutdown
147
174
  # Had to force kill this job because it didn't finish
@@ -149,20 +176,14 @@ module Sidekiq
149
176
  # we didn't properly finish it.
150
177
  ack = false
151
178
  rescue Exception => ex
152
- handle_exception(ex, { :context => "Job raised exception", :job => job_hash, :jobstr => jobstr })
153
- raise
179
+ e = ex.is_a?(::Sidekiq::JobRetry::Skip) && ex.cause ? ex.cause : ex
180
+ handle_exception(e, { :context => "Job raised exception", :job => job_hash, :jobstr => jobstr })
181
+ raise e
154
182
  ensure
155
183
  work.acknowledge if ack
156
184
  end
157
185
  end
158
186
 
159
- # If we're using a wrapper class, like ActiveJob, use the "wrapped"
160
- # attribute to expose the underlying thing.
161
- def log_context(item)
162
- klass = item['wrapped'.freeze] || item['class'.freeze]
163
- "#{klass} JID-#{item['jid'.freeze]}#{" BID-#{item['bid'.freeze]}" if item['bid'.freeze]}"
164
- end
165
-
166
187
  def execute_job(worker, cloned_args)
167
188
  worker.perform(*cloned_args)
168
189
  end
@@ -175,9 +196,9 @@ module Sidekiq
175
196
  PROCESSED = Concurrent::AtomicFixnum.new
176
197
  FAILURE = Concurrent::AtomicFixnum.new
177
198
 
178
- def stats(worker, job_hash, queue)
199
+ def stats(job_hash, queue)
179
200
  tid = thread_identity
180
- WORKER_STATE[tid] = {:queue => queue, :payload => cloned(job_hash), :run_at => Time.now.to_i }
201
+ WORKER_STATE[tid] = {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i }
181
202
 
182
203
  begin
183
204
  yield
@@ -193,8 +214,17 @@ module Sidekiq
193
214
  # Deep clone the arguments passed to the worker so that if
194
215
  # the job fails, what is pushed back onto Redis hasn't
195
216
  # been mutated by the worker.
196
- def cloned(ary)
197
- Marshal.load(Marshal.dump(ary))
217
+ def cloned(thing)
218
+ Marshal.load(Marshal.dump(thing))
219
+ end
220
+
221
+ def constantize(str)
222
+ names = str.split('::')
223
+ names.shift if names.empty? || names.first.empty?
224
+
225
+ names.inject(Object) do |constant, name|
226
+ constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
227
+ end
198
228
  end
199
229
 
200
230
  end
@@ -1,36 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Sidekiq
3
- def self.hook_rails!
4
- return if defined?(@delay_removed)
5
-
6
- ActiveSupport.on_load(:active_record) do
7
- include Sidekiq::Extensions::ActiveRecord
8
- end
9
-
10
- ActiveSupport.on_load(:action_mailer) do
11
- extend Sidekiq::Extensions::ActionMailer
12
- end
13
-
14
- Module.__send__(:include, Sidekiq::Extensions::Klass)
15
- end
16
-
17
- # Removes the generic aliases which MAY clash with names of already
18
- # created methods by other applications. The methods `sidekiq_delay`,
19
- # `sidekiq_delay_for` and `sidekiq_delay_until` can be used instead.
20
- def self.remove_delay!
21
- @delay_removed = true
22
-
23
- [Extensions::ActiveRecord,
24
- Extensions::ActionMailer,
25
- Extensions::Klass].each do |mod|
26
- mod.module_eval do
27
- remove_method :delay if respond_to?(:delay)
28
- remove_method :delay_for if respond_to?(:delay_for)
29
- remove_method :delay_until if respond_to?(:delay_until)
30
- end
31
- end
32
- end
33
-
34
3
  class Rails < ::Rails::Engine
35
4
  # We need to setup this up before any application configuration which might
36
5
  # change Sidekiq middleware.
@@ -48,10 +17,6 @@ module Sidekiq
48
17
  end
49
18
  end
50
19
 
51
- initializer 'sidekiq' do
52
- Sidekiq.hook_rails!
53
- end
54
-
55
20
  config.after_initialize do
56
21
  # This hook happens after all initializers are run, just before returning
57
22
  # from config/environment.rb back to sidekiq/cli.rb.
@@ -62,38 +27,9 @@ module Sidekiq
62
27
  #
63
28
  Sidekiq.configure_server do |_|
64
29
  if ::Rails::VERSION::MAJOR >= 5
65
- # The reloader also takes care of ActiveRecord but is incompatible with
66
- # the ActiveRecord middleware so make sure it's not in the chain already.
67
- if defined?(Sidekiq::Middleware::Server::ActiveRecord) && Sidekiq.server_middleware.exists?(Sidekiq::Middleware::Server::ActiveRecord)
68
- raise ArgumentError, "You are using the Sidekiq ActiveRecord middleware and the new Rails 5 reloader which are incompatible. Please remove the ActiveRecord middleware from your Sidekiq middleware configuration."
69
- elsif ::Rails.application.config.cache_classes
70
- # The reloader API has proven to be troublesome under load in production.
71
- # We won't use it at all when classes are cached, see #3154
72
- Sidekiq.logger.debug { "Autoload disabled in #{::Rails.env}, Sidekiq will not reload changed classes" }
73
- Sidekiq.options[:executor] = Sidekiq::Rails::Executor.new
74
- else
75
- Sidekiq.logger.debug { "Enabling Rails 5+ live code reloading, so hot!" }
76
- Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
77
- Psych::Visitors::ToRuby.prepend(Sidekiq::Rails::PsychAutoload)
78
- end
79
- end
80
- end
81
- end
82
-
83
- class Executor
84
- def initialize(app = ::Rails.application)
85
- @app = app
86
- end
87
-
88
- def call
89
- @app.executor.wrap do
90
- yield
30
+ Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new
91
31
  end
92
32
  end
93
-
94
- def inspect
95
- "#<Sidekiq::Rails::Executor @app=#{@app.class.name}>"
96
- end
97
33
  end
98
34
 
99
35
  class Reloader
@@ -111,13 +47,5 @@ module Sidekiq
111
47
  "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
112
48
  end
113
49
  end
114
-
115
- module PsychAutoload
116
- def resolve_class(klass_name)
117
- klass_name && klass_name.constantize
118
- rescue NameError
119
- super
120
- end
121
- end
122
50
  end if defined?(::Rails)
123
51
  end
@@ -8,8 +8,11 @@ module Sidekiq
8
8
  class << self
9
9
 
10
10
  def create(options={})
11
- options = options.symbolize_keys
11
+ options.keys.each do |key|
12
+ options[key.to_sym] = options.delete(key)
13
+ end
12
14
 
15
+ options[:id] = "Sidekiq-#{Sidekiq.server? ? "server" : "client"}-PID-#{$$}" if !options.has_key?(:id)
13
16
  options[:url] ||= determine_redis_provider
14
17
 
15
18
  size = options[:size] || (Sidekiq.server? ? (Sidekiq.options[:concurrency] + 5) : 5)
@@ -67,7 +70,7 @@ module Sidekiq
67
70
  opts.delete(:network_timeout)
68
71
  end
69
72
 
70
- opts[:driver] ||= 'ruby'
73
+ opts[:driver] ||= 'ruby'.freeze
71
74
 
72
75
  # Issue #3303, redis-rb will silently retry an operation.
73
76
  # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
@@ -98,7 +101,15 @@ module Sidekiq
98
101
  end
99
102
 
100
103
  def determine_redis_provider
101
- ENV[ENV['REDIS_PROVIDER'] || 'REDIS_URL']
104
+ # If you have this in your environment:
105
+ # MY_REDIS_URL=redis://hostname.example.com:1238/4
106
+ # then set:
107
+ # REDIS_PROVIDER=MY_REDIS_URL
108
+ # and Sidekiq will find your custom URL variable with no custom
109
+ # initialization code at all.
110
+ ENV[
111
+ ENV['REDIS_PROVIDER'] || 'REDIS_URL'
112
+ ]
102
113
  end
103
114
 
104
115
  end
@@ -55,6 +55,15 @@ module Sidekiq
55
55
  yield @server_chain if block_given?
56
56
  @server_chain
57
57
  end
58
+
59
+ def constantize(str)
60
+ names = str.split('::')
61
+ names.shift if names.empty? || names.first.empty?
62
+
63
+ names.inject(Object) do |constant, name|
64
+ constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
65
+ end
66
+ end
58
67
  end
59
68
  end
60
69
 
@@ -76,7 +85,7 @@ module Sidekiq
76
85
  true
77
86
  elsif Sidekiq::Testing.inline?
78
87
  payloads.each do |job|
79
- klass = job['class'].constantize
88
+ klass = Sidekiq::Testing.constantize(job['class'])
80
89
  job['id'] ||= SecureRandom.hex(12)
81
90
  job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
82
91
  klass.process_job(job_hash)
@@ -309,7 +318,7 @@ module Sidekiq
309
318
  worker_classes = jobs.map { |job| job["class"] }.uniq
310
319
 
311
320
  worker_classes.each do |worker_class|
312
- worker_class.constantize.drain
321
+ Sidekiq::Testing.constantize(worker_class).drain
313
322
  end
314
323
  end
315
324
  end
@@ -317,7 +326,7 @@ module Sidekiq
317
326
  end
318
327
  end
319
328
 
320
- if defined?(::Rails) && !Rails.env.test?
329
+ if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test?
321
330
  puts("**************************************************")
322
331
  puts("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.")
323
332
  puts("**************************************************")
@@ -2,7 +2,6 @@
2
2
  require 'socket'
3
3
  require 'securerandom'
4
4
  require 'sidekiq/exception_handler'
5
- require 'sidekiq/core_ext'
6
5
 
7
6
  module Sidekiq
8
7
  ##
@@ -22,7 +21,7 @@ module Sidekiq
22
21
 
23
22
  def safe_thread(name, &block)
24
23
  Thread.new do
25
- Thread.current['sidekiq_label'] = name
24
+ Thread.current['sidekiq_label'.freeze] = name
26
25
  watchdog(name, &block)
27
26
  end
28
27
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sidekiq
3
- VERSION = "4.2.10"
3
+ VERSION = "5.0.5"
4
4
  end
@@ -39,10 +39,6 @@ module Sidekiq
39
39
  env[RACK_SESSION]
40
40
  end
41
41
 
42
- def content_type(type)
43
- @type = type
44
- end
45
-
46
42
  def erb(content, options = {})
47
43
  if content.kind_of? Symbol
48
44
  unless respond_to?(:"_erb_#{content}")
@@ -234,7 +234,6 @@ module Sidekiq
234
234
  get '/stats' do
235
235
  sidekiq_stats = Sidekiq::Stats.new
236
236
  redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
237
-
238
237
  json(
239
238
  sidekiq: {
240
239
  processed: sidekiq_stats.processed,
@@ -247,7 +246,8 @@ module Sidekiq
247
246
  dead: sidekiq_stats.dead_size,
248
247
  default_latency: sidekiq_stats.default_queue_latency
249
248
  },
250
- redis: redis_stats
249
+ redis: redis_stats,
250
+ server_utc_time: server_utc_time
251
251
  )
252
252
  end
253
253
 
@@ -274,19 +274,14 @@ module Sidekiq
274
274
  resp = case resp
275
275
  when Array
276
276
  resp
277
- when Integer
278
- [resp, {}, []]
279
277
  else
280
- type_header = case action.type
281
- when :json
282
- { "Content-Type" => "application/json", "Cache-Control" => "no-cache" }
283
- when String
284
- { "Content-Type" => (action.type || "text/html"), "Cache-Control" => "no-cache" }
285
- else
286
- { "Content-Type" => "text/html", "Cache-Control" => "no-cache" }
287
- end
278
+ headers = {
279
+ "Content-Type" => "text/html",
280
+ "Cache-Control" => "no-cache",
281
+ "Content-Language" => action.locale,
282
+ }
288
283
 
289
- [200, type_header, [resp]]
284
+ [200, headers, [resp]]
290
285
  end
291
286
 
292
287
  resp[1] = resp[1].dup