sidekiq 5.1.3 → 6.4.1
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 +5 -5
- data/Changes.md +391 -1
- data/LICENSE +3 -3
- data/README.md +23 -34
- data/bin/sidekiq +27 -3
- data/bin/sidekiqload +68 -62
- data/bin/sidekiqmon +8 -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 +344 -243
- data/lib/sidekiq/cli.rb +209 -221
- data/lib/sidekiq/client.rb +62 -64
- data/lib/sidekiq/delay.rb +7 -6
- data/lib/sidekiq/exception_handler.rb +10 -12
- data/lib/sidekiq/extensions/action_mailer.rb +13 -22
- data/lib/sidekiq/extensions/active_record.rb +13 -10
- data/lib/sidekiq/extensions/class_methods.rb +14 -11
- data/lib/sidekiq/extensions/generic_proxy.rb +6 -4
- data/lib/sidekiq/fetch.rb +40 -32
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +35 -9
- data/lib/sidekiq/job_retry.rb +88 -68
- data/lib/sidekiq/job_util.rb +65 -0
- data/lib/sidekiq/launcher.rb +170 -73
- data/lib/sidekiq/logger.rb +170 -0
- data/lib/sidekiq/manager.rb +18 -22
- data/lib/sidekiq/middleware/chain.rb +20 -8
- data/lib/sidekiq/middleware/current_attributes.rb +57 -0
- data/lib/sidekiq/middleware/i18n.rb +5 -7
- data/lib/sidekiq/monitor.rb +133 -0
- data/lib/sidekiq/paginator.rb +20 -16
- data/lib/sidekiq/processor.rb +135 -83
- data/lib/sidekiq/rails.rb +42 -38
- data/lib/sidekiq/redis_connection.rb +49 -30
- data/lib/sidekiq/scheduled.rb +94 -31
- data/lib/sidekiq/sd_notify.rb +149 -0
- data/lib/sidekiq/systemd.rb +24 -0
- data/lib/sidekiq/testing/inline.rb +2 -1
- data/lib/sidekiq/testing.rb +40 -31
- data/lib/sidekiq/util.rb +57 -15
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +15 -11
- data/lib/sidekiq/web/application.rb +109 -74
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +117 -93
- data/lib/sidekiq/web/router.rb +23 -19
- data/lib/sidekiq/web.rb +61 -105
- data/lib/sidekiq/worker.rb +257 -99
- data/lib/sidekiq.rb +81 -46
- data/sidekiq.gemspec +23 -23
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +83 -64
- data/web/assets/javascripts/dashboard.js +66 -75
- data/web/assets/stylesheets/application-dark.css +143 -0
- data/web/assets/stylesheets/application-rtl.css +0 -4
- data/web/assets/stylesheets/application.css +77 -231
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +9 -2
- data/web/locales/de.yml +14 -2
- data/web/locales/en.yml +7 -1
- data/web/locales/es.yml +21 -5
- data/web/locales/fr.yml +10 -3
- data/web/locales/ja.yml +7 -1
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/ru.yml +4 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_footer.erb +1 -1
- data/web/views/_job_info.erb +3 -2
- data/web/views/_nav.erb +3 -17
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +54 -20
- data/web/views/dashboard.erb +22 -14
- data/web/views/dead.erb +3 -3
- data/web/views/layout.erb +4 -2
- data/web/views/morgue.erb +9 -6
- data/web/views/queue.erb +20 -10
- data/web/views/queues.erb +11 -3
- data/web/views/retries.erb +14 -7
- data/web/views/retry.erb +3 -3
- data/web/views/scheduled.erb +5 -2
- metadata +39 -136
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -11
- data/.gitignore +0 -13
- data/.travis.yml +0 -14
- 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 -95
- data/Ent-Changes.md +0 -216
- data/Gemfile +0 -8
- 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 -729
- data/Rakefile +0 -8
- data/bin/sidekiqctl +0 -99
- 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/logging.rb +0 -122
- data/lib/sidekiq/middleware/server/active_record.rb +0 -23
@@ -0,0 +1,57 @@
|
|
1
|
+
require "active_support/current_attributes"
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
##
|
5
|
+
# Automatically save and load any current attributes in the execution context
|
6
|
+
# so context attributes "flow" from Rails actions into any associated jobs.
|
7
|
+
# This can be useful for multi-tenancy, i18n locale, timezone, any implicit
|
8
|
+
# per-request attribute. See +ActiveSupport::CurrentAttributes+.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
#
|
12
|
+
# # in your initializer
|
13
|
+
# require "sidekiq/middleware/current_attributes"
|
14
|
+
# Sidekiq::CurrentAttributes.persist(Myapp::Current)
|
15
|
+
#
|
16
|
+
module CurrentAttributes
|
17
|
+
class Save
|
18
|
+
def initialize(cattr)
|
19
|
+
@klass = cattr
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(_, job, _, _)
|
23
|
+
attrs = @klass.attributes
|
24
|
+
if job.has_key?("cattr")
|
25
|
+
job["cattr"].merge!(attrs)
|
26
|
+
else
|
27
|
+
job["cattr"] = attrs
|
28
|
+
end
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Load
|
34
|
+
def initialize(cattr)
|
35
|
+
@klass = cattr
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(_, job, _, &block)
|
39
|
+
if job.has_key?("cattr")
|
40
|
+
@klass.set(job["cattr"], &block)
|
41
|
+
else
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.persist(klass)
|
48
|
+
Sidekiq.configure_client do |config|
|
49
|
+
config.client_middleware.add Save, klass
|
50
|
+
end
|
51
|
+
Sidekiq.configure_server do |config|
|
52
|
+
config.client_middleware.add Save, klass
|
53
|
+
config.server_middleware.add Load, klass
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -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,16 @@ 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
|
-
def call(
|
13
|
-
msg[
|
13
|
+
def call(_worker, msg, _queue, _redis)
|
14
|
+
msg["locale"] ||= I18n.locale
|
14
15
|
yield
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
19
|
# Pull the msg locale out and set the current thread to use it.
|
19
20
|
class Server
|
20
|
-
def call(
|
21
|
-
I18n.
|
22
|
-
yield
|
23
|
-
ensure
|
24
|
-
I18n.locale = I18n.default_locale
|
21
|
+
def call(_worker, msg, _queue, &block)
|
22
|
+
I18n.with_locale(msg.fetch("locale", I18n.default_locale), &block)
|
25
23
|
end
|
26
24
|
end
|
27
25
|
end
|
@@ -0,0 +1,133 @@
|
|
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
|
+
rescue => e
|
20
|
+
puts "Couldn't get status: #{e}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def all
|
24
|
+
version
|
25
|
+
puts
|
26
|
+
overview
|
27
|
+
puts
|
28
|
+
processes
|
29
|
+
puts
|
30
|
+
queues
|
31
|
+
end
|
32
|
+
|
33
|
+
def version
|
34
|
+
puts "Sidekiq #{Sidekiq::VERSION}"
|
35
|
+
puts Time.now.utc
|
36
|
+
end
|
37
|
+
|
38
|
+
def overview
|
39
|
+
puts "---- Overview ----"
|
40
|
+
puts " Processed: #{delimit stats.processed}"
|
41
|
+
puts " Failed: #{delimit stats.failed}"
|
42
|
+
puts " Busy: #{delimit stats.workers_size}"
|
43
|
+
puts " Enqueued: #{delimit stats.enqueued}"
|
44
|
+
puts " Retries: #{delimit stats.retry_size}"
|
45
|
+
puts " Scheduled: #{delimit stats.scheduled_size}"
|
46
|
+
puts " Dead: #{delimit stats.dead_size}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def processes
|
50
|
+
puts "---- Processes (#{process_set.size}) ----"
|
51
|
+
process_set.each_with_index do |process, index|
|
52
|
+
puts "#{process["identity"]} #{tags_for(process)}"
|
53
|
+
puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
|
54
|
+
puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
|
55
|
+
puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}"
|
56
|
+
puts "" unless (index + 1) == process_set.size
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def queues
|
61
|
+
puts "---- Queues (#{queue_data.size}) ----"
|
62
|
+
columns = {
|
63
|
+
name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
|
64
|
+
size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
|
65
|
+
latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
|
66
|
+
}
|
67
|
+
columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
|
68
|
+
puts
|
69
|
+
queue_data.each do |q|
|
70
|
+
columns.each do |col, (dir, width)|
|
71
|
+
print q.send(col).public_send(dir, width)
|
72
|
+
end
|
73
|
+
puts
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def delimit(number)
|
80
|
+
number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
|
81
|
+
end
|
82
|
+
|
83
|
+
def split_multiline(values, opts = {})
|
84
|
+
return "none" unless values
|
85
|
+
pad = opts[:pad] || 0
|
86
|
+
max_length = opts[:max_length] || (80 - pad)
|
87
|
+
out = []
|
88
|
+
line = ""
|
89
|
+
values.each do |value|
|
90
|
+
if (line.length + value.length) > max_length
|
91
|
+
out << line
|
92
|
+
line = " " * pad
|
93
|
+
end
|
94
|
+
line << value + ", "
|
95
|
+
end
|
96
|
+
out << line[0..-3]
|
97
|
+
out.join("\n")
|
98
|
+
end
|
99
|
+
|
100
|
+
def tags_for(process)
|
101
|
+
tags = [
|
102
|
+
process["tag"],
|
103
|
+
process["labels"],
|
104
|
+
(process["quiet"] == "true" ? "quiet" : nil)
|
105
|
+
].flatten.compact
|
106
|
+
tags.any? ? "[#{tags.join("] [")}]" : nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def time_ago(timestamp)
|
110
|
+
seconds = Time.now - Time.at(timestamp)
|
111
|
+
return "just now" if seconds < 60
|
112
|
+
return "a minute ago" if seconds < 120
|
113
|
+
return "#{seconds.floor / 60} minutes ago" if seconds < 3600
|
114
|
+
return "an hour ago" if seconds < 7200
|
115
|
+
"#{seconds.floor / 60 / 60} hours ago"
|
116
|
+
end
|
117
|
+
|
118
|
+
QUEUE_STRUCT = Struct.new(:name, :size, :latency)
|
119
|
+
def queue_data
|
120
|
+
@queue_data ||= Sidekiq::Queue.all.map { |q|
|
121
|
+
QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def process_set
|
126
|
+
@process_set ||= Sidekiq::ProcessSet.new
|
127
|
+
end
|
128
|
+
|
129
|
+
def stats
|
130
|
+
@stats ||= Sidekiq::Stats.new
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/sidekiq/paginator.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Sidekiq
|
3
4
|
module Paginator
|
4
|
-
|
5
|
-
def page(key, pageidx=1, page_size=25, opts=nil)
|
5
|
+
def page(key, pageidx = 1, page_size = 25, opts = nil)
|
6
6
|
current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
|
7
7
|
pageidx = current_page - 1
|
8
8
|
total_size = 0
|
@@ -12,32 +12,36 @@ 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.zrevrange(key, starting, ending, with_scores: true)
|
23
23
|
else
|
24
|
-
|
24
|
+
transaction.zrange(key, starting, ending, with_scores: 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}"
|
38
43
|
end
|
39
44
|
end
|
40
45
|
end
|
41
|
-
|
42
46
|
end
|
43
47
|
end
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require 'concurrent/map'
|
8
|
-
require 'concurrent/atomic/atomic_fixnum'
|
2
|
+
|
3
|
+
require "sidekiq/util"
|
4
|
+
require "sidekiq/fetch"
|
5
|
+
require "sidekiq/job_logger"
|
6
|
+
require "sidekiq/job_retry"
|
9
7
|
|
10
8
|
module Sidekiq
|
11
9
|
##
|
@@ -25,33 +23,32 @@ module Sidekiq
|
|
25
23
|
# to replace itself and exits.
|
26
24
|
#
|
27
25
|
class Processor
|
28
|
-
|
29
26
|
include Util
|
30
27
|
|
31
28
|
attr_reader :thread
|
32
29
|
attr_reader :job
|
33
30
|
|
34
|
-
def initialize(mgr)
|
31
|
+
def initialize(mgr, options)
|
35
32
|
@mgr = mgr
|
36
33
|
@down = false
|
37
34
|
@done = false
|
38
35
|
@job = nil
|
39
36
|
@thread = nil
|
40
|
-
@strategy =
|
41
|
-
@reloader =
|
42
|
-
@
|
37
|
+
@strategy = options[:fetch]
|
38
|
+
@reloader = options[:reloader] || proc { |&block| block.call }
|
39
|
+
@job_logger = (options[:job_logger] || Sidekiq::JobLogger).new
|
43
40
|
@retrier = Sidekiq::JobRetry.new
|
44
41
|
end
|
45
42
|
|
46
|
-
def terminate(wait=false)
|
43
|
+
def terminate(wait = false)
|
47
44
|
@done = true
|
48
|
-
return
|
45
|
+
return unless @thread
|
49
46
|
@thread.value if wait
|
50
47
|
end
|
51
48
|
|
52
|
-
def kill(wait=false)
|
49
|
+
def kill(wait = false)
|
53
50
|
@done = true
|
54
|
-
return
|
51
|
+
return unless @thread
|
55
52
|
# unlike the other actors, terminate does not wait
|
56
53
|
# for the thread to finish because we don't know how
|
57
54
|
# long the job will take to finish. Instead we
|
@@ -68,16 +65,12 @@ module Sidekiq
|
|
68
65
|
private unless $TESTING
|
69
66
|
|
70
67
|
def run
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
@mgr.processor_stopped(self)
|
78
|
-
rescue Exception => ex
|
79
|
-
@mgr.processor_died(self, ex)
|
80
|
-
end
|
68
|
+
process_one until @done
|
69
|
+
@mgr.processor_stopped(self)
|
70
|
+
rescue Sidekiq::Shutdown
|
71
|
+
@mgr.processor_stopped(self)
|
72
|
+
rescue Exception => ex
|
73
|
+
@mgr.processor_died(self, ex)
|
81
74
|
end
|
82
75
|
|
83
76
|
def process_one
|
@@ -87,14 +80,15 @@ module Sidekiq
|
|
87
80
|
end
|
88
81
|
|
89
82
|
def get_one
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
rescue Sidekiq::Shutdown
|
95
|
-
rescue => ex
|
96
|
-
handle_fetch_exception(ex)
|
83
|
+
work = @strategy.retrieve_work
|
84
|
+
if @down
|
85
|
+
logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
|
86
|
+
@down = nil
|
97
87
|
end
|
88
|
+
work
|
89
|
+
rescue Sidekiq::Shutdown
|
90
|
+
rescue => ex
|
91
|
+
handle_fetch_exception(ex)
|
98
92
|
end
|
99
93
|
|
100
94
|
def fetch
|
@@ -108,8 +102,8 @@ module Sidekiq
|
|
108
102
|
end
|
109
103
|
|
110
104
|
def handle_fetch_exception(ex)
|
111
|
-
|
112
|
-
@down =
|
105
|
+
unless @down
|
106
|
+
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
113
107
|
logger.error("Error fetching job: #{ex}")
|
114
108
|
handle_exception(ex)
|
115
109
|
end
|
@@ -117,25 +111,28 @@ module Sidekiq
|
|
117
111
|
nil
|
118
112
|
end
|
119
113
|
|
120
|
-
def dispatch(job_hash, queue)
|
114
|
+
def dispatch(job_hash, queue, jobstr)
|
121
115
|
# since middleware can mutate the job hash
|
122
|
-
# we clone
|
116
|
+
# we need to clone it to report the original
|
123
117
|
# job structure to the Web UI
|
124
|
-
|
118
|
+
# or to push back to redis when retrying.
|
119
|
+
# To avoid costly and, most of the time, useless cloning here,
|
120
|
+
# we pass original String of JSON to respected methods
|
121
|
+
# to re-parse it there if we need access to the original, untouched job
|
125
122
|
|
126
|
-
|
127
|
-
@retrier.global(
|
128
|
-
@
|
129
|
-
stats(
|
123
|
+
@job_logger.prepare(job_hash) do
|
124
|
+
@retrier.global(jobstr, queue) do
|
125
|
+
@job_logger.call(job_hash, queue) do
|
126
|
+
stats(jobstr, queue) do
|
130
127
|
# Rails 5 requires a Reloader to wrap code execution. In order to
|
131
128
|
# constantize the worker and instantiate an instance, we have to call
|
132
129
|
# the Reloader. It handles code loading, db connection management, etc.
|
133
130
|
# Effectively this block denotes a "unit of work" to Rails.
|
134
131
|
@reloader.call do
|
135
|
-
klass
|
132
|
+
klass = constantize(job_hash["class"])
|
136
133
|
worker = klass.new
|
137
|
-
worker.jid = job_hash[
|
138
|
-
@retrier.local(worker,
|
134
|
+
worker.jid = job_hash["jid"]
|
135
|
+
@retrier.local(worker, jobstr, queue) do
|
139
136
|
yield worker
|
140
137
|
end
|
141
138
|
end
|
@@ -149,37 +146,49 @@ module Sidekiq
|
|
149
146
|
jobstr = work.job
|
150
147
|
queue = work.queue_name
|
151
148
|
|
152
|
-
|
149
|
+
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
150
|
+
job_hash = nil
|
153
151
|
begin
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
DeadSet.new.kill(jobstr, notify_failure: false)
|
162
|
-
ack = true
|
163
|
-
raise
|
164
|
-
end
|
152
|
+
job_hash = Sidekiq.load_json(jobstr)
|
153
|
+
rescue => ex
|
154
|
+
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)
|
157
|
+
return work.acknowledge
|
158
|
+
end
|
165
159
|
|
166
|
-
|
167
|
-
|
160
|
+
ack = false
|
161
|
+
begin
|
162
|
+
dispatch(job_hash, queue, jobstr) do |worker|
|
168
163
|
Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
|
169
|
-
execute_job(worker,
|
164
|
+
execute_job(worker, job_hash["args"])
|
170
165
|
end
|
171
166
|
end
|
167
|
+
ack = true
|
172
168
|
rescue Sidekiq::Shutdown
|
173
169
|
# Had to force kill this job because it didn't finish
|
174
170
|
# within the timeout. Don't acknowledge the work since
|
175
171
|
# we didn't properly finish it.
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
172
|
+
rescue Sidekiq::JobRetry::Handled => h
|
173
|
+
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
174
|
+
# signals that we created a retry successfully. We can acknowlege the job.
|
175
|
+
ack = true
|
176
|
+
e = h.cause || h
|
177
|
+
handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr})
|
180
178
|
raise e
|
179
|
+
rescue Exception => ex
|
180
|
+
# Unexpected error! This is very bad and indicates an exception that got past
|
181
|
+
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
182
|
+
# so it can be rescued when using Sidekiq Pro.
|
183
|
+
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
184
|
+
raise ex
|
181
185
|
ensure
|
182
|
-
|
186
|
+
if ack
|
187
|
+
# We don't want a shutdown signal to interrupt job acknowledgment.
|
188
|
+
Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
|
189
|
+
work.acknowledge
|
190
|
+
end
|
191
|
+
end
|
183
192
|
end
|
184
193
|
end
|
185
194
|
|
@@ -187,42 +196,85 @@ module Sidekiq
|
|
187
196
|
worker.perform(*cloned_args)
|
188
197
|
end
|
189
198
|
|
190
|
-
|
191
|
-
|
192
|
-
|
199
|
+
# Ruby doesn't provide atomic counters out of the box so we'll
|
200
|
+
# implement something simple ourselves.
|
201
|
+
# https://bugs.ruby-lang.org/issues/14706
|
202
|
+
class Counter
|
203
|
+
def initialize
|
204
|
+
@value = 0
|
205
|
+
@lock = Mutex.new
|
206
|
+
end
|
193
207
|
|
194
|
-
|
195
|
-
|
196
|
-
|
208
|
+
def incr(amount = 1)
|
209
|
+
@lock.synchronize { @value += amount }
|
210
|
+
end
|
211
|
+
|
212
|
+
def reset
|
213
|
+
@lock.synchronize {
|
214
|
+
val = @value
|
215
|
+
@value = 0
|
216
|
+
val
|
217
|
+
}
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
|
222
|
+
class SharedWorkerState
|
223
|
+
def initialize
|
224
|
+
@worker_state = {}
|
225
|
+
@lock = Mutex.new
|
226
|
+
end
|
227
|
+
|
228
|
+
def set(tid, hash)
|
229
|
+
@lock.synchronize { @worker_state[tid] = hash }
|
230
|
+
end
|
231
|
+
|
232
|
+
def delete(tid)
|
233
|
+
@lock.synchronize { @worker_state.delete(tid) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def dup
|
237
|
+
@lock.synchronize { @worker_state.dup }
|
238
|
+
end
|
239
|
+
|
240
|
+
def size
|
241
|
+
@lock.synchronize { @worker_state.size }
|
242
|
+
end
|
243
|
+
|
244
|
+
def clear
|
245
|
+
@lock.synchronize { @worker_state.clear }
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
PROCESSED = Counter.new
|
250
|
+
FAILURE = Counter.new
|
251
|
+
WORKER_STATE = SharedWorkerState.new
|
252
|
+
|
253
|
+
def stats(jobstr, queue)
|
254
|
+
WORKER_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
|
197
255
|
|
198
256
|
begin
|
199
257
|
yield
|
200
258
|
rescue Exception
|
201
|
-
FAILURE.
|
259
|
+
FAILURE.incr
|
202
260
|
raise
|
203
261
|
ensure
|
204
262
|
WORKER_STATE.delete(tid)
|
205
|
-
PROCESSED.
|
263
|
+
PROCESSED.incr
|
206
264
|
end
|
207
265
|
end
|
208
266
|
|
209
|
-
# Deep clone the arguments passed to the worker so that if
|
210
|
-
# the job fails, what is pushed back onto Redis hasn't
|
211
|
-
# been mutated by the worker.
|
212
|
-
def cloned(thing)
|
213
|
-
Marshal.load(Marshal.dump(thing))
|
214
|
-
end
|
215
|
-
|
216
267
|
def constantize(str)
|
217
|
-
|
268
|
+
return Object.const_get(str) unless str.include?("::")
|
269
|
+
|
270
|
+
names = str.split("::")
|
218
271
|
names.shift if names.empty? || names.first.empty?
|
219
272
|
|
220
273
|
names.inject(Object) do |constant, name|
|
221
274
|
# the false flag limits search for name to under the constant namespace
|
222
275
|
# which mimics Rails' behaviour
|
223
|
-
constant.
|
276
|
+
constant.const_get(name, false)
|
224
277
|
end
|
225
278
|
end
|
226
|
-
|
227
279
|
end
|
228
280
|
end
|