sidekiq 6.5.12 → 7.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +303 -20
  3. data/README.md +43 -35
  4. data/bin/multi_queue_bench +271 -0
  5. data/bin/sidekiq +3 -8
  6. data/bin/sidekiqload +204 -118
  7. data/bin/sidekiqmon +3 -0
  8. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +88 -0
  9. data/lib/generators/sidekiq/job_generator.rb +2 -0
  10. data/lib/sidekiq/api.rb +196 -138
  11. data/lib/sidekiq/capsule.rb +132 -0
  12. data/lib/sidekiq/cli.rb +60 -75
  13. data/lib/sidekiq/client.rb +87 -38
  14. data/lib/sidekiq/component.rb +4 -1
  15. data/lib/sidekiq/config.rb +305 -0
  16. data/lib/sidekiq/deploy.rb +64 -0
  17. data/lib/sidekiq/embedded.rb +63 -0
  18. data/lib/sidekiq/fetch.rb +11 -14
  19. data/lib/sidekiq/iterable_job.rb +55 -0
  20. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  21. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  22. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  23. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  24. data/lib/sidekiq/job/iterable.rb +294 -0
  25. data/lib/sidekiq/job.rb +382 -10
  26. data/lib/sidekiq/job_logger.rb +23 -12
  27. data/lib/sidekiq/job_retry.rb +42 -19
  28. data/lib/sidekiq/job_util.rb +53 -15
  29. data/lib/sidekiq/launcher.rb +71 -65
  30. data/lib/sidekiq/logger.rb +2 -27
  31. data/lib/sidekiq/manager.rb +9 -11
  32. data/lib/sidekiq/metrics/query.rb +9 -4
  33. data/lib/sidekiq/metrics/shared.rb +21 -9
  34. data/lib/sidekiq/metrics/tracking.rb +40 -26
  35. data/lib/sidekiq/middleware/chain.rb +19 -18
  36. data/lib/sidekiq/middleware/current_attributes.rb +70 -20
  37. data/lib/sidekiq/middleware/modules.rb +2 -0
  38. data/lib/sidekiq/monitor.rb +18 -4
  39. data/lib/sidekiq/paginator.rb +2 -2
  40. data/lib/sidekiq/processor.rb +62 -57
  41. data/lib/sidekiq/rails.rb +21 -10
  42. data/lib/sidekiq/redis_client_adapter.rb +31 -71
  43. data/lib/sidekiq/redis_connection.rb +44 -115
  44. data/lib/sidekiq/ring_buffer.rb +2 -0
  45. data/lib/sidekiq/scheduled.rb +22 -23
  46. data/lib/sidekiq/systemd.rb +2 -0
  47. data/lib/sidekiq/testing.rb +37 -46
  48. data/lib/sidekiq/transaction_aware_client.rb +11 -5
  49. data/lib/sidekiq/version.rb +6 -1
  50. data/lib/sidekiq/web/action.rb +15 -5
  51. data/lib/sidekiq/web/application.rb +89 -17
  52. data/lib/sidekiq/web/csrf_protection.rb +10 -7
  53. data/lib/sidekiq/web/helpers.rb +102 -42
  54. data/lib/sidekiq/web/router.rb +5 -2
  55. data/lib/sidekiq/web.rb +65 -17
  56. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  57. data/lib/sidekiq.rb +78 -274
  58. data/sidekiq.gemspec +12 -10
  59. data/web/assets/javascripts/application.js +44 -0
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/dashboard-charts.js +192 -0
  62. data/web/assets/javascripts/dashboard.js +11 -233
  63. data/web/assets/javascripts/metrics.js +151 -115
  64. data/web/assets/stylesheets/application-dark.css +4 -0
  65. data/web/assets/stylesheets/application-rtl.css +10 -89
  66. data/web/assets/stylesheets/application.css +53 -298
  67. data/web/locales/ar.yml +70 -70
  68. data/web/locales/cs.yml +62 -62
  69. data/web/locales/da.yml +60 -53
  70. data/web/locales/de.yml +65 -65
  71. data/web/locales/el.yml +2 -7
  72. data/web/locales/en.yml +78 -71
  73. data/web/locales/es.yml +68 -68
  74. data/web/locales/fa.yml +65 -65
  75. data/web/locales/fr.yml +80 -67
  76. data/web/locales/gd.yml +98 -0
  77. data/web/locales/he.yml +65 -64
  78. data/web/locales/hi.yml +59 -59
  79. data/web/locales/it.yml +53 -53
  80. data/web/locales/ja.yml +67 -70
  81. data/web/locales/ko.yml +52 -52
  82. data/web/locales/lt.yml +66 -66
  83. data/web/locales/nb.yml +61 -61
  84. data/web/locales/nl.yml +52 -52
  85. data/web/locales/pl.yml +45 -45
  86. data/web/locales/pt-br.yml +78 -69
  87. data/web/locales/pt.yml +51 -51
  88. data/web/locales/ru.yml +67 -66
  89. data/web/locales/sv.yml +53 -53
  90. data/web/locales/ta.yml +60 -60
  91. data/web/locales/tr.yml +100 -0
  92. data/web/locales/uk.yml +85 -61
  93. data/web/locales/ur.yml +64 -64
  94. data/web/locales/vi.yml +67 -67
  95. data/web/locales/zh-cn.yml +20 -19
  96. data/web/locales/zh-tw.yml +10 -2
  97. data/web/views/_footer.erb +17 -2
  98. data/web/views/_job_info.erb +18 -2
  99. data/web/views/_metrics_period_select.erb +12 -0
  100. data/web/views/_paging.erb +2 -0
  101. data/web/views/_poll_link.erb +1 -1
  102. data/web/views/_summary.erb +7 -7
  103. data/web/views/busy.erb +46 -35
  104. data/web/views/dashboard.erb +28 -7
  105. data/web/views/filtering.erb +7 -0
  106. data/web/views/layout.erb +6 -6
  107. data/web/views/metrics.erb +48 -26
  108. data/web/views/metrics_for_job.erb +43 -71
  109. data/web/views/morgue.erb +5 -9
  110. data/web/views/queue.erb +10 -14
  111. data/web/views/queues.erb +9 -3
  112. data/web/views/retries.erb +5 -9
  113. data/web/views/scheduled.erb +12 -13
  114. metadata +53 -39
  115. data/lib/sidekiq/delay.rb +0 -43
  116. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  117. data/lib/sidekiq/extensions/active_record.rb +0 -43
  118. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  119. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  120. data/lib/sidekiq/metrics/deploy.rb +0 -47
  121. data/lib/sidekiq/worker.rb +0 -370
  122. data/web/assets/javascripts/graph.js +0 -16
  123. /data/{LICENSE → LICENSE.txt} +0 -0
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "time"
2
4
  require "sidekiq"
3
5
  require "sidekiq/metrics/shared"
@@ -29,11 +31,11 @@ module Sidekiq
29
31
  # We don't track time for failed jobs as they can have very unpredictable
30
32
  # execution times. more important to know average time for successful jobs so we
31
33
  # can better recognize when a perf regression is introduced.
32
- @lock.synchronize {
33
- @grams[klass].record_time(time_ms)
34
- @jobs["#{klass}|ms"] += time_ms
35
- @totals["ms"] += time_ms
36
- }
34
+ track_time(klass, time_ms)
35
+ rescue JobRetry::Skip
36
+ # This is raised when iterable job is interrupted.
37
+ track_time(klass, time_ms)
38
+ raise
37
39
  rescue Exception
38
40
  @lock.synchronize {
39
41
  @jobs["#{klass}|f"] += 1
@@ -48,8 +50,8 @@ module Sidekiq
48
50
  end
49
51
  end
50
52
 
51
- LONG_TERM = 90 * 24 * 60 * 60
52
- MID_TERM = 7 * 24 * 60 * 60
53
+ # LONG_TERM = 90 * 24 * 60 * 60
54
+ # MID_TERM = 7 * 24 * 60 * 60
53
55
  SHORT_TERM = 8 * 60 * 60
54
56
 
55
57
  def flush(time = Time.now)
@@ -59,12 +61,13 @@ module Sidekiq
59
61
  return if procd == 0 && fails == 0
60
62
 
61
63
  now = time.utc
62
- nowdate = now.strftime("%Y%m%d")
63
- nowhour = now.strftime("%Y%m%d|%-H")
64
+ # nowdate = now.strftime("%Y%m%d")
65
+ # nowhour = now.strftime("%Y%m%d|%-H")
64
66
  nowmin = now.strftime("%Y%m%d|%-H:%-M")
65
67
  count = 0
66
68
 
67
69
  redis do |conn|
70
+ # persist fine-grained histogram data
68
71
  if grams.size > 0
69
72
  conn.pipelined do |pipe|
70
73
  grams.each do |_, gram|
@@ -73,15 +76,16 @@ module Sidekiq
73
76
  end
74
77
  end
75
78
 
79
+ # persist coarse grained execution count + execution millis.
80
+ # note as of today we don't use or do anything with the
81
+ # daily or hourly rollups.
76
82
  [
77
- ["j", jobs, nowdate, LONG_TERM],
78
- ["j", jobs, nowhour, MID_TERM],
83
+ # ["j", jobs, nowdate, LONG_TERM],
84
+ # ["j", jobs, nowhour, MID_TERM],
79
85
  ["j", jobs, nowmin, SHORT_TERM]
80
86
  ].each do |prefix, data, bucket, ttl|
81
- # Quietly seed the new 7.0 stats format so migration is painless.
82
87
  conn.pipelined do |xa|
83
88
  stats = "#{prefix}|#{bucket}"
84
- # logger.debug "Flushing metrics #{stats}"
85
89
  data.each_pair do |key, value|
86
90
  xa.hincrby stats, key, value
87
91
  count += 1
@@ -89,22 +93,34 @@ module Sidekiq
89
93
  xa.expire(stats, ttl)
90
94
  end
91
95
  end
92
- logger.info "Flushed #{count} metrics"
96
+ logger.debug "Flushed #{count} metrics"
93
97
  count
94
98
  end
95
99
  end
96
100
 
97
101
  private
98
102
 
103
+ def track_time(klass, time_ms)
104
+ @lock.synchronize {
105
+ @grams[klass].record_time(time_ms)
106
+ @jobs["#{klass}|ms"] += time_ms
107
+ @totals["ms"] += time_ms
108
+ }
109
+ end
110
+
99
111
  def reset
100
112
  @lock.synchronize {
101
113
  array = [@totals, @jobs, @grams]
102
- @totals = Hash.new(0)
103
- @jobs = Hash.new(0)
104
- @grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
114
+ reset_instance_variables
105
115
  array
106
116
  }
107
117
  end
118
+
119
+ def reset_instance_variables
120
+ @totals = Hash.new(0)
121
+ @jobs = Hash.new(0)
122
+ @grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
123
+ end
108
124
  end
109
125
 
110
126
  class Middleware
@@ -121,14 +137,12 @@ module Sidekiq
121
137
  end
122
138
  end
123
139
 
124
- if ENV["SIDEKIQ_METRICS_BETA"] == "1"
125
- Sidekiq.configure_server do |config|
126
- exec = Sidekiq::Metrics::ExecutionTracker.new(config)
127
- config.server_middleware do |chain|
128
- chain.add Sidekiq::Metrics::Middleware, exec
129
- end
130
- config.on(:beat) do
131
- exec.flush
132
- end
140
+ Sidekiq.configure_server do |config|
141
+ exec = Sidekiq::Metrics::ExecutionTracker.new(config)
142
+ config.server_middleware do |chain|
143
+ chain.add Sidekiq::Metrics::Middleware, exec
144
+ end
145
+ config.on(:beat) do
146
+ exec.flush
133
147
  end
134
148
  end
@@ -80,15 +80,6 @@ module Sidekiq
80
80
  class Chain
81
81
  include Enumerable
82
82
 
83
- # A unique instance of the middleware chain is created for
84
- # each job executed in order to be thread-safe.
85
- # @param copy [Sidekiq::Middleware::Chain] New instance of Chain
86
- # @returns nil
87
- def initialize_copy(copy)
88
- copy.instance_variable_set(:@entries, entries.dup)
89
- nil
90
- end
91
-
92
83
  # Iterate through each middleware in the chain
93
84
  def each(&block)
94
85
  entries.each(&block)
@@ -105,6 +96,12 @@ module Sidekiq
105
96
  @entries ||= []
106
97
  end
107
98
 
99
+ def copy_for(capsule)
100
+ chain = Sidekiq::Middleware::Chain.new(capsule)
101
+ chain.instance_variable_set(:@entries, entries.dup)
102
+ chain
103
+ end
104
+
108
105
  # Remove all middleware matching the given Class
109
106
  # @param klass [Class]
110
107
  def remove(klass)
@@ -152,6 +149,7 @@ module Sidekiq
152
149
  def exists?(klass)
153
150
  any? { |entry| entry.klass == klass }
154
151
  end
152
+ alias_method :include?, :exists?
155
153
 
156
154
  # @return [Boolean] if the chain contains no middleware
157
155
  def empty?
@@ -168,23 +166,26 @@ module Sidekiq
168
166
 
169
167
  # Used by Sidekiq to execute the middleware at runtime
170
168
  # @api private
171
- def invoke(*args)
169
+ def invoke(*args, &block)
172
170
  return yield if empty?
173
171
 
174
172
  chain = retrieve
175
- traverse_chain = proc do
176
- if chain.empty?
177
- yield
178
- else
179
- chain.shift.call(*args, &traverse_chain)
173
+ traverse(chain, 0, args, &block)
174
+ end
175
+
176
+ private
177
+
178
+ def traverse(chain, index, args, &block)
179
+ if index >= chain.size
180
+ yield
181
+ else
182
+ chain[index].call(*args) do
183
+ traverse(chain, index + 1, args, &block)
180
184
  end
181
185
  end
182
- traverse_chain.call
183
186
  end
184
187
  end
185
188
 
186
- private
187
-
188
189
  # Represents each link in the middleware chain
189
190
  # @api private
190
191
  class Entry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/current_attributes"
2
4
 
3
5
  module Sidekiq
@@ -7,27 +9,31 @@ module Sidekiq
7
9
  # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
8
10
  # per-request attribute. See +ActiveSupport::CurrentAttributes+.
9
11
  #
12
+ # For multiple current attributes, pass an array of current attributes.
13
+ #
10
14
  # @example
11
15
  #
12
16
  # # in your initializer
13
17
  # require "sidekiq/middleware/current_attributes"
14
18
  # Sidekiq::CurrentAttributes.persist("Myapp::Current")
19
+ # # or multiple current attributes
20
+ # Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
15
21
  #
16
22
  module CurrentAttributes
17
23
  class Save
18
24
  include Sidekiq::ClientMiddleware
19
25
 
20
- def initialize(cattr)
21
- @strklass = cattr
26
+ def initialize(cattrs)
27
+ @cattrs = cattrs
22
28
  end
23
29
 
24
30
  def call(_, job, _, _)
25
- attrs = @strklass.constantize.attributes
26
- if attrs.any?
27
- if job.has_key?("cattr")
28
- job["cattr"].merge!(attrs)
29
- else
30
- job["cattr"] = attrs
31
+ @cattrs.each do |(key, strklass)|
32
+ if !job.has_key?(key)
33
+ attrs = strklass.constantize.attributes
34
+ # Retries can push the job N times, we don't
35
+ # want retries to reset cattr. #5692, #5090
36
+ job[key] = attrs if attrs.any?
31
37
  end
32
38
  end
33
39
  yield
@@ -37,26 +43,70 @@ module Sidekiq
37
43
  class Load
38
44
  include Sidekiq::ServerMiddleware
39
45
 
40
- def initialize(cattr)
41
- @strklass = cattr
46
+ def initialize(cattrs)
47
+ @cattrs = cattrs
42
48
  end
43
49
 
44
50
  def call(_, job, _, &block)
45
- if job.has_key?("cattr")
46
- @strklass.constantize.set(job["cattr"], &block)
47
- else
48
- yield
51
+ klass_attrs = {}
52
+
53
+ @cattrs.each do |(key, strklass)|
54
+ next unless job.has_key?(key)
55
+
56
+ klass_attrs[strklass.constantize] = job[key]
57
+ end
58
+
59
+ wrap(klass_attrs.to_a, &block)
60
+ end
61
+
62
+ private
63
+
64
+ def wrap(klass_attrs, &block)
65
+ klass, attrs = klass_attrs.shift
66
+ return block.call unless klass
67
+
68
+ retried = false
69
+
70
+ begin
71
+ klass.set(attrs) do
72
+ wrap(klass_attrs, &block)
73
+ end
74
+ rescue NoMethodError
75
+ raise if retried
76
+
77
+ # It is possible that the `CurrentAttributes` definition
78
+ # was changed before the job started processing.
79
+ attrs = attrs.select { |attr| klass.respond_to?(attr) }
80
+ retried = true
81
+ retry
49
82
  end
50
83
  end
51
84
  end
52
85
 
53
- def self.persist(klass)
54
- Sidekiq.configure_client do |config|
55
- config.client_middleware.add Save, klass.to_s
86
+ class << self
87
+ def persist(klass_or_array, config = Sidekiq.default_configuration)
88
+ cattrs = build_cattrs_hash(klass_or_array)
89
+
90
+ config.client_middleware.add Save, cattrs
91
+ config.server_middleware.prepend Load, cattrs
56
92
  end
57
- Sidekiq.configure_server do |config|
58
- config.client_middleware.add Save, klass.to_s
59
- config.server_middleware.add Load, klass.to_s
93
+
94
+ private
95
+
96
+ def build_cattrs_hash(klass_or_array)
97
+ if klass_or_array.is_a?(Array)
98
+ {}.tap do |hash|
99
+ klass_or_array.each_with_index do |klass, index|
100
+ hash[key_at(index)] = klass.to_s
101
+ end
102
+ end
103
+ else
104
+ {key_at(0) => klass_or_array.to_s}
105
+ end
106
+ end
107
+
108
+ def key_at(index)
109
+ (index == 0) ? "cattr" : "cattr_#{index}"
60
110
  end
61
111
  end
62
112
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sidekiq
2
4
  # Server-side middleware must import this Module in order
3
5
  # to get access to server resources during `call`.
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "fileutils"
4
5
  require "sidekiq/api"
@@ -16,8 +17,6 @@ class Sidekiq::Monitor
16
17
  return
17
18
  end
18
19
  send(section)
19
- rescue => e
20
- abort "Couldn't get status: #{e}"
21
20
  end
22
21
 
23
22
  def all
@@ -49,10 +48,25 @@ class Sidekiq::Monitor
49
48
  def processes
50
49
  puts "---- Processes (#{process_set.size}) ----"
51
50
  process_set.each_with_index do |process, index|
51
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
52
+ #
53
+ # Before:
54
+ # ["default", "critical"]
55
+ #
56
+ # After:
57
+ # {"default" => 1, "critical" => 10}
58
+ queues =
59
+ if process["weights"]
60
+ process["weights"].sort_by { |queue| queue[0] }.map { |capsule| capsule.map { |name, weight| (weight > 0) ? "#{name}: #{weight}" : name }.join(", ") }
61
+ else
62
+ process["queues"].sort
63
+ end
64
+
52
65
  puts "#{process["identity"]} #{tags_for(process)}"
53
66
  puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
54
67
  puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
55
- puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}"
68
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
69
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
56
70
  puts "" unless (index + 1) == process_set.size
57
71
  end
58
72
  end
@@ -85,7 +99,7 @@ class Sidekiq::Monitor
85
99
  pad = opts[:pad] || 0
86
100
  max_length = opts[:max_length] || (80 - pad)
87
101
  out = []
88
- line = ""
102
+ line = +""
89
103
  values.each do |value|
90
104
  if (line.length + value.length) > max_length
91
105
  out << line
@@ -19,9 +19,9 @@ module Sidekiq
19
19
  total_size, items = conn.multi { |transaction|
20
20
  transaction.zcard(key)
21
21
  if rev
22
- transaction.zrevrange(key, starting, ending, withscores: true)
22
+ transaction.zrange(key, starting, ending, "REV", "withscores")
23
23
  else
24
- transaction.zrange(key, starting, ending, withscores: true)
24
+ transaction.zrange(key, starting, ending, "withscores")
25
25
  end
26
26
  }
27
27
  [current_page, total_size, items]
@@ -26,18 +26,18 @@ module Sidekiq
26
26
 
27
27
  attr_reader :thread
28
28
  attr_reader :job
29
+ attr_reader :capsule
29
30
 
30
- def initialize(options, &block)
31
+ def initialize(capsule, &block)
32
+ @config = @capsule = capsule
31
33
  @callback = block
32
34
  @down = false
33
35
  @done = false
34
36
  @job = nil
35
37
  @thread = nil
36
- @config = options
37
- @strategy = options[:fetch]
38
- @reloader = options[:reloader] || proc { |&block| block.call }
39
- @job_logger = (options[:job_logger] || Sidekiq::JobLogger).new
40
- @retrier = Sidekiq::JobRetry.new(options)
38
+ @reloader = Sidekiq.default_configuration[:reloader]
39
+ @job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(capsule.config)
40
+ @retrier = Sidekiq::JobRetry.new(capsule)
41
41
  end
42
42
 
43
43
  def terminate(wait = false)
@@ -58,13 +58,21 @@ module Sidekiq
58
58
  @thread.value if wait
59
59
  end
60
60
 
61
+ def stopping?
62
+ @done
63
+ end
64
+
61
65
  def start
62
- @thread ||= safe_thread("processor", &method(:run))
66
+ @thread ||= safe_thread("#{config.name}/processor", &method(:run))
63
67
  end
64
68
 
65
69
  private unless $TESTING
66
70
 
67
71
  def run
72
+ # By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
73
+ # instead of the global pool in +Sidekiq::Config#redis_pool+.
74
+ Thread.current[:sidekiq_capsule] = @capsule
75
+
68
76
  process_one until @done
69
77
  @callback.call(self)
70
78
  rescue Sidekiq::Shutdown
@@ -80,7 +88,7 @@ module Sidekiq
80
88
  end
81
89
 
82
90
  def get_one
83
- uow = @strategy.retrieve_work
91
+ uow = capsule.fetcher.retrieve_work
84
92
  if @down
85
93
  logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
86
94
  @down = nil
@@ -129,11 +137,12 @@ module Sidekiq
129
137
  # the Reloader. It handles code loading, db connection management, etc.
130
138
  # Effectively this block denotes a "unit of work" to Rails.
131
139
  @reloader.call do
132
- klass = constantize(job_hash["class"])
133
- inst = klass.new
134
- inst.jid = job_hash["jid"]
135
- @retrier.local(inst, jobstr, queue) do
136
- yield inst
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
137
146
  end
138
147
  end
139
148
  end
@@ -142,6 +151,11 @@ module Sidekiq
142
151
  end
143
152
  end
144
153
 
154
+ IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
155
+ private_constant :IGNORE_SHUTDOWN_INTERRUPTS
156
+ ALLOW_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :immediate}
157
+ private_constant :ALLOW_SHUTDOWN_INTERRUPTS
158
+
145
159
  def process(uow)
146
160
  jobstr = uow.job
147
161
  queue = uow.queue_name
@@ -153,53 +167,57 @@ module Sidekiq
153
167
  rescue => ex
154
168
  handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
155
169
  now = Time.now.to_f
156
- config.redis do |conn|
170
+ redis do |conn|
157
171
  conn.multi do |xa|
158
172
  xa.zadd("dead", now.to_s, jobstr)
159
- xa.zremrangebyscore("dead", "-inf", now - config[:dead_timeout_in_seconds])
160
- xa.zremrangebyrank("dead", 0, - config[:dead_max_jobs])
173
+ xa.zremrangebyscore("dead", "-inf", now - @capsule.config[:dead_timeout_in_seconds])
174
+ xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
161
175
  end
162
176
  end
163
177
  return uow.acknowledge
164
178
  end
165
179
 
166
180
  ack = false
167
- begin
168
- dispatch(job_hash, queue, jobstr) do |inst|
169
- @config.server_middleware.invoke(inst, job_hash, queue) do
170
- execute_job(inst, job_hash["args"])
181
+ Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
182
+ Thread.handle_interrupt(ALLOW_SHUTDOWN_INTERRUPTS) do
183
+ dispatch(job_hash, queue, jobstr) do |instance|
184
+ config.server_middleware.invoke(instance, job_hash, queue) do
185
+ execute_job(instance, job_hash["args"])
186
+ end
171
187
  end
188
+ ack = true
189
+ rescue Sidekiq::Shutdown
190
+ # Had to force kill this job because it didn't finish
191
+ # within the timeout. Don't acknowledge the work since
192
+ # we didn't properly finish it.
193
+ rescue Sidekiq::JobRetry::Skip => s
194
+ # Skip means we handled this error elsewhere. We don't
195
+ # need to log or report the error.
196
+ ack = true
197
+ raise s
198
+ rescue Sidekiq::JobRetry::Handled => h
199
+ # this is the common case: job raised error and Sidekiq::JobRetry::Handled
200
+ # signals that we created a retry successfully. We can acknowledge the job.
201
+ ack = true
202
+ e = h.cause || h
203
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
204
+ raise e
205
+ rescue Exception => ex
206
+ # Unexpected error! This is very bad and indicates an exception that got past
207
+ # the retry subsystem (e.g. network partition). We won't acknowledge the job
208
+ # so it can be rescued when using Sidekiq Pro.
209
+ handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
210
+ raise ex
172
211
  end
173
- ack = true
174
- rescue Sidekiq::Shutdown
175
- # Had to force kill this job because it didn't finish
176
- # within the timeout. Don't acknowledge the work since
177
- # we didn't properly finish it.
178
- rescue Sidekiq::JobRetry::Handled => h
179
- # this is the common case: job raised error and Sidekiq::JobRetry::Handled
180
- # signals that we created a retry successfully. We can acknowlege the job.
181
- ack = true
182
- e = h.cause || h
183
- handle_exception(e, {context: "Job raised exception", job: job_hash})
184
- raise e
185
- rescue Exception => ex
186
- # Unexpected error! This is very bad and indicates an exception that got past
187
- # the retry subsystem (e.g. network partition). We won't acknowledge the job
188
- # so it can be rescued when using Sidekiq Pro.
189
- handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
190
- raise ex
191
212
  ensure
192
213
  if ack
193
- # We don't want a shutdown signal to interrupt job acknowledgment.
194
- Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
195
- uow.acknowledge
196
- end
214
+ uow.acknowledge
197
215
  end
198
216
  end
199
217
  end
200
218
 
201
- def execute_job(inst, cloned_args)
202
- inst.perform(*cloned_args)
219
+ def execute_job(instance, cloned_args)
220
+ instance.perform(*cloned_args)
203
221
  end
204
222
 
205
223
  # Ruby doesn't provide atomic counters out of the box so we'll
@@ -269,18 +287,5 @@ module Sidekiq
269
287
  PROCESSED.incr
270
288
  end
271
289
  end
272
-
273
- def constantize(str)
274
- return Object.const_get(str) unless str.include?("::")
275
-
276
- names = str.split("::")
277
- names.shift if names.empty? || names.first.empty?
278
-
279
- names.inject(Object) do |constant, name|
280
- # the false flag limits search for name to under the constant namespace
281
- # which mimics Rails' behaviour
282
- constant.const_get(name, false)
283
- end
284
- end
285
290
  end
286
291
  end
data/lib/sidekiq/rails.rb CHANGED
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq/job"
4
+ require "rails"
5
+
6
+ begin
7
+ require "active_job"
8
+ require "active_job/queue_adapters/sidekiq_adapter"
9
+ rescue LoadError
10
+ end
4
11
 
5
12
  module Sidekiq
6
13
  class Rails < ::Rails::Engine
@@ -10,7 +17,8 @@ module Sidekiq
10
17
  end
11
18
 
12
19
  def call
13
- @app.reloader.wrap do
20
+ params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
21
+ @app.reloader.wrap(**params) do
14
22
  yield
15
23
  end
16
24
  end
@@ -18,11 +26,15 @@ module Sidekiq
18
26
  def inspect
19
27
  "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
20
28
  end
29
+
30
+ def to_hash
31
+ {app: @app.class.name}
32
+ end
21
33
  end
22
34
 
23
35
  # By including the Options module, we allow AJs to directly control sidekiq features
24
36
  # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
25
- # AJ retries don't show up in the Sidekiq UI Retries tab, save any error data, can't be
37
+ # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
26
38
  # manually retried, don't automatically die, etc.
27
39
  #
28
40
  # class SomeJob < ActiveJob::Base
@@ -37,11 +49,10 @@ module Sidekiq
37
49
  end
38
50
  end
39
51
 
40
- config.before_configuration do
41
- dep = ActiveSupport::Deprecation.new("7.0", "Sidekiq")
42
- dep.deprecate_methods(Sidekiq.singleton_class,
43
- default_worker_options: :default_job_options,
44
- "default_worker_options=": :default_job_options=)
52
+ initializer "sidekiq.backtrace_cleaner" do
53
+ Sidekiq.configure_server do |config|
54
+ config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
55
+ end
45
56
  end
46
57
 
47
58
  # This hook happens after all initializers are run, just before returning
@@ -55,10 +66,10 @@ module Sidekiq
55
66
  # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
56
67
  # it will appear in the Sidekiq console with all of the job context.
57
68
  unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
58
- if ::Rails::VERSION::STRING < "7.1"
59
- ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
60
- else
69
+ if ::Rails.logger.respond_to?(:broadcast_to)
61
70
  ::Rails.logger.broadcast_to(config.logger)
71
+ else
72
+ ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
62
73
  end
63
74
  end
64
75
  end