sidekiq 5.2.6 → 7.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +537 -8
- data/LICENSE.txt +9 -0
- data/README.md +47 -50
- data/bin/sidekiq +22 -3
- data/bin/sidekiqload +213 -115
- data/bin/sidekiqmon +11 -0
- data/lib/generators/sidekiq/job_generator.rb +57 -0
- data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
- data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
- data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
- data/lib/sidekiq/api.rb +556 -351
- data/lib/sidekiq/capsule.rb +127 -0
- data/lib/sidekiq/cli.rb +203 -226
- data/lib/sidekiq/client.rb +121 -101
- data/lib/sidekiq/component.rb +68 -0
- data/lib/sidekiq/config.rb +274 -0
- data/lib/sidekiq/deploy.rb +62 -0
- data/lib/sidekiq/embedded.rb +61 -0
- data/lib/sidekiq/fetch.rb +49 -42
- data/lib/sidekiq/job.rb +374 -0
- data/lib/sidekiq/job_logger.rb +33 -7
- data/lib/sidekiq/job_retry.rb +131 -108
- data/lib/sidekiq/job_util.rb +105 -0
- data/lib/sidekiq/launcher.rb +203 -105
- data/lib/sidekiq/logger.rb +131 -0
- data/lib/sidekiq/manager.rb +43 -46
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +95 -0
- data/lib/sidekiq/metrics/tracking.rb +136 -0
- data/lib/sidekiq/middleware/chain.rb +113 -56
- data/lib/sidekiq/middleware/current_attributes.rb +56 -0
- data/lib/sidekiq/middleware/i18n.rb +7 -7
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +146 -0
- data/lib/sidekiq/paginator.rb +28 -16
- data/lib/sidekiq/processor.rb +108 -107
- data/lib/sidekiq/rails.rb +49 -38
- data/lib/sidekiq/redis_client_adapter.rb +96 -0
- data/lib/sidekiq/redis_connection.rb +38 -107
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +111 -49
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing/inline.rb +6 -5
- data/lib/sidekiq/testing.rb +66 -84
- data/lib/sidekiq/transaction_aware_client.rb +44 -0
- data/lib/sidekiq/version.rb +3 -1
- data/lib/sidekiq/web/action.rb +15 -11
- data/lib/sidekiq/web/application.rb +123 -79
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +137 -106
- data/lib/sidekiq/web/router.rb +23 -19
- data/lib/sidekiq/web.rb +56 -107
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +92 -182
- data/sidekiq.gemspec +25 -16
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +130 -61
- data/web/assets/javascripts/base-charts.js +106 -0
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +166 -0
- data/web/assets/javascripts/dashboard.js +36 -292
- data/web/assets/javascripts/metrics.js +264 -0
- data/web/assets/stylesheets/application-dark.css +147 -0
- data/web/assets/stylesheets/application-rtl.css +2 -95
- data/web/assets/stylesheets/application.css +102 -522
- data/web/locales/ar.yml +71 -65
- data/web/locales/cs.yml +62 -62
- data/web/locales/da.yml +60 -53
- data/web/locales/de.yml +65 -53
- data/web/locales/el.yml +43 -24
- data/web/locales/en.yml +84 -66
- data/web/locales/es.yml +70 -54
- data/web/locales/fa.yml +65 -65
- data/web/locales/fr.yml +83 -62
- data/web/locales/gd.yml +99 -0
- data/web/locales/he.yml +65 -64
- data/web/locales/hi.yml +59 -59
- data/web/locales/it.yml +53 -53
- data/web/locales/ja.yml +75 -64
- data/web/locales/ko.yml +52 -52
- data/web/locales/lt.yml +83 -0
- data/web/locales/nb.yml +61 -61
- data/web/locales/nl.yml +52 -52
- data/web/locales/pl.yml +45 -45
- data/web/locales/pt-br.yml +63 -55
- data/web/locales/pt.yml +51 -51
- data/web/locales/ru.yml +68 -63
- data/web/locales/sv.yml +53 -53
- data/web/locales/ta.yml +60 -60
- data/web/locales/uk.yml +62 -61
- data/web/locales/ur.yml +64 -64
- data/web/locales/vi.yml +83 -0
- data/web/locales/zh-cn.yml +43 -16
- data/web/locales/zh-tw.yml +42 -8
- data/web/views/_footer.erb +6 -3
- data/web/views/_job_info.erb +21 -4
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_nav.erb +1 -1
- data/web/views/_paging.erb +2 -0
- data/web/views/_poll_link.erb +3 -6
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +75 -25
- data/web/views/dashboard.erb +58 -18
- data/web/views/dead.erb +3 -3
- data/web/views/layout.erb +3 -1
- data/web/views/metrics.erb +82 -0
- data/web/views/metrics_for_job.erb +68 -0
- data/web/views/morgue.erb +14 -15
- data/web/views/queue.erb +33 -24
- data/web/views/queues.erb +13 -3
- data/web/views/retries.erb +16 -17
- data/web/views/retry.erb +3 -3
- data/web/views/scheduled.erb +17 -15
- metadata +69 -69
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -11
- data/.gitignore +0 -15
- data/.travis.yml +0 -11
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/5.0-Upgrade.md +0 -56
- data/COMM-LICENSE +0 -97
- data/Ent-Changes.md +0 -238
- data/Gemfile +0 -23
- data/LICENSE +0 -9
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-4.0-Upgrade.md +0 -35
- data/Pro-Changes.md +0 -759
- data/Rakefile +0 -9
- data/bin/sidekiqctl +0 -20
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/worker_generator.rb +0 -49
- data/lib/sidekiq/core_ext.rb +0 -1
- data/lib/sidekiq/ctl.rb +0 -221
- data/lib/sidekiq/delay.rb +0 -42
- data/lib/sidekiq/exception_handler.rb +0 -29
- data/lib/sidekiq/extensions/action_mailer.rb +0 -57
- data/lib/sidekiq/extensions/active_record.rb +0 -40
- data/lib/sidekiq/extensions/class_methods.rb +0 -40
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
- data/lib/sidekiq/logging.rb +0 -122
- data/lib/sidekiq/middleware/server/active_record.rb +0 -23
- data/lib/sidekiq/util.rb +0 -66
- data/lib/sidekiq/worker.rb +0 -220
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
#
|
3
4
|
# Simple middleware to save the current locale and restore it when the job executes.
|
4
5
|
# Use it by requiring it in your initializer:
|
@@ -9,19 +10,18 @@ module Sidekiq::Middleware::I18n
|
|
9
10
|
# Get the current locale and store it in the message
|
10
11
|
# to be sent to Sidekiq.
|
11
12
|
class Client
|
12
|
-
|
13
|
-
|
13
|
+
include Sidekiq::ClientMiddleware
|
14
|
+
def call(_jobclass, job, _queue, _redis)
|
15
|
+
job["locale"] ||= I18n.locale
|
14
16
|
yield
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
20
|
# Pull the msg locale out and set the current thread to use it.
|
19
21
|
class Server
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
ensure
|
24
|
-
I18n.locale = I18n.default_locale
|
22
|
+
include Sidekiq::ServerMiddleware
|
23
|
+
def call(_jobclass, job, _queue, &block)
|
24
|
+
I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
# Server-side middleware must import this Module in order
|
3
|
+
# to get access to server resources during `call`.
|
4
|
+
module ServerMiddleware
|
5
|
+
attr_accessor :config
|
6
|
+
def redis_pool
|
7
|
+
config.redis_pool
|
8
|
+
end
|
9
|
+
|
10
|
+
def logger
|
11
|
+
config.logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def redis(&block)
|
15
|
+
config.redis(&block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# no difference for now
|
20
|
+
ClientMiddleware = ServerMiddleware
|
21
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "sidekiq/api"
|
5
|
+
|
6
|
+
class Sidekiq::Monitor
|
7
|
+
class Status
|
8
|
+
VALID_SECTIONS = %w[all version overview processes queues]
|
9
|
+
COL_PAD = 2
|
10
|
+
|
11
|
+
def display(section = nil)
|
12
|
+
section ||= "all"
|
13
|
+
unless VALID_SECTIONS.include? section
|
14
|
+
puts "I don't know how to check the status of '#{section}'!"
|
15
|
+
puts "Try one of these: #{VALID_SECTIONS.join(", ")}"
|
16
|
+
return
|
17
|
+
end
|
18
|
+
send(section)
|
19
|
+
end
|
20
|
+
|
21
|
+
def all
|
22
|
+
version
|
23
|
+
puts
|
24
|
+
overview
|
25
|
+
puts
|
26
|
+
processes
|
27
|
+
puts
|
28
|
+
queues
|
29
|
+
end
|
30
|
+
|
31
|
+
def version
|
32
|
+
puts "Sidekiq #{Sidekiq::VERSION}"
|
33
|
+
puts Time.now.utc
|
34
|
+
end
|
35
|
+
|
36
|
+
def overview
|
37
|
+
puts "---- Overview ----"
|
38
|
+
puts " Processed: #{delimit stats.processed}"
|
39
|
+
puts " Failed: #{delimit stats.failed}"
|
40
|
+
puts " Busy: #{delimit stats.workers_size}"
|
41
|
+
puts " Enqueued: #{delimit stats.enqueued}"
|
42
|
+
puts " Retries: #{delimit stats.retry_size}"
|
43
|
+
puts " Scheduled: #{delimit stats.scheduled_size}"
|
44
|
+
puts " Dead: #{delimit stats.dead_size}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def processes
|
48
|
+
puts "---- Processes (#{process_set.size}) ----"
|
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
|
+
|
64
|
+
puts "#{process["identity"]} #{tags_for(process)}"
|
65
|
+
puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
|
66
|
+
puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
|
67
|
+
puts " Queues: #{split_multiline(queues, pad: 11)}"
|
68
|
+
puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
|
69
|
+
puts "" unless (index + 1) == process_set.size
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def queues
|
74
|
+
puts "---- Queues (#{queue_data.size}) ----"
|
75
|
+
columns = {
|
76
|
+
name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
|
77
|
+
size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
|
78
|
+
latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
|
79
|
+
}
|
80
|
+
columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
|
81
|
+
puts
|
82
|
+
queue_data.each do |q|
|
83
|
+
columns.each do |col, (dir, width)|
|
84
|
+
print q.send(col).public_send(dir, width)
|
85
|
+
end
|
86
|
+
puts
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def delimit(number)
|
93
|
+
number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
|
94
|
+
end
|
95
|
+
|
96
|
+
def split_multiline(values, opts = {})
|
97
|
+
return "none" unless values
|
98
|
+
pad = opts[:pad] || 0
|
99
|
+
max_length = opts[:max_length] || (80 - pad)
|
100
|
+
out = []
|
101
|
+
line = ""
|
102
|
+
values.each do |value|
|
103
|
+
if (line.length + value.length) > max_length
|
104
|
+
out << line
|
105
|
+
line = " " * pad
|
106
|
+
end
|
107
|
+
line << value + ", "
|
108
|
+
end
|
109
|
+
out << line[0..-3]
|
110
|
+
out.join("\n")
|
111
|
+
end
|
112
|
+
|
113
|
+
def tags_for(process)
|
114
|
+
tags = [
|
115
|
+
process["tag"],
|
116
|
+
process["labels"],
|
117
|
+
((process["quiet"] == "true") ? "quiet" : nil)
|
118
|
+
].flatten.compact
|
119
|
+
tags.any? ? "[#{tags.join("] [")}]" : nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def time_ago(timestamp)
|
123
|
+
seconds = Time.now - Time.at(timestamp)
|
124
|
+
return "just now" if seconds < 60
|
125
|
+
return "a minute ago" if seconds < 120
|
126
|
+
return "#{seconds.floor / 60} minutes ago" if seconds < 3600
|
127
|
+
return "an hour ago" if seconds < 7200
|
128
|
+
"#{seconds.floor / 60 / 60} hours ago"
|
129
|
+
end
|
130
|
+
|
131
|
+
QUEUE_STRUCT = Struct.new(:name, :size, :latency)
|
132
|
+
def queue_data
|
133
|
+
@queue_data ||= Sidekiq::Queue.all.map { |q|
|
134
|
+
QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def process_set
|
139
|
+
@process_set ||= Sidekiq::ProcessSet.new
|
140
|
+
end
|
141
|
+
|
142
|
+
def stats
|
143
|
+
@stats ||= Sidekiq::Stats.new
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/sidekiq/paginator.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Sidekiq
|
3
4
|
module Paginator
|
4
|
-
|
5
|
-
|
6
|
-
current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
|
5
|
+
def page(key, pageidx = 1, page_size = 25, opts = nil)
|
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 = []
|
@@ -12,26 +12,31 @@ module Sidekiq
|
|
12
12
|
|
13
13
|
Sidekiq.redis do |conn|
|
14
14
|
type = conn.type(key)
|
15
|
+
rev = opts && opts[:reverse]
|
15
16
|
|
16
17
|
case type
|
17
|
-
when
|
18
|
-
|
19
|
-
|
20
|
-
conn.zcard(key)
|
18
|
+
when "zset"
|
19
|
+
total_size, items = conn.multi { |transaction|
|
20
|
+
transaction.zcard(key)
|
21
21
|
if rev
|
22
|
-
|
22
|
+
transaction.zrange(key, starting, ending, "REV", withscores: true)
|
23
23
|
else
|
24
|
-
|
24
|
+
transaction.zrange(key, starting, ending, withscores: true)
|
25
25
|
end
|
26
|
-
|
26
|
+
}
|
27
27
|
[current_page, total_size, items]
|
28
|
-
when
|
29
|
-
total_size, items = conn.multi
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
when "list"
|
29
|
+
total_size, items = conn.multi { |transaction|
|
30
|
+
transaction.llen(key)
|
31
|
+
if rev
|
32
|
+
transaction.lrange(key, -ending - 1, -starting - 1)
|
33
|
+
else
|
34
|
+
transaction.lrange(key, starting, ending)
|
35
|
+
end
|
36
|
+
}
|
37
|
+
items.reverse! if rev
|
33
38
|
[current_page, total_size, items]
|
34
|
-
when
|
39
|
+
when "none"
|
35
40
|
[1, 0, []]
|
36
41
|
else
|
37
42
|
raise "can't page a #{type}"
|
@@ -39,5 +44,12 @@ module Sidekiq
|
|
39
44
|
end
|
40
45
|
end
|
41
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
|
42
54
|
end
|
43
55
|
end
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require 'thread'
|
2
|
+
|
3
|
+
require "sidekiq/fetch"
|
4
|
+
require "sidekiq/job_logger"
|
5
|
+
require "sidekiq/job_retry"
|
7
6
|
|
8
7
|
module Sidekiq
|
9
8
|
##
|
@@ -11,45 +10,45 @@ module Sidekiq
|
|
11
10
|
#
|
12
11
|
# 1. fetches a job from Redis
|
13
12
|
# 2. executes the job
|
14
|
-
# a. instantiate the
|
13
|
+
# a. instantiate the job class
|
15
14
|
# b. run the middleware chain
|
16
15
|
# c. call #perform
|
17
16
|
#
|
18
|
-
# A Processor can exit due to shutdown
|
19
|
-
#
|
17
|
+
# A Processor can exit due to shutdown or due to
|
18
|
+
# an error during job execution.
|
20
19
|
#
|
21
20
|
# If an error occurs in the job execution, the
|
22
21
|
# Processor calls the Manager to create a new one
|
23
22
|
# to replace itself and exits.
|
24
23
|
#
|
25
24
|
class Processor
|
26
|
-
|
27
|
-
include Util
|
25
|
+
include Sidekiq::Component
|
28
26
|
|
29
27
|
attr_reader :thread
|
30
28
|
attr_reader :job
|
29
|
+
attr_reader :capsule
|
31
30
|
|
32
|
-
def initialize(
|
33
|
-
@
|
31
|
+
def initialize(capsule, &block)
|
32
|
+
@config = @capsule = capsule
|
33
|
+
@callback = block
|
34
34
|
@down = false
|
35
35
|
@done = false
|
36
36
|
@job = nil
|
37
37
|
@thread = nil
|
38
|
-
@
|
39
|
-
@
|
40
|
-
@
|
41
|
-
@retrier = Sidekiq::JobRetry.new
|
38
|
+
@reloader = Sidekiq.default_configuration[:reloader]
|
39
|
+
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(logger)
|
40
|
+
@retrier = Sidekiq::JobRetry.new(capsule)
|
42
41
|
end
|
43
42
|
|
44
|
-
def terminate(wait=false)
|
43
|
+
def terminate(wait = false)
|
45
44
|
@done = true
|
46
|
-
return
|
45
|
+
return unless @thread
|
47
46
|
@thread.value if wait
|
48
47
|
end
|
49
48
|
|
50
|
-
def kill(wait=false)
|
49
|
+
def kill(wait = false)
|
51
50
|
@done = true
|
52
|
-
return
|
51
|
+
return unless @thread
|
53
52
|
# unlike the other actors, terminate does not wait
|
54
53
|
# for the thread to finish because we don't know how
|
55
54
|
# long the job will take to finish. Instead we
|
@@ -60,39 +59,40 @@ module Sidekiq
|
|
60
59
|
end
|
61
60
|
|
62
61
|
def start
|
63
|
-
@thread ||= safe_thread("processor", &method(:run))
|
62
|
+
@thread ||= safe_thread("#{config.name}/processor", &method(:run))
|
64
63
|
end
|
65
64
|
|
66
65
|
private unless $TESTING
|
67
66
|
|
68
67
|
def run
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
+
|
72
|
+
process_one until @done
|
73
|
+
@callback.call(self)
|
74
|
+
rescue Sidekiq::Shutdown
|
75
|
+
@callback.call(self)
|
76
|
+
rescue Exception => ex
|
77
|
+
@callback.call(self, ex)
|
79
78
|
end
|
80
79
|
|
81
|
-
def process_one
|
80
|
+
def process_one(&block)
|
82
81
|
@job = fetch
|
83
82
|
process(@job) if @job
|
84
83
|
@job = nil
|
85
84
|
end
|
86
85
|
|
87
86
|
def get_one
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
rescue Sidekiq::Shutdown
|
93
|
-
rescue => ex
|
94
|
-
handle_fetch_exception(ex)
|
87
|
+
uow = capsule.fetcher.retrieve_work
|
88
|
+
if @down
|
89
|
+
logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
|
90
|
+
@down = nil
|
95
91
|
end
|
92
|
+
uow
|
93
|
+
rescue Sidekiq::Shutdown
|
94
|
+
rescue => ex
|
95
|
+
handle_fetch_exception(ex)
|
96
96
|
end
|
97
97
|
|
98
98
|
def fetch
|
@@ -106,7 +106,7 @@ module Sidekiq
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def handle_fetch_exception(ex)
|
109
|
-
|
109
|
+
unless @down
|
110
110
|
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
111
111
|
logger.error("Error fetching job: #{ex}")
|
112
112
|
handle_exception(ex)
|
@@ -115,26 +115,29 @@ module Sidekiq
|
|
115
115
|
nil
|
116
116
|
end
|
117
117
|
|
118
|
-
def dispatch(job_hash, queue)
|
118
|
+
def dispatch(job_hash, queue, jobstr)
|
119
119
|
# since middleware can mutate the job hash
|
120
|
-
# we clone
|
120
|
+
# we need to clone it to report the original
|
121
121
|
# job structure to the Web UI
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
122
|
+
# or to push back to redis when retrying.
|
123
|
+
# To avoid costly and, most of the time, useless cloning here,
|
124
|
+
# we pass original String of JSON to respected methods
|
125
|
+
# to re-parse it there if we need access to the original, untouched job
|
126
|
+
|
127
|
+
@job_logger.prepare(job_hash) do
|
128
|
+
@retrier.global(jobstr, queue) do
|
129
|
+
@job_logger.call(job_hash, queue) do
|
130
|
+
stats(jobstr, queue) do
|
128
131
|
# Rails 5 requires a Reloader to wrap code execution. In order to
|
129
132
|
# constantize the worker and instantiate an instance, we have to call
|
130
133
|
# the Reloader. It handles code loading, db connection management, etc.
|
131
134
|
# Effectively this block denotes a "unit of work" to Rails.
|
132
135
|
@reloader.call do
|
133
|
-
klass
|
134
|
-
|
135
|
-
|
136
|
-
@retrier.local(
|
137
|
-
yield
|
136
|
+
klass = Object.const_get(job_hash["class"])
|
137
|
+
inst = klass.new
|
138
|
+
inst.jid = job_hash["jid"]
|
139
|
+
@retrier.local(inst, jobstr, queue) do
|
140
|
+
yield inst
|
138
141
|
end
|
139
142
|
end
|
140
143
|
end
|
@@ -143,53 +146,67 @@ module Sidekiq
|
|
143
146
|
end
|
144
147
|
end
|
145
148
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
+
IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
|
150
|
+
private_constant :IGNORE_SHUTDOWN_INTERRUPTS
|
151
|
+
|
152
|
+
def process(uow)
|
153
|
+
jobstr = uow.job
|
154
|
+
queue = uow.queue_name
|
149
155
|
|
150
156
|
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
151
157
|
job_hash = nil
|
152
158
|
begin
|
153
159
|
job_hash = Sidekiq.load_json(jobstr)
|
154
160
|
rescue => ex
|
155
|
-
handle_exception(ex, {
|
156
|
-
|
157
|
-
|
158
|
-
|
161
|
+
handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
|
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
|
170
|
+
return uow.acknowledge
|
159
171
|
end
|
160
172
|
|
161
|
-
ack =
|
173
|
+
ack = false
|
162
174
|
begin
|
163
|
-
dispatch(job_hash, queue) do |
|
164
|
-
|
165
|
-
execute_job(
|
175
|
+
dispatch(job_hash, queue, jobstr) do |inst|
|
176
|
+
config.server_middleware.invoke(inst, job_hash, queue) do
|
177
|
+
execute_job(inst, job_hash["args"])
|
166
178
|
end
|
167
179
|
end
|
180
|
+
ack = true
|
168
181
|
rescue Sidekiq::Shutdown
|
169
182
|
# Had to force kill this job because it didn't finish
|
170
183
|
# within the timeout. Don't acknowledge the work since
|
171
184
|
# we didn't properly finish it.
|
172
|
-
ack = false
|
173
185
|
rescue Sidekiq::JobRetry::Handled => h
|
174
186
|
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
175
187
|
# signals that we created a retry successfully. We can acknowlege the job.
|
176
|
-
|
177
|
-
|
188
|
+
ack = true
|
189
|
+
e = h.cause || h
|
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
|
181
194
|
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
182
195
|
# so it can be rescued when using Sidekiq Pro.
|
183
|
-
|
184
|
-
|
185
|
-
raise e
|
196
|
+
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
197
|
+
raise ex
|
186
198
|
ensure
|
187
|
-
|
199
|
+
if ack
|
200
|
+
# We don't want a shutdown signal to interrupt job acknowledgment.
|
201
|
+
Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
|
202
|
+
uow.acknowledge
|
203
|
+
end
|
204
|
+
end
|
188
205
|
end
|
189
206
|
end
|
190
207
|
|
191
|
-
def execute_job(
|
192
|
-
|
208
|
+
def execute_job(inst, cloned_args)
|
209
|
+
inst.perform(*cloned_args)
|
193
210
|
end
|
194
211
|
|
195
212
|
# Ruby doesn't provide atomic counters out of the box so we'll
|
@@ -201,50 +218,53 @@ module Sidekiq
|
|
201
218
|
@lock = Mutex.new
|
202
219
|
end
|
203
220
|
|
204
|
-
def incr(amount=1)
|
205
|
-
@lock.synchronize { @value
|
221
|
+
def incr(amount = 1)
|
222
|
+
@lock.synchronize { @value += amount }
|
206
223
|
end
|
207
224
|
|
208
225
|
def reset
|
209
|
-
@lock.synchronize {
|
226
|
+
@lock.synchronize {
|
227
|
+
val = @value
|
228
|
+
@value = 0
|
229
|
+
val
|
230
|
+
}
|
210
231
|
end
|
211
232
|
end
|
212
233
|
|
213
234
|
# jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
|
214
|
-
class
|
235
|
+
class SharedWorkState
|
215
236
|
def initialize
|
216
|
-
@
|
237
|
+
@work_state = {}
|
217
238
|
@lock = Mutex.new
|
218
239
|
end
|
219
240
|
|
220
241
|
def set(tid, hash)
|
221
|
-
@lock.synchronize { @
|
242
|
+
@lock.synchronize { @work_state[tid] = hash }
|
222
243
|
end
|
223
244
|
|
224
245
|
def delete(tid)
|
225
|
-
@lock.synchronize { @
|
246
|
+
@lock.synchronize { @work_state.delete(tid) }
|
226
247
|
end
|
227
248
|
|
228
249
|
def dup
|
229
|
-
@lock.synchronize { @
|
250
|
+
@lock.synchronize { @work_state.dup }
|
230
251
|
end
|
231
252
|
|
232
253
|
def size
|
233
|
-
@lock.synchronize { @
|
254
|
+
@lock.synchronize { @work_state.size }
|
234
255
|
end
|
235
256
|
|
236
257
|
def clear
|
237
|
-
@lock.synchronize { @
|
258
|
+
@lock.synchronize { @work_state.clear }
|
238
259
|
end
|
239
260
|
end
|
240
261
|
|
241
262
|
PROCESSED = Counter.new
|
242
263
|
FAILURE = Counter.new
|
243
|
-
|
264
|
+
WORK_STATE = SharedWorkState.new
|
244
265
|
|
245
|
-
def stats(
|
246
|
-
tid
|
247
|
-
WORKER_STATE.set(tid, {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i })
|
266
|
+
def stats(jobstr, queue)
|
267
|
+
WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
|
248
268
|
|
249
269
|
begin
|
250
270
|
yield
|
@@ -252,28 +272,9 @@ module Sidekiq
|
|
252
272
|
FAILURE.incr
|
253
273
|
raise
|
254
274
|
ensure
|
255
|
-
|
275
|
+
WORK_STATE.delete(tid)
|
256
276
|
PROCESSED.incr
|
257
277
|
end
|
258
278
|
end
|
259
|
-
|
260
|
-
# Deep clone the arguments passed to the worker so that if
|
261
|
-
# the job fails, what is pushed back onto Redis hasn't
|
262
|
-
# been mutated by the worker.
|
263
|
-
def cloned(thing)
|
264
|
-
Marshal.load(Marshal.dump(thing))
|
265
|
-
end
|
266
|
-
|
267
|
-
def constantize(str)
|
268
|
-
names = str.split('::')
|
269
|
-
names.shift if names.empty? || names.first.empty?
|
270
|
-
|
271
|
-
names.inject(Object) do |constant, name|
|
272
|
-
# the false flag limits search for name to under the constant namespace
|
273
|
-
# which mimics Rails' behaviour
|
274
|
-
constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
279
|
end
|
279
280
|
end
|