sidekiq 7.3.7 → 8.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +30 -0
  3. data/README.md +16 -13
  4. data/bin/sidekiqload +10 -10
  5. data/bin/webload +69 -0
  6. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +5 -18
  7. data/lib/sidekiq/api.rb +108 -36
  8. data/lib/sidekiq/capsule.rb +1 -1
  9. data/lib/sidekiq/cli.rb +15 -19
  10. data/lib/sidekiq/client.rb +13 -16
  11. data/lib/sidekiq/component.rb +30 -2
  12. data/lib/sidekiq/config.rb +18 -15
  13. data/lib/sidekiq/embedded.rb +1 -0
  14. data/lib/sidekiq/job/iterable.rb +3 -5
  15. data/lib/sidekiq/job_retry.rb +2 -2
  16. data/lib/sidekiq/job_util.rb +5 -1
  17. data/lib/sidekiq/launcher.rb +1 -1
  18. data/lib/sidekiq/logger.rb +6 -10
  19. data/lib/sidekiq/manager.rb +0 -1
  20. data/lib/sidekiq/metrics/query.rb +1 -3
  21. data/lib/sidekiq/middleware/current_attributes.rb +5 -17
  22. data/lib/sidekiq/paginator.rb +14 -1
  23. data/lib/sidekiq/processor.rb +21 -14
  24. data/lib/sidekiq/profiler.rb +59 -0
  25. data/lib/sidekiq/rails.rb +12 -2
  26. data/lib/sidekiq/redis_client_adapter.rb +0 -1
  27. data/lib/sidekiq/testing.rb +2 -2
  28. data/lib/sidekiq/version.rb +2 -2
  29. data/lib/sidekiq/web/action.rb +101 -85
  30. data/lib/sidekiq/web/application.rb +339 -333
  31. data/lib/sidekiq/web/config.rb +116 -0
  32. data/lib/sidekiq/web/helpers.rb +45 -20
  33. data/lib/sidekiq/web/router.rb +60 -76
  34. data/lib/sidekiq/web.rb +51 -156
  35. data/sidekiq.gemspec +6 -4
  36. data/web/assets/javascripts/application.js +6 -13
  37. data/web/assets/javascripts/base-charts.js +30 -18
  38. data/web/assets/javascripts/dashboard-charts.js +2 -0
  39. data/web/assets/javascripts/dashboard.js +6 -0
  40. data/web/assets/javascripts/metrics.js +1 -1
  41. data/web/assets/stylesheets/style.css +750 -0
  42. data/web/locales/ar.yml +1 -0
  43. data/web/locales/cs.yml +1 -0
  44. data/web/locales/da.yml +1 -0
  45. data/web/locales/de.yml +1 -0
  46. data/web/locales/el.yml +1 -0
  47. data/web/locales/en.yml +9 -0
  48. data/web/locales/es.yml +24 -2
  49. data/web/locales/fa.yml +1 -0
  50. data/web/locales/fr.yml +1 -0
  51. data/web/locales/gd.yml +1 -0
  52. data/web/locales/he.yml +1 -0
  53. data/web/locales/hi.yml +1 -0
  54. data/web/locales/it.yml +1 -0
  55. data/web/locales/ja.yml +1 -0
  56. data/web/locales/ko.yml +1 -0
  57. data/web/locales/lt.yml +1 -0
  58. data/web/locales/nb.yml +1 -0
  59. data/web/locales/nl.yml +1 -0
  60. data/web/locales/pl.yml +1 -0
  61. data/web/locales/{pt-br.yml → pt-BR.yml} +2 -1
  62. data/web/locales/pt.yml +1 -0
  63. data/web/locales/ru.yml +1 -0
  64. data/web/locales/sv.yml +1 -0
  65. data/web/locales/ta.yml +1 -0
  66. data/web/locales/tr.yml +1 -0
  67. data/web/locales/uk.yml +1 -0
  68. data/web/locales/ur.yml +1 -0
  69. data/web/locales/vi.yml +1 -0
  70. data/web/locales/{zh-cn.yml → zh-CN.yml} +85 -73
  71. data/web/locales/{zh-tw.yml → zh-TW.yml} +2 -1
  72. data/web/views/_footer.erb +31 -34
  73. data/web/views/_job_info.erb +91 -89
  74. data/web/views/_metrics_period_select.erb +1 -1
  75. data/web/views/_nav.erb +14 -21
  76. data/web/views/_paging.erb +23 -21
  77. data/web/views/_poll_link.erb +2 -2
  78. data/web/views/_summary.erb +16 -16
  79. data/web/views/busy.erb +124 -122
  80. data/web/views/dashboard.erb +62 -64
  81. data/web/views/dead.erb +31 -27
  82. data/web/views/filtering.erb +3 -3
  83. data/web/views/layout.erb +5 -21
  84. data/web/views/metrics.erb +83 -80
  85. data/web/views/metrics_for_job.erb +39 -42
  86. data/web/views/morgue.erb +61 -70
  87. data/web/views/profiles.erb +43 -0
  88. data/web/views/queue.erb +54 -52
  89. data/web/views/queues.erb +43 -41
  90. data/web/views/retries.erb +66 -75
  91. data/web/views/retry.erb +32 -27
  92. data/web/views/scheduled.erb +58 -54
  93. data/web/views/scheduled_job_info.erb +1 -1
  94. metadata +46 -22
  95. data/web/assets/stylesheets/application-dark.css +0 -147
  96. data/web/assets/stylesheets/application-rtl.css +0 -163
  97. data/web/assets/stylesheets/application.css +0 -759
  98. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  99. data/web/assets/stylesheets/bootstrap.css +0 -5
  100. data/web/views/_status.erb +0 -4
@@ -67,9 +67,7 @@ module Sidekiq
67
67
  c.pipelined do |p|
68
68
  p.hsetnx(key, "cancelled", Time.now.to_i)
69
69
  p.hget(key, "cancelled")
70
- p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
71
- # TODO When Redis 7.2 is required
72
- # p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
70
+ p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
73
71
  end
74
72
  end
75
73
  result.to_i
@@ -266,22 +264,21 @@ module Sidekiq
266
264
  if payloads.first.key?("at")
267
265
  conn.zadd("schedule", payloads.flat_map { |hash|
268
266
  at = hash["at"].to_s
269
- # ActiveJob sets this but the job has not been enqueued yet
270
- hash.delete("enqueued_at")
271
- # TODO: Use hash.except("at") when support for Ruby 2.7 is dropped
272
- hash = hash.dup
273
- hash.delete("at")
267
+ # ActiveJob sets enqueued_at but the job has not been enqueued yet
268
+ hash = hash.except("enqueued_at", "at")
274
269
  [at, Sidekiq.dump_json(hash)]
275
270
  })
276
271
  else
277
- queue = payloads.first["queue"]
278
- now = Time.now.to_f
279
- to_push = payloads.map { |entry|
280
- entry["enqueued_at"] = now
281
- Sidekiq.dump_json(entry)
282
- }
283
- conn.sadd("queues", [queue])
284
- conn.lpush("queue:#{queue}", to_push)
272
+ now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) # milliseconds since the epoch
273
+ grouped_queues = payloads.group_by { |job| job["queue"] }
274
+ conn.sadd("queues", grouped_queues.keys)
275
+ grouped_queues.each do |queue, grouped_payloads|
276
+ to_push = grouped_payloads.map { |entry|
277
+ entry["enqueued_at"] = now
278
+ Sidekiq.dump_json(entry)
279
+ }
280
+ conn.lpush("queue:#{queue}", to_push)
281
+ end
285
282
  end
286
283
  end
287
284
  end
@@ -1,6 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
+ # Ruby's default thread priority is 0, which uses 100ms time slices.
5
+ # This can lead to some surprising thread starvation; if using a lot of
6
+ # CPU-heavy concurrency, it may take several seconds before a Thread gets
7
+ # on the CPU.
8
+ #
9
+ # Negative priorities lower the timeslice by half, so -1 = 50ms, -2 = 25ms, etc.
10
+ # With more frequent timeslices, we reduce the risk of unintentional timeouts
11
+ # and starvation.
12
+ #
13
+ # Customize like so:
14
+ #
15
+ # Sidekiq.configure_server do |cfg|
16
+ # cfg.thread_priority = 0
17
+ # end
18
+ #
19
+ DEFAULT_THREAD_PRIORITY = -1
20
+
4
21
  ##
5
22
  # Sidekiq::Component assumes a config instance is available at @config
6
23
  module Component # :nodoc:
@@ -13,11 +30,11 @@ module Sidekiq
13
30
  raise ex
14
31
  end
15
32
 
16
- def safe_thread(name, &block)
33
+ def safe_thread(name, priority: nil, &block)
17
34
  Thread.new do
18
35
  Thread.current.name = "sidekiq.#{name}"
19
36
  watchdog(name, &block)
20
- end
37
+ end.tap { |t| t.priority = (priority || config.thread_priority || DEFAULT_THREAD_PRIORITY) }
21
38
  end
22
39
 
23
40
  def logger
@@ -86,5 +103,16 @@ module Sidekiq
86
103
  end.join(", ")
87
104
  }>"
88
105
  end
106
+
107
+ def default_tag(dir = Dir.pwd)
108
+ name = File.basename(dir)
109
+ prevdir = File.dirname(dir) # Capistrano release directory?
110
+ if name.to_i != 0 && prevdir
111
+ if File.basename(prevdir) == "releases"
112
+ return File.basename(File.dirname(prevdir))
113
+ end
114
+ end
115
+ name
116
+ end
89
117
  end
90
118
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "forwardable"
4
-
5
- require "set"
6
4
  require "sidekiq/redis_connection"
7
5
 
8
6
  module Sidekiq
@@ -41,12 +39,22 @@ module Sidekiq
41
39
  }
42
40
 
43
41
  ERROR_HANDLER = ->(ex, ctx, cfg = Sidekiq.default_configuration) {
44
- l = cfg.logger
45
- l.warn(Sidekiq.dump_json(ctx)) unless ctx.empty?
46
- l.warn("#{ex.class.name}: #{ex.message}")
47
- unless ex.backtrace.nil?
48
- backtrace = cfg[:backtrace_cleaner].call(ex.backtrace)
49
- l.warn(backtrace.join("\n"))
42
+ Sidekiq::Context.with(ctx) do
43
+ dev = cfg[:environment] == "development"
44
+ fancy = dev && $stdout.tty? # 🎩
45
+ # Weird logic here but we want to show the backtrace in local
46
+ # development or if verbose logging is enabled.
47
+ #
48
+ # `full_message` contains the error class, message and backtrace
49
+ # `detailed_message` contains the error class and message
50
+ #
51
+ # Absolutely terrible API names. Not useful at all to have two
52
+ # methods with similar but obscure names.
53
+ if dev || cfg.logger.debug?
54
+ cfg.logger.info { ex.full_message(highlight: fancy) }
55
+ else
56
+ cfg.logger.info { ex.detailed_message(highlight: fancy) }
57
+ end
50
58
  end
51
59
  }
52
60
 
@@ -60,6 +68,7 @@ module Sidekiq
60
68
 
61
69
  def_delegators :@options, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
62
70
  attr_reader :capsules
71
+ attr_accessor :thread_priority
63
72
 
64
73
  def inspect
65
74
  "#<#{self.class.name} @options=#{
@@ -293,13 +302,7 @@ module Sidekiq
293
302
  p ["!!!!!", ex]
294
303
  end
295
304
  @options[:error_handlers].each do |handler|
296
- if parameter_size(handler) == 2
297
- # TODO Remove in 8.0
298
- logger.info { "DEPRECATION: Sidekiq exception handlers now take three arguments, see #{handler}" }
299
- handler.call(ex, {_config: self}.merge(ctx))
300
- else
301
- handler.call(ex, ctx, self)
302
- end
305
+ handler.call(ex, ctx, self)
303
306
  rescue Exception => e
304
307
  l = logger
305
308
  l.error "!!! ERROR HANDLER THREW AN ERROR !!!"
@@ -34,6 +34,7 @@ module Sidekiq
34
34
  private
35
35
 
36
36
  def housekeeping
37
+ @config[:tag] ||= default_tag
37
38
  logger.info "Running in #{RUBY_DESCRIPTION}"
38
39
  logger.info Sidekiq::LICENSE
39
40
  logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro)
@@ -54,9 +54,7 @@ module Sidekiq
54
54
  c.pipelined do |p|
55
55
  p.hsetnx(key, "cancelled", Time.now.to_i)
56
56
  p.hget(key, "cancelled")
57
- # TODO When Redis 7.2 is required
58
- # p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
59
- p.expire(key, Sidekiq::Job::Iterable::STATE_TTL)
57
+ p.expire(key, Sidekiq::Job::Iterable::STATE_TTL, "nx")
60
58
  end
61
59
  end
62
60
  @_cancelled = result.to_i
@@ -200,7 +198,7 @@ module Sidekiq
200
198
  if ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - state_flushed_at >= STATE_FLUSH_INTERVAL || is_interrupted
201
199
  _, _, cancelled = flush_state
202
200
  state_flushed_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
203
- if cancelled == 1
201
+ if cancelled
204
202
  @_cancelled = true
205
203
  logger.info { "Job cancelled" }
206
204
  return true
@@ -265,7 +263,7 @@ module Sidekiq
265
263
  Sidekiq.redis do |conn|
266
264
  conn.multi do |pipe|
267
265
  pipe.hset(key, state)
268
- pipe.expire(key, STATE_TTL)
266
+ pipe.expire(key, STATE_TTL, "nx")
269
267
  pipe.hget(key, "cancelled")
270
268
  end
271
269
  end
@@ -149,7 +149,7 @@ module Sidekiq
149
149
 
150
150
  m = exception_message(exception)
151
151
  if m.respond_to?(:scrub!)
152
- m.force_encoding("utf-8")
152
+ m.force_encoding(Encoding::UTF_8)
153
153
  m.scrub!
154
154
  end
155
155
 
@@ -189,7 +189,7 @@ module Sidekiq
189
189
 
190
190
  # Logging here can break retries if the logging device raises ENOSPC #3979
191
191
  # logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
192
- jitter = rand(10) * (count + 1)
192
+ jitter = rand(10 * (count + 1))
193
193
  retry_at = Time.now.to_f + delay + jitter
194
194
  payload = Sidekiq.dump_json(msg)
195
195
  redis do |conn|
@@ -58,10 +58,14 @@ module Sidekiq
58
58
  item["class"] = item["class"].to_s
59
59
  item["queue"] = item["queue"].to_s
60
60
  item["retry_for"] = item["retry_for"].to_i if item["retry_for"]
61
- item["created_at"] ||= Time.now.to_f
61
+ item["created_at"] ||= now_in_millis
62
62
  item
63
63
  end
64
64
 
65
+ def now_in_millis
66
+ ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
67
+ end
68
+
65
69
  def normalized_hash(item_class)
66
70
  if item_class.is_a?(Class)
67
71
  raise(ArgumentError, "Message must include a Sidekiq::Job class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?(:get_sidekiq_options)
@@ -81,7 +81,7 @@ module Sidekiq
81
81
 
82
82
  end
83
83
 
84
- private unless $TESTING
84
+ private
85
85
 
86
86
  BEAT_PAUSE = 10
87
87
 
@@ -81,13 +81,9 @@ module Sidekiq
81
81
  Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
82
82
  end
83
83
 
84
- def ctx
85
- Sidekiq::Context.current
86
- end
87
-
88
- def format_context
89
- if ctx.any?
90
- " " + ctx.compact.map { |k, v|
84
+ def format_context(ctxt = Sidekiq::Context.current)
85
+ if ctxt.size > 0
86
+ ctxt.map { |k, v|
91
87
  case v
92
88
  when Array
93
89
  "#{k}=#{v.join(",")}"
@@ -101,13 +97,13 @@ module Sidekiq
101
97
 
102
98
  class Pretty < Base
103
99
  def call(severity, time, program_name, message)
104
- "#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
100
+ "#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid} #{format_context} #{severity}: #{message}\n"
105
101
  end
106
102
  end
107
103
 
108
104
  class WithoutTimestamp < Pretty
109
105
  def call(severity, time, program_name, message)
110
- "pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n"
106
+ "pid=#{::Process.pid} tid=#{tid} #{format_context} #{severity}: #{message}\n"
111
107
  end
112
108
  end
113
109
 
@@ -120,7 +116,7 @@ module Sidekiq
120
116
  lvl: severity,
121
117
  msg: message
122
118
  }
123
- c = ctx
119
+ c = Sidekiq::Context.current
124
120
  hash["ctx"] = c unless c.empty?
125
121
 
126
122
  Sidekiq.dump_json(hash) << "\n"
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq/processor"
4
- require "set"
5
4
 
6
5
  module Sidekiq
7
6
  ##
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq"
4
3
  require "date"
5
- require "set"
6
-
4
+ require "sidekiq"
7
5
  require "sidekiq/metrics/shared"
8
6
 
9
7
  module Sidekiq
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_job/arguments"
3
4
  require "active_support/current_attributes"
4
5
 
5
6
  module Sidekiq
@@ -20,6 +21,8 @@ module Sidekiq
20
21
  # Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
21
22
  #
22
23
  module CurrentAttributes
24
+ Serializer = ::ActiveJob::Arguments
25
+
23
26
  class Save
24
27
  include Sidekiq::ClientMiddleware
25
28
 
@@ -33,26 +36,11 @@ module Sidekiq
33
36
  attrs = strklass.constantize.attributes
34
37
  # Retries can push the job N times, we don't
35
38
  # want retries to reset cattr. #5692, #5090
36
- if attrs.any?
37
- # Older rails has a bug that `CurrentAttributes#attributes` always returns
38
- # the same hash instance. We need to dup it to avoid being accidentally mutated.
39
- job[key] = if returns_same_object?
40
- attrs.dup
41
- else
42
- attrs
43
- end
44
- end
39
+ job[key] = Serializer.serialize(attrs) if attrs.any?
45
40
  end
46
41
  end
47
42
  yield
48
43
  end
49
-
50
- private
51
-
52
- def returns_same_object?
53
- ActiveSupport::VERSION::MAJOR < 8 ||
54
- (ActiveSupport::VERSION::MAJOR == 8 && ActiveSupport::VERSION::MINOR == 0)
55
- end
56
44
  end
57
45
 
58
46
  class Load
@@ -68,7 +56,7 @@ module Sidekiq
68
56
  @cattrs.each do |(key, strklass)|
69
57
  next unless job.has_key?(key)
70
58
 
71
- klass_attrs[strklass.constantize] = job[key]
59
+ klass_attrs[strklass.constantize] = Serializer.deserialize(job[key]).to_h
72
60
  end
73
61
 
74
62
  wrap(klass_attrs.to_a, &block)
@@ -2,6 +2,12 @@
2
2
 
3
3
  module Sidekiq
4
4
  module Paginator
5
+ TYPE_CACHE = {
6
+ "dead" => "zset",
7
+ "retry" => "zset",
8
+ "schedule" => "zset"
9
+ }
10
+
5
11
  def page(key, pageidx = 1, page_size = 25, opts = nil)
6
12
  current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
7
13
  pageidx = current_page - 1
@@ -11,7 +17,14 @@ module Sidekiq
11
17
  ending = starting + page_size - 1
12
18
 
13
19
  Sidekiq.redis do |conn|
14
- type = conn.type(key)
20
+ # horrible, think you can make this cleaner?
21
+ type = TYPE_CACHE[key]
22
+ if type
23
+ elsif key.start_with?("queue:")
24
+ type = TYPE_CACHE[key] = "list"
25
+ else
26
+ type = TYPE_CACHE[key] = conn.type(key)
27
+ end
15
28
  rev = opts && opts[:reverse]
16
29
 
17
30
  case type
@@ -3,6 +3,7 @@
3
3
  require "sidekiq/fetch"
4
4
  require "sidekiq/job_logger"
5
5
  require "sidekiq/job_retry"
6
+ require "sidekiq/profiler"
6
7
 
7
8
  module Sidekiq
8
9
  ##
@@ -66,7 +67,7 @@ module Sidekiq
66
67
  @thread ||= safe_thread("#{config.name}/processor", &method(:run))
67
68
  end
68
69
 
69
- private unless $TESTING
70
+ private
70
71
 
71
72
  def run
72
73
  # By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
@@ -112,13 +113,17 @@ module Sidekiq
112
113
  def handle_fetch_exception(ex)
113
114
  unless @down
114
115
  @down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
115
- logger.error("Error fetching job: #{ex}")
116
116
  handle_exception(ex)
117
117
  end
118
118
  sleep(1)
119
119
  nil
120
120
  end
121
121
 
122
+ def profile(job, &block)
123
+ return yield unless job["profile"]
124
+ Sidekiq::Profiler.new(config).call(job, &block)
125
+ end
126
+
122
127
  def dispatch(job_hash, queue, jobstr)
123
128
  # since middleware can mutate the job hash
124
129
  # we need to clone it to report the original
@@ -132,17 +137,19 @@ module Sidekiq
132
137
  @retrier.global(jobstr, queue) do
133
138
  @job_logger.call(job_hash, queue) do
134
139
  stats(jobstr, queue) do
135
- # Rails 5 requires a Reloader to wrap code execution. In order to
136
- # constantize the worker and instantiate an instance, we have to call
137
- # the Reloader. It handles code loading, db connection management, etc.
138
- # Effectively this block denotes a "unit of work" to Rails.
139
- @reloader.call do
140
- klass = Object.const_get(job_hash["class"])
141
- instance = klass.new
142
- instance.jid = job_hash["jid"]
143
- instance._context = self
144
- @retrier.local(instance, jobstr, queue) do
145
- yield instance
140
+ profile(job_hash) do
141
+ # Rails 5 requires a Reloader to wrap code execution. In order to
142
+ # constantize the worker and instantiate an instance, we have to call
143
+ # the Reloader. It handles code loading, db connection management, etc.
144
+ # Effectively this block denotes a "unit of work" to Rails.
145
+ @reloader.call do
146
+ klass = Object.const_get(job_hash["class"])
147
+ instance = klass.new
148
+ instance.jid = job_hash["jid"]
149
+ instance._context = self
150
+ @retrier.local(instance, jobstr, queue) do
151
+ yield instance
152
+ end
146
153
  end
147
154
  end
148
155
  end
@@ -165,7 +172,6 @@ module Sidekiq
165
172
  begin
166
173
  job_hash = Sidekiq.load_json(jobstr)
167
174
  rescue => ex
168
- handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
169
175
  now = Time.now.to_f
170
176
  redis do |conn|
171
177
  conn.multi do |xa|
@@ -174,6 +180,7 @@ module Sidekiq
174
180
  xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
175
181
  end
176
182
  end
183
+ handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
177
184
  return uow.acknowledge
178
185
  end
179
186
 
@@ -0,0 +1,59 @@
1
+ require "fileutils"
2
+ require "sidekiq/component"
3
+
4
+ module Sidekiq
5
+ # Allows the user to profile jobs running in production.
6
+ # See details in the Profiling wiki page.
7
+ class Profiler
8
+ EXPIRY = 86400 # 1 day
9
+ DEFAULT_OPTIONS = {
10
+ mode: :wall
11
+ }
12
+
13
+ include Sidekiq::Component
14
+ def initialize(config)
15
+ @config = config
16
+ end
17
+
18
+ def call(job, &block)
19
+ return yield unless job["profile"]
20
+
21
+ token = job["profile"]
22
+ type = job["class"]
23
+ jid = job["jid"]
24
+ started_at = Time.now
25
+ options = DEFAULT_OPTIONS.merge((job["profiler_options"] || {}).transform_keys!(&:to_sym))
26
+
27
+ rundata = {
28
+ started_at: started_at.to_i,
29
+ token: token,
30
+ type: type,
31
+ jid: jid,
32
+ # .gz extension tells Vernier to compress the data
33
+ filename: "#{token}-#{type}-#{jid}-#{started_at.strftime("%Y%m%d-%H%M%S")}.json.gz"
34
+ }
35
+
36
+ require "vernier"
37
+ begin
38
+ a = Time.now
39
+ rc = Vernier.profile(**options.merge(out: rundata[:filename]), &block)
40
+ b = Time.now
41
+
42
+ # Failed jobs will raise an exception on previous line and skip this
43
+ # block. Only successful jobs will persist profile data to Redis.
44
+ key = "#{token}-#{jid}"
45
+ data = File.read(rundata[:filename])
46
+ redis do |conn|
47
+ conn.multi do |m|
48
+ m.zadd("profiles", Time.now.to_f + EXPIRY, key)
49
+ m.hset(key, rundata.merge(elapsed: (b - a), data: data, size: data.bytesize))
50
+ m.expire(key, EXPIRY)
51
+ end
52
+ end
53
+ rc
54
+ ensure
55
+ FileUtils.rm_f(rundata[:filename])
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/sidekiq/rails.rb CHANGED
@@ -4,6 +4,17 @@ require "sidekiq/job"
4
4
  require "rails"
5
5
 
6
6
  module Sidekiq
7
+ module ActiveJob
8
+ # @api private
9
+ class Wrapper
10
+ include Sidekiq::Job
11
+
12
+ def perform(job_data)
13
+ ::ActiveJob::Base.execute(job_data.merge("provider_job_id" => jid))
14
+ end
15
+ end
16
+ end
17
+
7
18
  class Rails < ::Rails::Engine
8
19
  class Reloader
9
20
  def initialize(app = ::Rails.application)
@@ -39,8 +50,7 @@ module Sidekiq
39
50
  # end
40
51
  initializer "sidekiq.active_job_integration" do
41
52
  ActiveSupport.on_load(:active_job) do
42
- require "active_job/queue_adapters/sidekiq_adapter"
43
-
53
+ require_relative "../active_job/queue_adapters/sidekiq_adapter"
44
54
  include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
45
55
  end
46
56
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
3
  require "redis_client"
5
4
  require "redis_client/decorator"
6
5
 
@@ -87,7 +87,7 @@ module Sidekiq
87
87
  if Sidekiq::Testing.fake?
88
88
  payloads.each do |job|
89
89
  job = Sidekiq.load_json(Sidekiq.dump_json(job))
90
- job["enqueued_at"] = Time.now.to_f unless job["at"]
90
+ job["enqueued_at"] = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) unless job["at"]
91
91
  Queues.push(job["queue"], job["class"], job)
92
92
  end
93
93
  true
@@ -329,6 +329,6 @@ module Sidekiq
329
329
  end
330
330
  end
331
331
 
332
- if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
332
+ if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING # rubocop:disable Style/GlobalVars
333
333
  warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
334
334
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "7.3.7"
5
- MAJOR = 7
4
+ VERSION = "8.0.0.beta1"
5
+ MAJOR = 8
6
6
 
7
7
  def self.gem_version
8
8
  Gem::Version.new(VERSION)