sidekiq 6.5.1 → 7.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +142 -12
  3. data/README.md +40 -32
  4. data/bin/sidekiq +3 -8
  5. data/bin/sidekiqload +186 -118
  6. data/bin/sidekiqmon +3 -0
  7. data/lib/sidekiq/api.rb +226 -139
  8. data/lib/sidekiq/capsule.rb +127 -0
  9. data/lib/sidekiq/cli.rb +55 -61
  10. data/lib/sidekiq/client.rb +31 -18
  11. data/lib/sidekiq/component.rb +5 -1
  12. data/lib/sidekiq/config.rb +270 -0
  13. data/lib/sidekiq/deploy.rb +62 -0
  14. data/lib/sidekiq/embedded.rb +61 -0
  15. data/lib/sidekiq/fetch.rb +11 -14
  16. data/lib/sidekiq/job.rb +375 -10
  17. data/lib/sidekiq/job_logger.rb +2 -2
  18. data/lib/sidekiq/job_retry.rb +62 -41
  19. data/lib/sidekiq/job_util.rb +48 -14
  20. data/lib/sidekiq/launcher.rb +71 -65
  21. data/lib/sidekiq/logger.rb +1 -26
  22. data/lib/sidekiq/manager.rb +9 -11
  23. data/lib/sidekiq/metrics/query.rb +153 -0
  24. data/lib/sidekiq/metrics/shared.rb +95 -0
  25. data/lib/sidekiq/metrics/tracking.rb +136 -0
  26. data/lib/sidekiq/middleware/chain.rb +84 -48
  27. data/lib/sidekiq/middleware/current_attributes.rb +12 -17
  28. data/lib/sidekiq/monitor.rb +17 -4
  29. data/lib/sidekiq/paginator.rb +9 -1
  30. data/lib/sidekiq/processor.rb +27 -27
  31. data/lib/sidekiq/rails.rb +4 -9
  32. data/lib/sidekiq/redis_client_adapter.rb +8 -47
  33. data/lib/sidekiq/redis_connection.rb +11 -113
  34. data/lib/sidekiq/scheduled.rb +60 -33
  35. data/lib/sidekiq/testing.rb +5 -33
  36. data/lib/sidekiq/transaction_aware_client.rb +4 -5
  37. data/lib/sidekiq/version.rb +2 -1
  38. data/lib/sidekiq/web/action.rb +3 -3
  39. data/lib/sidekiq/web/application.rb +40 -9
  40. data/lib/sidekiq/web/csrf_protection.rb +1 -1
  41. data/lib/sidekiq/web/helpers.rb +32 -18
  42. data/lib/sidekiq/web.rb +7 -14
  43. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  44. data/lib/sidekiq.rb +76 -266
  45. data/sidekiq.gemspec +21 -10
  46. data/web/assets/javascripts/application.js +19 -1
  47. data/web/assets/javascripts/base-charts.js +106 -0
  48. data/web/assets/javascripts/chart.min.js +13 -0
  49. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  50. data/web/assets/javascripts/dashboard-charts.js +166 -0
  51. data/web/assets/javascripts/dashboard.js +3 -240
  52. data/web/assets/javascripts/metrics.js +264 -0
  53. data/web/assets/stylesheets/application-dark.css +4 -0
  54. data/web/assets/stylesheets/application-rtl.css +2 -91
  55. data/web/assets/stylesheets/application.css +65 -297
  56. data/web/locales/ar.yml +70 -70
  57. data/web/locales/cs.yml +62 -62
  58. data/web/locales/da.yml +60 -53
  59. data/web/locales/de.yml +65 -65
  60. data/web/locales/el.yml +43 -24
  61. data/web/locales/en.yml +82 -69
  62. data/web/locales/es.yml +68 -68
  63. data/web/locales/fa.yml +65 -65
  64. data/web/locales/fr.yml +67 -67
  65. data/web/locales/gd.yml +99 -0
  66. data/web/locales/he.yml +65 -64
  67. data/web/locales/hi.yml +59 -59
  68. data/web/locales/it.yml +53 -53
  69. data/web/locales/ja.yml +73 -68
  70. data/web/locales/ko.yml +52 -52
  71. data/web/locales/lt.yml +66 -66
  72. data/web/locales/nb.yml +61 -61
  73. data/web/locales/nl.yml +52 -52
  74. data/web/locales/pl.yml +45 -45
  75. data/web/locales/pt-br.yml +59 -69
  76. data/web/locales/pt.yml +51 -51
  77. data/web/locales/ru.yml +67 -66
  78. data/web/locales/sv.yml +53 -53
  79. data/web/locales/ta.yml +60 -60
  80. data/web/locales/uk.yml +62 -61
  81. data/web/locales/ur.yml +64 -64
  82. data/web/locales/vi.yml +67 -67
  83. data/web/locales/zh-cn.yml +43 -16
  84. data/web/locales/zh-tw.yml +42 -8
  85. data/web/views/_footer.erb +5 -2
  86. data/web/views/_job_info.erb +18 -2
  87. data/web/views/_metrics_period_select.erb +12 -0
  88. data/web/views/_nav.erb +1 -1
  89. data/web/views/_paging.erb +2 -0
  90. data/web/views/_poll_link.erb +1 -1
  91. data/web/views/busy.erb +43 -27
  92. data/web/views/dashboard.erb +36 -4
  93. data/web/views/metrics.erb +82 -0
  94. data/web/views/metrics_for_job.erb +68 -0
  95. data/web/views/morgue.erb +5 -9
  96. data/web/views/queue.erb +15 -15
  97. data/web/views/queues.erb +3 -1
  98. data/web/views/retries.erb +5 -9
  99. data/web/views/scheduled.erb +12 -13
  100. metadata +60 -27
  101. data/lib/sidekiq/.DS_Store +0 -0
  102. data/lib/sidekiq/delay.rb +0 -43
  103. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  104. data/lib/sidekiq/extensions/active_record.rb +0 -43
  105. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  106. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  107. data/lib/sidekiq/worker.rb +0 -367
  108. /data/{LICENSE → LICENSE.txt} +0 -0
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require "sidekiq"
5
+ require "sidekiq/metrics/shared"
6
+
7
+ # This file contains the components which track execution metrics within Sidekiq.
8
+ module Sidekiq
9
+ module Metrics
10
+ class ExecutionTracker
11
+ include Sidekiq::Component
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ @jobs = Hash.new(0)
16
+ @totals = Hash.new(0)
17
+ @grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
18
+ @lock = Mutex.new
19
+ end
20
+
21
+ def track(queue, klass)
22
+ start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
23
+ time_ms = 0
24
+ begin
25
+ begin
26
+ yield
27
+ ensure
28
+ finish = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond)
29
+ time_ms = finish - start
30
+ end
31
+ # We don't track time for failed jobs as they can have very unpredictable
32
+ # execution times. more important to know average time for successful jobs so we
33
+ # can better recognize when a perf regression is introduced.
34
+ @lock.synchronize {
35
+ @grams[klass].record_time(time_ms)
36
+ @jobs["#{klass}|ms"] += time_ms
37
+ @totals["ms"] += time_ms
38
+ }
39
+ rescue Exception
40
+ @lock.synchronize {
41
+ @jobs["#{klass}|f"] += 1
42
+ @totals["f"] += 1
43
+ }
44
+ raise
45
+ ensure
46
+ @lock.synchronize {
47
+ @jobs["#{klass}|p"] += 1
48
+ @totals["p"] += 1
49
+ }
50
+ end
51
+ end
52
+
53
+ # LONG_TERM = 90 * 24 * 60 * 60
54
+ # MID_TERM = 7 * 24 * 60 * 60
55
+ SHORT_TERM = 8 * 60 * 60
56
+
57
+ def flush(time = Time.now)
58
+ totals, jobs, grams = reset
59
+ procd = totals["p"]
60
+ fails = totals["f"]
61
+ return if procd == 0 && fails == 0
62
+
63
+ now = time.utc
64
+ # nowdate = now.strftime("%Y%m%d")
65
+ # nowhour = now.strftime("%Y%m%d|%-H")
66
+ nowmin = now.strftime("%Y%m%d|%-H:%-M")
67
+ count = 0
68
+
69
+ redis do |conn|
70
+ # persist fine-grained histogram data
71
+ if grams.size > 0
72
+ conn.pipelined do |pipe|
73
+ grams.each do |_, gram|
74
+ gram.persist(pipe, now)
75
+ end
76
+ end
77
+ end
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.
82
+ [
83
+ # ["j", jobs, nowdate, LONG_TERM],
84
+ # ["j", jobs, nowhour, MID_TERM],
85
+ ["j", jobs, nowmin, SHORT_TERM]
86
+ ].each do |prefix, data, bucket, ttl|
87
+ conn.pipelined do |xa|
88
+ stats = "#{prefix}|#{bucket}"
89
+ data.each_pair do |key, value|
90
+ xa.hincrby stats, key, value
91
+ count += 1
92
+ end
93
+ xa.expire(stats, ttl)
94
+ end
95
+ end
96
+ logger.debug "Flushed #{count} metrics"
97
+ count
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def reset
104
+ @lock.synchronize {
105
+ array = [@totals, @jobs, @grams]
106
+ @totals = Hash.new(0)
107
+ @jobs = Hash.new(0)
108
+ @grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
109
+ array
110
+ }
111
+ end
112
+ end
113
+
114
+ class Middleware
115
+ include Sidekiq::ServerMiddleware
116
+
117
+ def initialize(options)
118
+ @exec = options
119
+ end
120
+
121
+ def call(_instance, hash, queue, &block)
122
+ @exec.track(queue, hash["wrapped"] || hash["class"], &block)
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ Sidekiq.configure_server do |config|
129
+ exec = Sidekiq::Metrics::ExecutionTracker.new(config)
130
+ config.server_middleware do |chain|
131
+ chain.add Sidekiq::Metrics::Middleware, exec
132
+ end
133
+ config.on(:beat) do
134
+ exec.flush
135
+ end
136
+ end
@@ -4,84 +4,89 @@ require "sidekiq/middleware/modules"
4
4
 
5
5
  module Sidekiq
6
6
  # Middleware is code configured to run before/after
7
- # a message is processed. It is patterned after Rack
7
+ # a job is processed. It is patterned after Rack
8
8
  # middleware. Middleware exists for the client side
9
9
  # (pushing jobs onto the queue) as well as the server
10
10
  # side (when jobs are actually processed).
11
11
  #
12
+ # Callers will register middleware Classes and Sidekiq will
13
+ # create new instances of the middleware for every job. This
14
+ # is important so that instance state is not shared accidentally
15
+ # between job executions.
16
+ #
12
17
  # To add middleware for the client:
13
18
  #
14
- # Sidekiq.configure_client do |config|
15
- # config.client_middleware do |chain|
16
- # chain.add MyClientHook
19
+ # Sidekiq.configure_client do |config|
20
+ # config.client_middleware do |chain|
21
+ # chain.add MyClientHook
22
+ # end
17
23
  # end
18
- # end
19
24
  #
20
25
  # To modify middleware for the server, just call
21
26
  # with another block:
22
27
  #
23
- # Sidekiq.configure_server do |config|
24
- # config.server_middleware do |chain|
25
- # chain.add MyServerHook
26
- # chain.remove ActiveRecord
28
+ # Sidekiq.configure_server do |config|
29
+ # config.server_middleware do |chain|
30
+ # chain.add MyServerHook
31
+ # chain.remove ActiveRecord
32
+ # end
27
33
  # end
28
- # end
29
34
  #
30
35
  # To insert immediately preceding another entry:
31
36
  #
32
- # Sidekiq.configure_client do |config|
33
- # config.client_middleware do |chain|
34
- # chain.insert_before ActiveRecord, MyClientHook
37
+ # Sidekiq.configure_client do |config|
38
+ # config.client_middleware do |chain|
39
+ # chain.insert_before ActiveRecord, MyClientHook
40
+ # end
35
41
  # end
36
- # end
37
42
  #
38
43
  # To insert immediately after another entry:
39
44
  #
40
- # Sidekiq.configure_client do |config|
41
- # config.client_middleware do |chain|
42
- # chain.insert_after ActiveRecord, MyClientHook
45
+ # Sidekiq.configure_client do |config|
46
+ # config.client_middleware do |chain|
47
+ # chain.insert_after ActiveRecord, MyClientHook
48
+ # end
43
49
  # end
44
- # end
45
50
  #
46
51
  # This is an example of a minimal server middleware:
47
52
  #
48
- # class MyServerHook
49
- # include Sidekiq::ServerMiddleware
50
- # def call(job_instance, msg, queue)
51
- # logger.info "Before job"
52
- # redis {|conn| conn.get("foo") } # do something in Redis
53
- # yield
54
- # logger.info "After job"
53
+ # class MyServerHook
54
+ # include Sidekiq::ServerMiddleware
55
+ #
56
+ # def call(job_instance, msg, queue)
57
+ # logger.info "Before job"
58
+ # redis {|conn| conn.get("foo") } # do something in Redis
59
+ # yield
60
+ # logger.info "After job"
61
+ # end
55
62
  # end
56
- # end
57
63
  #
58
64
  # This is an example of a minimal client middleware, note
59
65
  # the method must return the result or the job will not push
60
66
  # to Redis:
61
67
  #
62
- # class MyClientHook
63
- # include Sidekiq::ClientMiddleware
64
- # def call(job_class, msg, queue, redis_pool)
65
- # logger.info "Before push"
66
- # result = yield
67
- # logger.info "After push"
68
- # result
68
+ # class MyClientHook
69
+ # include Sidekiq::ClientMiddleware
70
+ #
71
+ # def call(job_class, msg, queue, redis_pool)
72
+ # logger.info "Before push"
73
+ # result = yield
74
+ # logger.info "After push"
75
+ # result
76
+ # end
69
77
  # end
70
- # end
71
78
  #
72
79
  module Middleware
73
80
  class Chain
74
81
  include Enumerable
75
82
 
76
- def initialize_copy(copy)
77
- copy.instance_variable_set(:@entries, entries.dup)
78
- end
79
-
83
+ # Iterate through each middleware in the chain
80
84
  def each(&block)
81
85
  entries.each(&block)
82
86
  end
83
87
 
84
- def initialize(config = nil)
88
+ # @api private
89
+ def initialize(config = nil) # :nodoc:
85
90
  @config = config
86
91
  @entries = nil
87
92
  yield self if block_given?
@@ -91,20 +96,39 @@ module Sidekiq
91
96
  @entries ||= []
92
97
  end
93
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
+
105
+ # Remove all middleware matching the given Class
106
+ # @param klass [Class]
94
107
  def remove(klass)
95
108
  entries.delete_if { |entry| entry.klass == klass }
96
109
  end
97
110
 
111
+ # Add the given middleware to the end of the chain.
112
+ # Sidekiq will call `klass.new(*args)` to create a clean
113
+ # copy of your middleware for every job executed.
114
+ #
115
+ # chain.add(Statsd::Metrics, { collector: "localhost:8125" })
116
+ #
117
+ # @param klass [Class] Your middleware class
118
+ # @param *args [Array<Object>] Set of arguments to pass to every instance of your middleware
98
119
  def add(klass, *args)
99
120
  remove(klass)
100
121
  entries << Entry.new(@config, klass, *args)
101
122
  end
102
123
 
124
+ # Identical to {#add} except the middleware is added to the front of the chain.
103
125
  def prepend(klass, *args)
104
126
  remove(klass)
105
127
  entries.insert(0, Entry.new(@config, klass, *args))
106
128
  end
107
129
 
130
+ # Inserts +newklass+ before +oldklass+ in the chain.
131
+ # Useful if one middleware must run before another middleware.
108
132
  def insert_before(oldklass, newklass, *args)
109
133
  i = entries.index { |entry| entry.klass == newklass }
110
134
  new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i)
@@ -112,6 +136,8 @@ module Sidekiq
112
136
  entries.insert(i, new_entry)
113
137
  end
114
138
 
139
+ # Inserts +newklass+ after +oldklass+ in the chain.
140
+ # Useful if one middleware must run after another middleware.
115
141
  def insert_after(oldklass, newklass, *args)
116
142
  i = entries.index { |entry| entry.klass == newklass }
117
143
  new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i)
@@ -119,10 +145,13 @@ module Sidekiq
119
145
  entries.insert(i + 1, new_entry)
120
146
  end
121
147
 
148
+ # @return [Boolean] if the given class is already in the chain
122
149
  def exists?(klass)
123
150
  any? { |entry| entry.klass == klass }
124
151
  end
152
+ alias_method :include?, :exists?
125
153
 
154
+ # @return [Boolean] if the chain contains no middleware
126
155
  def empty?
127
156
  @entries.nil? || @entries.empty?
128
157
  end
@@ -135,23 +164,30 @@ module Sidekiq
135
164
  entries.clear
136
165
  end
137
166
 
138
- def invoke(*args)
167
+ # Used by Sidekiq to execute the middleware at runtime
168
+ # @api private
169
+ def invoke(*args, &block)
139
170
  return yield if empty?
140
171
 
141
172
  chain = retrieve
142
- traverse_chain = proc do
143
- if chain.empty?
144
- yield
145
- else
146
- 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)
147
184
  end
148
185
  end
149
- traverse_chain.call
150
186
  end
151
187
  end
152
188
 
153
- private
154
-
189
+ # Represents each link in the middleware chain
190
+ # @api private
155
191
  class Entry
156
192
  attr_reader :klass
157
193
 
@@ -11,22 +11,22 @@ module Sidekiq
11
11
  #
12
12
  # # in your initializer
13
13
  # require "sidekiq/middleware/current_attributes"
14
- # Sidekiq::CurrentAttributes.persist(Myapp::Current)
14
+ # Sidekiq::CurrentAttributes.persist("Myapp::Current")
15
15
  #
16
16
  module CurrentAttributes
17
17
  class Save
18
18
  include Sidekiq::ClientMiddleware
19
19
 
20
20
  def initialize(cattr)
21
- @klass = cattr
21
+ @strklass = cattr
22
22
  end
23
23
 
24
24
  def call(_, job, _, _)
25
- attrs = @klass.attributes
26
- if job.has_key?("cattr")
27
- job["cattr"].merge!(attrs)
28
- else
29
- job["cattr"] = attrs
25
+ if !job.has_key?("cattr")
26
+ attrs = @strklass.constantize.attributes
27
+ # Retries can push the job N times, we don't
28
+ # want retries to reset cattr. #5692, #5090
29
+ job["cattr"] = attrs if attrs.any?
30
30
  end
31
31
  yield
32
32
  end
@@ -36,26 +36,21 @@ module Sidekiq
36
36
  include Sidekiq::ServerMiddleware
37
37
 
38
38
  def initialize(cattr)
39
- @klass = cattr
39
+ @strklass = cattr
40
40
  end
41
41
 
42
42
  def call(_, job, _, &block)
43
43
  if job.has_key?("cattr")
44
- @klass.set(job["cattr"], &block)
44
+ @strklass.constantize.set(job["cattr"], &block)
45
45
  else
46
46
  yield
47
47
  end
48
48
  end
49
49
  end
50
50
 
51
- def self.persist(klass)
52
- Sidekiq.configure_client do |config|
53
- config.client_middleware.add Save, klass
54
- end
55
- Sidekiq.configure_server do |config|
56
- config.client_middleware.add Save, klass
57
- config.server_middleware.add Load, klass
58
- end
51
+ def self.persist(klass, config = Sidekiq.default_configuration)
52
+ config.client_middleware.add Save, klass.to_s
53
+ config.server_middleware.add Load, klass.to_s
59
54
  end
60
55
  end
61
56
  end
@@ -16,8 +16,6 @@ class Sidekiq::Monitor
16
16
  return
17
17
  end
18
18
  send(section)
19
- rescue => e
20
- abort "Couldn't get status: #{e}"
21
19
  end
22
20
 
23
21
  def all
@@ -49,10 +47,25 @@ class Sidekiq::Monitor
49
47
  def processes
50
48
  puts "---- Processes (#{process_set.size}) ----"
51
49
  process_set.each_with_index do |process, index|
50
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
51
+ #
52
+ # Before:
53
+ # ["default", "critical"]
54
+ #
55
+ # After:
56
+ # {"default" => 1, "critical" => 10}
57
+ queues =
58
+ if process["weights"]
59
+ process["weights"].sort_by { |queue| queue[0] }.map { |capsule| capsule.map { |name, weight| (weight > 0) ? "#{name}: #{weight}" : name }.join(", ") }
60
+ else
61
+ process["queues"].sort
62
+ end
63
+
52
64
  puts "#{process["identity"]} #{tags_for(process)}"
53
65
  puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
54
66
  puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
55
- puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}"
67
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
68
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
56
69
  puts "" unless (index + 1) == process_set.size
57
70
  end
58
71
  end
@@ -101,7 +114,7 @@ class Sidekiq::Monitor
101
114
  tags = [
102
115
  process["tag"],
103
116
  process["labels"],
104
- (process["quiet"] == "true" ? "quiet" : nil)
117
+ ((process["quiet"] == "true") ? "quiet" : nil)
105
118
  ].flatten.compact
106
119
  tags.any? ? "[#{tags.join("] [")}]" : nil
107
120
  end
@@ -3,7 +3,7 @@
3
3
  module Sidekiq
4
4
  module Paginator
5
5
  def page(key, pageidx = 1, page_size = 25, opts = nil)
6
- current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
6
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
7
7
  pageidx = current_page - 1
8
8
  total_size = 0
9
9
  items = []
@@ -43,5 +43,13 @@ module Sidekiq
43
43
  end
44
44
  end
45
45
  end
46
+
47
+ def page_items(items, pageidx = 1, page_size = 25)
48
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
49
+ pageidx = current_page - 1
50
+ starting = pageidx * page_size
51
+ items = items.to_a
52
+ [current_page, items.size, items[starting, page_size]]
53
+ end
46
54
  end
47
55
  end
@@ -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(logger)
40
+ @retrier = Sidekiq::JobRetry.new(capsule)
41
41
  end
42
42
 
43
43
  def terminate(wait = false)
@@ -59,12 +59,16 @@ module Sidekiq
59
59
  end
60
60
 
61
61
  def start
62
- @thread ||= safe_thread("processor", &method(:run))
62
+ @thread ||= safe_thread("#{config.name}/processor", &method(:run))
63
63
  end
64
64
 
65
65
  private unless $TESTING
66
66
 
67
67
  def run
68
+ # By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
69
+ # instead of the global pool in +Sidekiq::Config#redis_pool+.
70
+ Thread.current[:sidekiq_capsule] = @capsule
71
+
68
72
  process_one until @done
69
73
  @callback.call(self)
70
74
  rescue Sidekiq::Shutdown
@@ -80,7 +84,7 @@ module Sidekiq
80
84
  end
81
85
 
82
86
  def get_one
83
- uow = @strategy.retrieve_work
87
+ uow = capsule.fetcher.retrieve_work
84
88
  if @down
85
89
  logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
86
90
  @down = nil
@@ -129,7 +133,7 @@ module Sidekiq
129
133
  # the Reloader. It handles code loading, db connection management, etc.
130
134
  # Effectively this block denotes a "unit of work" to Rails.
131
135
  @reloader.call do
132
- klass = constantize(job_hash["class"])
136
+ klass = Object.const_get(job_hash["class"])
133
137
  inst = klass.new
134
138
  inst.jid = job_hash["jid"]
135
139
  @retrier.local(inst, jobstr, queue) do
@@ -142,6 +146,9 @@ module Sidekiq
142
146
  end
143
147
  end
144
148
 
149
+ IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
150
+ private_constant :IGNORE_SHUTDOWN_INTERRUPTS
151
+
145
152
  def process(uow)
146
153
  jobstr = uow.job
147
154
  queue = uow.queue_name
@@ -152,15 +159,21 @@ module Sidekiq
152
159
  job_hash = Sidekiq.load_json(jobstr)
153
160
  rescue => ex
154
161
  handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
155
- # we can't notify because the job isn't a valid hash payload.
156
- DeadSet.new.kill(jobstr, notify_failure: false)
162
+ now = Time.now.to_f
163
+ redis do |conn|
164
+ conn.multi do |xa|
165
+ xa.zadd("dead", now.to_s, jobstr)
166
+ xa.zremrangebyscore("dead", "-inf", now - @capsule.config[:dead_timeout_in_seconds])
167
+ xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
168
+ end
169
+ end
157
170
  return uow.acknowledge
158
171
  end
159
172
 
160
173
  ack = false
161
174
  begin
162
175
  dispatch(job_hash, queue, jobstr) do |inst|
163
- @config.server_middleware.invoke(inst, job_hash, queue) do
176
+ config.server_middleware.invoke(inst, job_hash, queue) do
164
177
  execute_job(inst, job_hash["args"])
165
178
  end
166
179
  end
@@ -174,7 +187,7 @@ module Sidekiq
174
187
  # signals that we created a retry successfully. We can acknowlege the job.
175
188
  ack = true
176
189
  e = h.cause || h
177
- handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr})
190
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
178
191
  raise e
179
192
  rescue Exception => ex
180
193
  # Unexpected error! This is very bad and indicates an exception that got past
@@ -185,7 +198,7 @@ module Sidekiq
185
198
  ensure
186
199
  if ack
187
200
  # We don't want a shutdown signal to interrupt job acknowledgment.
188
- Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
201
+ Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
189
202
  uow.acknowledge
190
203
  end
191
204
  end
@@ -263,18 +276,5 @@ module Sidekiq
263
276
  PROCESSED.incr
264
277
  end
265
278
  end
266
-
267
- def constantize(str)
268
- return Object.const_get(str) unless str.include?("::")
269
-
270
- names = str.split("::")
271
- names.shift if names.empty? || names.first.empty?
272
-
273
- names.inject(Object) do |constant, name|
274
- # the false flag limits search for name to under the constant namespace
275
- # which mimics Rails' behaviour
276
- constant.const_get(name, false)
277
- end
278
- end
279
279
  end
280
280
  end
data/lib/sidekiq/rails.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq/job"
4
+ require "rails"
4
5
 
5
6
  module Sidekiq
6
7
  class Rails < ::Rails::Engine
@@ -10,7 +11,8 @@ module Sidekiq
10
11
  end
11
12
 
12
13
  def call
13
- @app.reloader.wrap do
14
+ params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
15
+ @app.reloader.wrap(**params) do
14
16
  yield
15
17
  end
16
18
  end
@@ -22,7 +24,7 @@ module Sidekiq
22
24
 
23
25
  # By including the Options module, we allow AJs to directly control sidekiq features
24
26
  # 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
27
+ # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
26
28
  # manually retried, don't automatically die, etc.
27
29
  #
28
30
  # class SomeJob < ActiveJob::Base
@@ -48,13 +50,6 @@ module Sidekiq
48
50
  end
49
51
  end
50
52
 
51
- config.before_configuration do
52
- dep = ActiveSupport::Deprecation.new("7.0", "Sidekiq")
53
- dep.deprecate_methods(Sidekiq.singleton_class,
54
- default_worker_options: :default_job_options,
55
- "default_worker_options=": :default_job_options=)
56
- end
57
-
58
53
  # This hook happens after all initializers are run, just before returning
59
54
  # from config/environment.rb back to sidekiq/cli.rb.
60
55
  #