sidekiq 7.0.0 → 7.3.0
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.
- checksums.yaml +4 -4
- data/Changes.md +261 -13
- data/README.md +34 -27
- data/bin/multi_queue_bench +271 -0
- data/bin/sidekiqload +204 -109
- data/bin/sidekiqmon +3 -0
- data/lib/sidekiq/api.rb +151 -23
- data/lib/sidekiq/capsule.rb +20 -0
- data/lib/sidekiq/cli.rb +9 -4
- data/lib/sidekiq/client.rb +40 -24
- data/lib/sidekiq/component.rb +3 -1
- data/lib/sidekiq/config.rb +32 -12
- data/lib/sidekiq/deploy.rb +5 -5
- data/lib/sidekiq/embedded.rb +3 -3
- data/lib/sidekiq/fetch.rb +3 -5
- data/lib/sidekiq/iterable_job.rb +53 -0
- data/lib/sidekiq/job/interrupt_handler.rb +22 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +231 -0
- data/lib/sidekiq/job.rb +17 -10
- data/lib/sidekiq/job_logger.rb +24 -11
- data/lib/sidekiq/job_retry.rb +34 -11
- data/lib/sidekiq/job_util.rb +51 -15
- data/lib/sidekiq/launcher.rb +38 -22
- data/lib/sidekiq/logger.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +6 -3
- data/lib/sidekiq/metrics/shared.rb +4 -4
- data/lib/sidekiq/metrics/tracking.rb +9 -3
- data/lib/sidekiq/middleware/chain.rb +12 -9
- data/lib/sidekiq/middleware/current_attributes.rb +70 -17
- data/lib/sidekiq/monitor.rb +17 -4
- data/lib/sidekiq/paginator.rb +4 -4
- data/lib/sidekiq/processor.rb +41 -27
- data/lib/sidekiq/rails.rb +18 -8
- data/lib/sidekiq/redis_client_adapter.rb +31 -35
- data/lib/sidekiq/redis_connection.rb +29 -7
- data/lib/sidekiq/scheduled.rb +4 -4
- data/lib/sidekiq/testing.rb +27 -8
- data/lib/sidekiq/transaction_aware_client.rb +7 -0
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +10 -4
- data/lib/sidekiq/web/application.rb +113 -16
- data/lib/sidekiq/web/csrf_protection.rb +9 -6
- data/lib/sidekiq/web/helpers.rb +104 -33
- data/lib/sidekiq/web.rb +63 -2
- data/lib/sidekiq.rb +2 -1
- data/sidekiq.gemspec +8 -29
- data/web/assets/javascripts/application.js +45 -0
- data/web/assets/javascripts/dashboard-charts.js +38 -12
- data/web/assets/javascripts/dashboard.js +8 -10
- data/web/assets/javascripts/metrics.js +64 -2
- data/web/assets/stylesheets/application-dark.css +4 -0
- data/web/assets/stylesheets/application-rtl.css +10 -0
- data/web/assets/stylesheets/application.css +38 -4
- data/web/locales/da.yml +11 -4
- data/web/locales/en.yml +2 -0
- data/web/locales/fr.yml +14 -0
- data/web/locales/gd.yml +99 -0
- data/web/locales/ja.yml +3 -1
- data/web/locales/pt-br.yml +20 -0
- data/web/locales/tr.yml +101 -0
- data/web/locales/zh-cn.yml +20 -19
- data/web/views/_footer.erb +14 -2
- data/web/views/_job_info.erb +18 -2
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_paging.erb +2 -0
- data/web/views/_poll_link.erb +1 -1
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +46 -35
- data/web/views/dashboard.erb +25 -35
- data/web/views/filtering.erb +7 -0
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +42 -31
- data/web/views/metrics_for_job.erb +41 -51
- data/web/views/morgue.erb +5 -9
- data/web/views/queue.erb +10 -14
- data/web/views/queues.erb +9 -3
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +37 -32
data/lib/sidekiq/launcher.rb
CHANGED
@@ -32,15 +32,18 @@ module Sidekiq
|
|
32
32
|
@done = false
|
33
33
|
end
|
34
34
|
|
35
|
-
|
35
|
+
# Start this Sidekiq instance. If an embedding process already
|
36
|
+
# has a heartbeat thread, caller can use `async_beat: false`
|
37
|
+
# and instead have thread call Launcher#heartbeat every N seconds.
|
38
|
+
def run(async_beat: true)
|
36
39
|
Sidekiq.freeze!
|
37
|
-
|
40
|
+
logger.debug { @config.merge!({}) }
|
41
|
+
@thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
|
38
42
|
@poller.start
|
39
43
|
@managers.each(&:start)
|
40
44
|
end
|
41
45
|
|
42
46
|
# Stops this instance from processing any more jobs,
|
43
|
-
#
|
44
47
|
def quiet
|
45
48
|
return if @done
|
46
49
|
|
@@ -71,18 +74,30 @@ module Sidekiq
|
|
71
74
|
@done
|
72
75
|
end
|
73
76
|
|
77
|
+
# If embedding Sidekiq, you can have the process heartbeat
|
78
|
+
# call this method to regularly heartbeat rather than creating
|
79
|
+
# a separate thread.
|
80
|
+
def heartbeat
|
81
|
+
❤
|
82
|
+
end
|
83
|
+
|
74
84
|
private unless $TESTING
|
75
85
|
|
76
86
|
BEAT_PAUSE = 10
|
77
87
|
|
78
88
|
def start_heartbeat
|
79
89
|
loop do
|
80
|
-
|
90
|
+
beat
|
81
91
|
sleep BEAT_PAUSE
|
82
92
|
end
|
83
93
|
logger.info("Heartbeat stopping...")
|
84
94
|
end
|
85
95
|
|
96
|
+
def beat
|
97
|
+
$0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ") unless @embedded
|
98
|
+
❤
|
99
|
+
end
|
100
|
+
|
86
101
|
def clear_heartbeat
|
87
102
|
flush_stats
|
88
103
|
|
@@ -99,12 +114,6 @@ module Sidekiq
|
|
99
114
|
# best effort, ignore network errors
|
100
115
|
end
|
101
116
|
|
102
|
-
def heartbeat
|
103
|
-
$0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ") unless @embedded
|
104
|
-
|
105
|
-
❤
|
106
|
-
end
|
107
|
-
|
108
117
|
def flush_stats
|
109
118
|
fails = Processor::FAILURE.reset
|
110
119
|
procd = Processor::PROCESSED.reset
|
@@ -136,15 +145,17 @@ module Sidekiq
|
|
136
145
|
flush_stats
|
137
146
|
|
138
147
|
curstate = Processor::WORK_STATE.dup
|
148
|
+
curstate.transform_values! { |val| Sidekiq.dump_json(val) }
|
149
|
+
|
139
150
|
redis do |conn|
|
140
151
|
# work is the current set of executing jobs
|
141
152
|
work_key = "#{key}:work"
|
142
|
-
conn.
|
153
|
+
conn.multi do |transaction|
|
143
154
|
transaction.unlink(work_key)
|
144
|
-
curstate.
|
145
|
-
transaction.hset(work_key,
|
155
|
+
if curstate.size > 0
|
156
|
+
transaction.hset(work_key, curstate)
|
157
|
+
transaction.expire(work_key, 60)
|
146
158
|
end
|
147
|
-
transaction.expire(work_key, 60)
|
148
159
|
end
|
149
160
|
end
|
150
161
|
|
@@ -153,11 +164,11 @@ module Sidekiq
|
|
153
164
|
fails = procd = 0
|
154
165
|
kb = memory_usage(::Process.pid)
|
155
166
|
|
156
|
-
_, exists, _, _,
|
167
|
+
_, exists, _, _, signal = redis { |conn|
|
157
168
|
conn.multi { |transaction|
|
158
169
|
transaction.sadd("processes", [key])
|
159
170
|
transaction.exists(key)
|
160
|
-
transaction.
|
171
|
+
transaction.hset(key, "info", to_json,
|
161
172
|
"busy", curstate.size,
|
162
173
|
"beat", Time.now.to_f,
|
163
174
|
"rtt_us", rtt,
|
@@ -172,9 +183,7 @@ module Sidekiq
|
|
172
183
|
fire_event(:heartbeat) unless exists > 0
|
173
184
|
fire_event(:beat, oneshot: false)
|
174
185
|
|
175
|
-
|
176
|
-
|
177
|
-
::Process.kill(msg, ::Process.pid)
|
186
|
+
::Process.kill(signal, ::Process.pid) if signal && !@embedded
|
178
187
|
rescue => e
|
179
188
|
# ignore all redis/network issues
|
180
189
|
logger.error("heartbeat: #{e}")
|
@@ -208,7 +217,7 @@ module Sidekiq
|
|
208
217
|
Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000.
|
209
218
|
Ensure Redis is running in the same AZ or datacenter as Sidekiq.
|
210
219
|
If these values are close to 100,000, that means your Sidekiq process may be
|
211
|
-
CPU-saturated; reduce your concurrency and/or see https://github.com/
|
220
|
+
CPU-saturated; reduce your concurrency and/or see https://github.com/sidekiq/sidekiq/discussions/5039
|
212
221
|
EOM
|
213
222
|
RTT_READINGS.reset
|
214
223
|
end
|
@@ -242,12 +251,19 @@ module Sidekiq
|
|
242
251
|
"pid" => ::Process.pid,
|
243
252
|
"tag" => @config[:tag] || "",
|
244
253
|
"concurrency" => @config.total_concurrency,
|
245
|
-
"queues" => @config.capsules.values.
|
254
|
+
"queues" => @config.capsules.values.flat_map { |cap| cap.queues }.uniq,
|
255
|
+
"weights" => to_weights,
|
246
256
|
"labels" => @config[:labels].to_a,
|
247
|
-
"identity" => identity
|
257
|
+
"identity" => identity,
|
258
|
+
"version" => Sidekiq::VERSION,
|
259
|
+
"embedded" => @embedded
|
248
260
|
}
|
249
261
|
end
|
250
262
|
|
263
|
+
def to_weights
|
264
|
+
@config.capsules.values.map(&:weights)
|
265
|
+
end
|
266
|
+
|
251
267
|
def to_json
|
252
268
|
# this data changes infrequently so dump it to a string
|
253
269
|
# now so we don't need to dump it every heartbeat.
|
data/lib/sidekiq/logger.rb
CHANGED
@@ -20,7 +20,8 @@ module Sidekiq
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Get metric data for all jobs from the last hour
|
23
|
-
|
23
|
+
# +class_filter+: return only results for classes matching filter
|
24
|
+
def top_jobs(class_filter: nil, minutes: 60)
|
24
25
|
result = Result.new
|
25
26
|
|
26
27
|
time = @time
|
@@ -39,6 +40,7 @@ module Sidekiq
|
|
39
40
|
redis_results.each do |hash|
|
40
41
|
hash.each do |k, v|
|
41
42
|
kls, metric = k.split("|")
|
43
|
+
next if class_filter && !class_filter.match?(kls)
|
42
44
|
result.job_results[kls].add_metric metric, time, v.to_i
|
43
45
|
end
|
44
46
|
time -= 60
|
@@ -70,7 +72,7 @@ module Sidekiq
|
|
70
72
|
result.job_results[klass].add_metric "ms", time, ms.to_i if ms
|
71
73
|
result.job_results[klass].add_metric "p", time, p.to_i if p
|
72
74
|
result.job_results[klass].add_metric "f", time, f.to_i if f
|
73
|
-
result.job_results[klass].add_hist time, Histogram.new(klass).fetch(conn, time)
|
75
|
+
result.job_results[klass].add_hist time, Histogram.new(klass).fetch(conn, time).reverse
|
74
76
|
time -= 60
|
75
77
|
end
|
76
78
|
end
|
@@ -117,13 +119,14 @@ module Sidekiq
|
|
117
119
|
|
118
120
|
def total_avg(metric = "ms")
|
119
121
|
completed = totals["p"] - totals["f"]
|
122
|
+
return 0 if completed.zero?
|
120
123
|
totals[metric].to_f / completed
|
121
124
|
end
|
122
125
|
|
123
126
|
def series_avg(metric = "ms")
|
124
127
|
series[metric].each_with_object(Hash.new(0)) do |(bucket, value), result|
|
125
128
|
completed = series.dig("p", bucket) - series.dig("f", bucket)
|
126
|
-
result[bucket] = completed == 0 ? 0 : value.to_f / completed
|
129
|
+
result[bucket] = (completed == 0) ? 0 : value.to_f / completed
|
127
130
|
end
|
128
131
|
end
|
129
132
|
end
|
@@ -29,8 +29,8 @@ module Sidekiq
|
|
29
29
|
1100, 1700, 2500, 3800, 5750,
|
30
30
|
8500, 13000, 20000, 30000, 45000,
|
31
31
|
65000, 100000, 150000, 225000, 335000,
|
32
|
-
|
33
|
-
]
|
32
|
+
1e20 # the "maybe your job is too long" bucket
|
33
|
+
].freeze
|
34
34
|
LABELS = [
|
35
35
|
"20ms", "30ms", "45ms", "65ms", "100ms",
|
36
36
|
"150ms", "225ms", "335ms", "500ms", "750ms",
|
@@ -38,7 +38,7 @@ module Sidekiq
|
|
38
38
|
"8.5s", "13s", "20s", "30s", "45s",
|
39
39
|
"65s", "100s", "150s", "225s", "335s",
|
40
40
|
"Slow"
|
41
|
-
]
|
41
|
+
].freeze
|
42
42
|
FETCH = "GET u16 #0 GET u16 #1 GET u16 #2 GET u16 #3 \
|
43
43
|
GET u16 #4 GET u16 #5 GET u16 #6 GET u16 #7 \
|
44
44
|
GET u16 #8 GET u16 #9 GET u16 #10 GET u16 #11 \
|
@@ -73,7 +73,7 @@ module Sidekiq
|
|
73
73
|
def fetch(conn, now = Time.now)
|
74
74
|
window = now.utc.strftime("%d-%H:%-M")
|
75
75
|
key = "#{@klass}-#{window}"
|
76
|
-
conn.
|
76
|
+
conn.bitfield_ro(key, *FETCH)
|
77
77
|
end
|
78
78
|
|
79
79
|
def persist(conn, now = Time.now)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "time"
|
2
4
|
require "sidekiq"
|
3
5
|
require "sidekiq/metrics/shared"
|
@@ -101,12 +103,16 @@ module Sidekiq
|
|
101
103
|
def reset
|
102
104
|
@lock.synchronize {
|
103
105
|
array = [@totals, @jobs, @grams]
|
104
|
-
|
105
|
-
@jobs = Hash.new(0)
|
106
|
-
@grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
|
106
|
+
reset_instance_variables
|
107
107
|
array
|
108
108
|
}
|
109
109
|
end
|
110
|
+
|
111
|
+
def reset_instance_variables
|
112
|
+
@totals = Hash.new(0)
|
113
|
+
@jobs = Hash.new(0)
|
114
|
+
@grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) }
|
115
|
+
end
|
110
116
|
end
|
111
117
|
|
112
118
|
class Middleware
|
@@ -166,23 +166,26 @@ module Sidekiq
|
|
166
166
|
|
167
167
|
# Used by Sidekiq to execute the middleware at runtime
|
168
168
|
# @api private
|
169
|
-
def invoke(*args)
|
169
|
+
def invoke(*args, &block)
|
170
170
|
return yield if empty?
|
171
171
|
|
172
172
|
chain = retrieve
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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)
|
178
184
|
end
|
179
185
|
end
|
180
|
-
traverse_chain.call
|
181
186
|
end
|
182
187
|
end
|
183
188
|
|
184
|
-
private
|
185
|
-
|
186
189
|
# Represents each link in the middleware chain
|
187
190
|
# @api private
|
188
191
|
class Entry
|
@@ -7,27 +7,31 @@ module Sidekiq
|
|
7
7
|
# This can be useful for multi-tenancy, i18n locale, timezone, any implicit
|
8
8
|
# per-request attribute. See +ActiveSupport::CurrentAttributes+.
|
9
9
|
#
|
10
|
+
# For multiple current attributes, pass an array of current attributes.
|
11
|
+
#
|
10
12
|
# @example
|
11
13
|
#
|
12
14
|
# # in your initializer
|
13
15
|
# require "sidekiq/middleware/current_attributes"
|
14
16
|
# Sidekiq::CurrentAttributes.persist("Myapp::Current")
|
17
|
+
# # or multiple current attributes
|
18
|
+
# Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
|
15
19
|
#
|
16
20
|
module CurrentAttributes
|
17
21
|
class Save
|
18
22
|
include Sidekiq::ClientMiddleware
|
19
23
|
|
20
|
-
def initialize(
|
21
|
-
@
|
24
|
+
def initialize(cattrs)
|
25
|
+
@cattrs = cattrs
|
22
26
|
end
|
23
27
|
|
24
28
|
def call(_, job, _, _)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
job
|
29
|
-
|
30
|
-
job[
|
29
|
+
@cattrs.each do |(key, strklass)|
|
30
|
+
if !job.has_key?(key)
|
31
|
+
attrs = strklass.constantize.attributes
|
32
|
+
# Retries can push the job N times, we don't
|
33
|
+
# want retries to reset cattr. #5692, #5090
|
34
|
+
job[key] = attrs if attrs.any?
|
31
35
|
end
|
32
36
|
end
|
33
37
|
yield
|
@@ -37,22 +41,71 @@ module Sidekiq
|
|
37
41
|
class Load
|
38
42
|
include Sidekiq::ServerMiddleware
|
39
43
|
|
40
|
-
def initialize(
|
41
|
-
@
|
44
|
+
def initialize(cattrs)
|
45
|
+
@cattrs = cattrs
|
42
46
|
end
|
43
47
|
|
44
48
|
def call(_, job, _, &block)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
+
klass_attrs = {}
|
50
|
+
|
51
|
+
@cattrs.each do |(key, strklass)|
|
52
|
+
next unless job.has_key?(key)
|
53
|
+
|
54
|
+
klass_attrs[strklass.constantize] = job[key]
|
55
|
+
end
|
56
|
+
|
57
|
+
wrap(klass_attrs.to_a, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def wrap(klass_attrs, &block)
|
63
|
+
klass, attrs = klass_attrs.shift
|
64
|
+
return block.call unless klass
|
65
|
+
|
66
|
+
retried = false
|
67
|
+
|
68
|
+
begin
|
69
|
+
klass.set(attrs) do
|
70
|
+
wrap(klass_attrs, &block)
|
71
|
+
end
|
72
|
+
rescue NoMethodError
|
73
|
+
raise if retried
|
74
|
+
|
75
|
+
# It is possible that the `CurrentAttributes` definition
|
76
|
+
# was changed before the job started processing.
|
77
|
+
attrs = attrs.select { |attr| klass.respond_to?(attr) }
|
78
|
+
retried = true
|
79
|
+
retry
|
49
80
|
end
|
50
81
|
end
|
51
82
|
end
|
52
83
|
|
53
|
-
|
54
|
-
|
55
|
-
|
84
|
+
class << self
|
85
|
+
def persist(klass_or_array, config = Sidekiq.default_configuration)
|
86
|
+
cattrs = build_cattrs_hash(klass_or_array)
|
87
|
+
|
88
|
+
config.client_middleware.add Save, cattrs
|
89
|
+
config.server_middleware.add Load, cattrs
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def build_cattrs_hash(klass_or_array)
|
95
|
+
if klass_or_array.is_a?(Array)
|
96
|
+
{}.tap do |hash|
|
97
|
+
klass_or_array.each_with_index do |klass, index|
|
98
|
+
hash[key_at(index)] = klass.to_s
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
{key_at(0) => klass_or_array.to_s}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def key_at(index)
|
107
|
+
(index == 0) ? "cattr" : "cattr_#{index}"
|
108
|
+
end
|
56
109
|
end
|
57
110
|
end
|
58
111
|
end
|
data/lib/sidekiq/monitor.rb
CHANGED
@@ -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(
|
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
|
data/lib/sidekiq/paginator.rb
CHANGED
@@ -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 = []
|
@@ -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.
|
22
|
+
transaction.zrange(key, starting, ending, "REV", "withscores")
|
23
23
|
else
|
24
|
-
transaction.zrange(key, starting, ending, withscores
|
24
|
+
transaction.zrange(key, starting, ending, "withscores")
|
25
25
|
end
|
26
26
|
}
|
27
27
|
[current_page, total_size, items]
|
@@ -45,7 +45,7 @@ module Sidekiq
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def page_items(items, pageidx = 1, page_size = 25)
|
48
|
-
current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
|
48
|
+
current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
|
49
49
|
pageidx = current_page - 1
|
50
50
|
starting = pageidx * page_size
|
51
51
|
items = items.to_a
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -36,7 +36,7 @@ module Sidekiq
|
|
36
36
|
@job = nil
|
37
37
|
@thread = nil
|
38
38
|
@reloader = Sidekiq.default_configuration[:reloader]
|
39
|
-
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(
|
39
|
+
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(capsule.config)
|
40
40
|
@retrier = Sidekiq::JobRetry.new(capsule)
|
41
41
|
end
|
42
42
|
|
@@ -58,6 +58,10 @@ 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
66
|
@thread ||= safe_thread("#{config.name}/processor", &method(:run))
|
63
67
|
end
|
@@ -136,6 +140,7 @@ module Sidekiq
|
|
136
140
|
klass = Object.const_get(job_hash["class"])
|
137
141
|
inst = klass.new
|
138
142
|
inst.jid = job_hash["jid"]
|
143
|
+
inst._context = self
|
139
144
|
@retrier.local(inst, jobstr, queue) do
|
140
145
|
yield inst
|
141
146
|
end
|
@@ -146,6 +151,11 @@ module Sidekiq
|
|
146
151
|
end
|
147
152
|
end
|
148
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
|
+
|
149
159
|
def process(uow)
|
150
160
|
jobstr = uow.job
|
151
161
|
queue = uow.queue_name
|
@@ -168,36 +178,40 @@ module Sidekiq
|
|
168
178
|
end
|
169
179
|
|
170
180
|
ack = false
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
181
|
+
Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
|
182
|
+
Thread.handle_interrupt(ALLOW_SHUTDOWN_INTERRUPTS) do
|
183
|
+
dispatch(job_hash, queue, jobstr) do |inst|
|
184
|
+
config.server_middleware.invoke(inst, job_hash, queue) do
|
185
|
+
execute_job(inst, job_hash["args"])
|
186
|
+
end
|
175
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
|
176
211
|
end
|
177
|
-
ack = true
|
178
|
-
rescue Sidekiq::Shutdown
|
179
|
-
# Had to force kill this job because it didn't finish
|
180
|
-
# within the timeout. Don't acknowledge the work since
|
181
|
-
# we didn't properly finish it.
|
182
|
-
rescue Sidekiq::JobRetry::Handled => h
|
183
|
-
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
184
|
-
# signals that we created a retry successfully. We can acknowlege the job.
|
185
|
-
ack = true
|
186
|
-
e = h.cause || h
|
187
|
-
handle_exception(e, {context: "Job raised exception", job: job_hash})
|
188
|
-
raise e
|
189
|
-
rescue Exception => ex
|
190
|
-
# Unexpected error! This is very bad and indicates an exception that got past
|
191
|
-
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
192
|
-
# so it can be rescued when using Sidekiq Pro.
|
193
|
-
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
194
|
-
raise ex
|
195
212
|
ensure
|
196
213
|
if ack
|
197
|
-
|
198
|
-
Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
|
199
|
-
uow.acknowledge
|
200
|
-
end
|
214
|
+
uow.acknowledge
|
201
215
|
end
|
202
216
|
end
|
203
217
|
end
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -11,7 +11,8 @@ module Sidekiq
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def call
|
14
|
-
|
14
|
+
params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
|
15
|
+
@app.reloader.wrap(**params) do
|
15
16
|
yield
|
16
17
|
end
|
17
18
|
end
|
@@ -19,6 +20,10 @@ module Sidekiq
|
|
19
20
|
def inspect
|
20
21
|
"#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
|
21
22
|
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
{app: @app.class.name}
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
29
|
# By including the Options module, we allow AJs to directly control sidekiq features
|
@@ -38,14 +43,9 @@ module Sidekiq
|
|
38
43
|
end
|
39
44
|
end
|
40
45
|
|
41
|
-
initializer "sidekiq.
|
46
|
+
initializer "sidekiq.backtrace_cleaner" do
|
42
47
|
Sidekiq.configure_server do |config|
|
43
|
-
|
44
|
-
# it will appear in the Sidekiq console with all of the job context. See #5021 and
|
45
|
-
# https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84
|
46
|
-
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
47
|
-
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
48
|
-
end
|
48
|
+
config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -56,6 +56,16 @@ module Sidekiq
|
|
56
56
|
config.after_initialize do
|
57
57
|
Sidekiq.configure_server do |config|
|
58
58
|
config[:reloader] = Sidekiq::Rails::Reloader.new
|
59
|
+
|
60
|
+
# This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
|
61
|
+
# it will appear in the Sidekiq console with all of the job context.
|
62
|
+
unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
|
63
|
+
if ::Rails.logger.respond_to?(:broadcast_to)
|
64
|
+
::Rails.logger.broadcast_to(config.logger)
|
65
|
+
else
|
66
|
+
::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
|
67
|
+
end
|
68
|
+
end
|
59
69
|
end
|
60
70
|
end
|
61
71
|
end
|