sidekiq 4.1.4 → 6.5.6
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 +666 -0
- data/LICENSE +3 -3
- data/README.md +27 -35
- data/bin/sidekiq +27 -3
- data/bin/sidekiqload +78 -84
- 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/job_spec.rb.erb +6 -0
- data/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
- data/lib/sidekiq/api.rb +583 -288
- data/lib/sidekiq/cli.rb +255 -218
- data/lib/sidekiq/client.rb +86 -83
- data/lib/sidekiq/component.rb +65 -0
- data/lib/sidekiq/delay.rb +43 -0
- 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 +13 -5
- data/lib/sidekiq/fetch.rb +50 -40
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +51 -0
- data/lib/sidekiq/job_retry.rb +282 -0
- data/lib/sidekiq/job_util.rb +71 -0
- data/lib/sidekiq/launcher.rb +192 -85
- data/lib/sidekiq/logger.rb +156 -0
- data/lib/sidekiq/manager.rb +44 -45
- data/lib/sidekiq/metrics/deploy.rb +47 -0
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +94 -0
- data/lib/sidekiq/metrics/tracking.rb +134 -0
- data/lib/sidekiq/middleware/chain.rb +102 -46
- data/lib/sidekiq/middleware/current_attributes.rb +63 -0
- data/lib/sidekiq/middleware/i18n.rb +7 -7
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +133 -0
- data/lib/sidekiq/paginator.rb +20 -16
- data/lib/sidekiq/processor.rb +178 -78
- data/lib/sidekiq/rails.rb +56 -27
- data/lib/sidekiq/redis_client_adapter.rb +154 -0
- data/lib/sidekiq/redis_connection.rb +123 -47
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +97 -40
- 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 +83 -56
- data/lib/sidekiq/transaction_aware_client.rb +45 -0
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +93 -0
- data/lib/sidekiq/web/application.rb +379 -0
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +365 -0
- data/lib/sidekiq/web/router.rb +104 -0
- data/lib/sidekiq/web.rb +108 -213
- data/lib/sidekiq/worker.rb +288 -42
- data/lib/sidekiq.rb +188 -80
- data/sidekiq.gemspec +24 -22
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
- data/web/assets/javascripts/application.js +130 -75
- 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.js +70 -91
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application-dark.css +143 -0
- data/web/assets/stylesheets/application-rtl.css +242 -0
- data/web/assets/stylesheets/application.css +390 -145
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +4 -8
- data/web/locales/ar.yml +87 -0
- data/web/locales/de.yml +15 -3
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +15 -1
- data/web/locales/es.yml +22 -5
- data/web/locales/fa.yml +80 -0
- data/web/locales/fr.yml +10 -3
- data/web/locales/he.yml +79 -0
- data/web/locales/ja.yml +12 -4
- data/web/locales/lt.yml +83 -0
- data/web/locales/pl.yml +4 -4
- data/web/locales/pt-br.yml +27 -9
- data/web/locales/ru.yml +4 -0
- data/web/locales/ur.yml +80 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_footer.erb +6 -3
- data/web/views/_job_info.erb +4 -3
- data/web/views/_nav.erb +5 -19
- data/web/views/_paging.erb +1 -1
- data/web/views/_poll_link.erb +2 -5
- data/web/views/_summary.erb +7 -7
- data/web/views/busy.erb +64 -26
- data/web/views/dashboard.erb +26 -17
- data/web/views/dead.erb +4 -4
- data/web/views/layout.erb +15 -5
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/morgue.erb +21 -14
- data/web/views/queue.erb +28 -14
- data/web/views/queues.erb +15 -5
- data/web/views/retries.erb +24 -15
- data/web/views/retry.erb +5 -5
- data/web/views/scheduled.erb +9 -6
- data/web/views/scheduled_job_info.erb +1 -1
- metadata +73 -256
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -4
- data/.gitignore +0 -12
- data/.travis.yml +0 -18
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/COMM-LICENSE +0 -95
- data/Ent-Changes.md +0 -123
- data/Gemfile +0 -29
- data/Pro-2.0-Upgrade.md +0 -138
- data/Pro-3.0-Upgrade.md +0 -44
- data/Pro-Changes.md +0 -559
- data/Rakefile +0 -9
- data/bin/sidekiqctl +0 -99
- data/code_of_conduct.md +0 -50
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +0 -8
- data/lib/generators/sidekiq/worker_generator.rb +0 -49
- data/lib/sidekiq/core_ext.rb +0 -106
- data/lib/sidekiq/exception_handler.rb +0 -31
- data/lib/sidekiq/logging.rb +0 -106
- data/lib/sidekiq/middleware/server/active_record.rb +0 -13
- data/lib/sidekiq/middleware/server/logging.rb +0 -40
- data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
- data/lib/sidekiq/util.rb +0 -62
- data/lib/sidekiq/web_helpers.rb +0 -255
- data/test/config.yml +0 -9
- data/test/env_based_config.yml +0 -11
- data/test/fake_env.rb +0 -1
- data/test/fixtures/en.yml +0 -2
- data/test/helper.rb +0 -75
- data/test/test_actors.rb +0 -138
- data/test/test_api.rb +0 -528
- data/test/test_cli.rb +0 -406
- data/test/test_client.rb +0 -266
- data/test/test_exception_handler.rb +0 -56
- data/test/test_extensions.rb +0 -127
- data/test/test_fetch.rb +0 -50
- data/test/test_launcher.rb +0 -85
- data/test/test_logging.rb +0 -35
- data/test/test_manager.rb +0 -50
- data/test/test_middleware.rb +0 -158
- data/test/test_processor.rb +0 -201
- data/test/test_rails.rb +0 -22
- data/test/test_redis_connection.rb +0 -132
- data/test/test_retry.rb +0 -326
- data/test/test_retry_exhausted.rb +0 -149
- data/test/test_scheduled.rb +0 -115
- data/test/test_scheduling.rb +0 -50
- data/test/test_sidekiq.rb +0 -107
- data/test/test_testing.rb +0 -143
- data/test/test_testing_fake.rb +0 -357
- data/test/test_testing_inline.rb +0 -94
- data/test/test_util.rb +0 -13
- data/test/test_web.rb +0 -614
- data/test/test_web_helpers.rb +0 -54
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/images/status/active.png +0 -0
- data/web/assets/images/status/idle.png +0 -0
- data/web/assets/javascripts/locales/README.md +0 -27
- data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
- data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
- data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
- data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
- data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.nb.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
- data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
- data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
- data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
- data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
- data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
- data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
- data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
- data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
- data/web/views/_poll_js.erb +0 -5
@@ -0,0 +1,63 @@
|
|
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
|
+
include Sidekiq::ClientMiddleware
|
19
|
+
|
20
|
+
def initialize(cattr)
|
21
|
+
@klass = cattr
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(_, job, _, _)
|
25
|
+
attrs = @klass.attributes
|
26
|
+
if attrs.any?
|
27
|
+
if job.has_key?("cattr")
|
28
|
+
job["cattr"].merge!(attrs)
|
29
|
+
else
|
30
|
+
job["cattr"] = attrs
|
31
|
+
end
|
32
|
+
end
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Load
|
38
|
+
include Sidekiq::ServerMiddleware
|
39
|
+
|
40
|
+
def initialize(cattr)
|
41
|
+
@klass = cattr
|
42
|
+
end
|
43
|
+
|
44
|
+
def call(_, job, _, &block)
|
45
|
+
if job.has_key?("cattr")
|
46
|
+
@klass.set(job["cattr"], &block)
|
47
|
+
else
|
48
|
+
yield
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.persist(klass)
|
54
|
+
Sidekiq.configure_client do |config|
|
55
|
+
config.client_middleware.add Save, klass
|
56
|
+
end
|
57
|
+
Sidekiq.configure_server do |config|
|
58
|
+
config.client_middleware.add Save, klass
|
59
|
+
config.server_middleware.add Load, klass
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
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,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,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
|
+
abort "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, 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}"
|
38
43
|
end
|
39
44
|
end
|
40
45
|
end
|
41
|
-
|
42
46
|
end
|
43
47
|
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 'concurrent/atomic/atomic_fixnum'
|
2
|
+
|
3
|
+
require "sidekiq/fetch"
|
4
|
+
require "sidekiq/job_logger"
|
5
|
+
require "sidekiq/job_retry"
|
7
6
|
|
8
7
|
module Sidekiq
|
9
8
|
##
|
@@ -11,42 +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
|
31
29
|
|
32
|
-
def initialize(
|
33
|
-
@
|
30
|
+
def initialize(options, &block)
|
31
|
+
@callback = block
|
34
32
|
@down = false
|
35
33
|
@done = false
|
36
34
|
@job = nil
|
37
35
|
@thread = nil
|
38
|
-
@
|
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)
|
39
41
|
end
|
40
42
|
|
41
|
-
def terminate(wait=false)
|
43
|
+
def terminate(wait = false)
|
42
44
|
@done = true
|
43
|
-
return
|
45
|
+
return unless @thread
|
44
46
|
@thread.value if wait
|
45
47
|
end
|
46
48
|
|
47
|
-
def kill(wait=false)
|
49
|
+
def kill(wait = false)
|
48
50
|
@done = true
|
49
|
-
return
|
51
|
+
return unless @thread
|
50
52
|
# unlike the other actors, terminate does not wait
|
51
53
|
# for the thread to finish because we don't know how
|
52
54
|
# long the job will take to finish. Instead we
|
@@ -63,33 +65,30 @@ module Sidekiq
|
|
63
65
|
private unless $TESTING
|
64
66
|
|
65
67
|
def run
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@mgr.processor_stopped(self)
|
73
|
-
rescue Exception => ex
|
74
|
-
@mgr.processor_died(self, ex)
|
75
|
-
end
|
68
|
+
process_one until @done
|
69
|
+
@callback.call(self)
|
70
|
+
rescue Sidekiq::Shutdown
|
71
|
+
@callback.call(self)
|
72
|
+
rescue Exception => ex
|
73
|
+
@callback.call(self, ex)
|
76
74
|
end
|
77
75
|
|
78
|
-
def process_one
|
76
|
+
def process_one(&block)
|
79
77
|
@job = fetch
|
80
78
|
process(@job) if @job
|
81
79
|
@job = nil
|
82
80
|
end
|
83
81
|
|
84
82
|
def get_one
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
rescue Sidekiq::Shutdown
|
90
|
-
rescue => ex
|
91
|
-
handle_fetch_exception(ex)
|
83
|
+
uow = @strategy.retrieve_work
|
84
|
+
if @down
|
85
|
+
logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
|
86
|
+
@down = nil
|
92
87
|
end
|
88
|
+
uow
|
89
|
+
rescue Sidekiq::Shutdown
|
90
|
+
rescue => ex
|
91
|
+
handle_fetch_exception(ex)
|
93
92
|
end
|
94
93
|
|
95
94
|
def fetch
|
@@ -103,35 +102,72 @@ module Sidekiq
|
|
103
102
|
end
|
104
103
|
|
105
104
|
def handle_fetch_exception(ex)
|
106
|
-
|
107
|
-
@down =
|
105
|
+
unless @down
|
106
|
+
@down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
108
107
|
logger.error("Error fetching job: #{ex}")
|
109
|
-
ex
|
110
|
-
logger.error(bt)
|
111
|
-
end
|
108
|
+
handle_exception(ex)
|
112
109
|
end
|
113
110
|
sleep(1)
|
114
111
|
nil
|
115
112
|
end
|
116
113
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
114
|
+
def dispatch(job_hash, queue, jobstr)
|
115
|
+
# since middleware can mutate the job hash
|
116
|
+
# we need to clone it to report the original
|
117
|
+
# job structure to the Web UI
|
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
|
122
|
+
|
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
|
127
|
+
# Rails 5 requires a Reloader to wrap code execution. In order to
|
128
|
+
# constantize the worker and instantiate an instance, we have to call
|
129
|
+
# the Reloader. It handles code loading, db connection management, etc.
|
130
|
+
# Effectively this block denotes a "unit of work" to Rails.
|
131
|
+
@reloader.call do
|
132
|
+
klass = constantize(job_hash["class"])
|
133
|
+
inst = klass.new
|
134
|
+
inst.jid = job_hash["jid"]
|
135
|
+
@retrier.local(inst, jobstr, queue) do
|
136
|
+
yield inst
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def process(uow)
|
146
|
+
jobstr = uow.job
|
147
|
+
queue = uow.queue_name
|
148
|
+
|
149
|
+
# Treat malformed JSON as a special case: job goes straight to the morgue.
|
150
|
+
job_hash = nil
|
151
|
+
begin
|
152
|
+
job_hash = Sidekiq.load_json(jobstr)
|
153
|
+
rescue => ex
|
154
|
+
handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
|
155
|
+
now = Time.now.to_f
|
156
|
+
config.redis do |conn|
|
157
|
+
conn.multi do |xa|
|
158
|
+
xa.zadd("dead", now.to_s, jobstr)
|
159
|
+
xa.zremrangebyscore("dead", "-inf", now - config[:dead_timeout_in_seconds])
|
160
|
+
xa.zremrangebyrank("dead", 0, - config[:dead_max_jobs])
|
161
|
+
end
|
162
|
+
end
|
163
|
+
return uow.acknowledge
|
164
|
+
end
|
120
165
|
|
121
166
|
ack = false
|
122
167
|
begin
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
worker.jid = job['jid'.freeze]
|
127
|
-
|
128
|
-
stats(worker, job, queue) do
|
129
|
-
Sidekiq.server_middleware.invoke(worker, job, queue) do
|
130
|
-
# Only ack if we either attempted to start this job or
|
131
|
-
# successfully completed it. This prevents us from
|
132
|
-
# losing jobs if a middleware raises an exception before yielding
|
133
|
-
ack = true
|
134
|
-
execute_job(worker, cloned(job['args'.freeze]))
|
168
|
+
dispatch(job_hash, queue, jobstr) do |inst|
|
169
|
+
@config.server_middleware.invoke(inst, job_hash, queue) do
|
170
|
+
execute_job(inst, job_hash["args"])
|
135
171
|
end
|
136
172
|
end
|
137
173
|
ack = true
|
@@ -139,48 +175,112 @@ module Sidekiq
|
|
139
175
|
# Had to force kill this job because it didn't finish
|
140
176
|
# within the timeout. Don't acknowledge the work since
|
141
177
|
# we didn't properly finish it.
|
142
|
-
|
178
|
+
rescue Sidekiq::JobRetry::Handled => h
|
179
|
+
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
180
|
+
# signals that we created a retry successfully. We can acknowlege the job.
|
181
|
+
ack = true
|
182
|
+
e = h.cause || h
|
183
|
+
handle_exception(e, {context: "Job raised exception", job: job_hash})
|
184
|
+
raise e
|
143
185
|
rescue Exception => ex
|
144
|
-
|
145
|
-
|
186
|
+
# Unexpected error! This is very bad and indicates an exception that got past
|
187
|
+
# the retry subsystem (e.g. network partition). We won't acknowledge the job
|
188
|
+
# so it can be rescued when using Sidekiq Pro.
|
189
|
+
handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
|
190
|
+
raise ex
|
146
191
|
ensure
|
147
|
-
|
192
|
+
if ack
|
193
|
+
# We don't want a shutdown signal to interrupt job acknowledgment.
|
194
|
+
Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
|
195
|
+
uow.acknowledge
|
196
|
+
end
|
197
|
+
end
|
148
198
|
end
|
149
199
|
end
|
150
200
|
|
151
|
-
def execute_job(
|
152
|
-
|
201
|
+
def execute_job(inst, cloned_args)
|
202
|
+
inst.perform(*cloned_args)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Ruby doesn't provide atomic counters out of the box so we'll
|
206
|
+
# implement something simple ourselves.
|
207
|
+
# https://bugs.ruby-lang.org/issues/14706
|
208
|
+
class Counter
|
209
|
+
def initialize
|
210
|
+
@value = 0
|
211
|
+
@lock = Mutex.new
|
212
|
+
end
|
213
|
+
|
214
|
+
def incr(amount = 1)
|
215
|
+
@lock.synchronize { @value += amount }
|
216
|
+
end
|
217
|
+
|
218
|
+
def reset
|
219
|
+
@lock.synchronize {
|
220
|
+
val = @value
|
221
|
+
@value = 0
|
222
|
+
val
|
223
|
+
}
|
224
|
+
end
|
153
225
|
end
|
154
226
|
|
155
|
-
|
156
|
-
|
227
|
+
# jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
|
228
|
+
class SharedWorkState
|
229
|
+
def initialize
|
230
|
+
@work_state = {}
|
231
|
+
@lock = Mutex.new
|
232
|
+
end
|
233
|
+
|
234
|
+
def set(tid, hash)
|
235
|
+
@lock.synchronize { @work_state[tid] = hash }
|
236
|
+
end
|
237
|
+
|
238
|
+
def delete(tid)
|
239
|
+
@lock.synchronize { @work_state.delete(tid) }
|
240
|
+
end
|
241
|
+
|
242
|
+
def dup
|
243
|
+
@lock.synchronize { @work_state.dup }
|
244
|
+
end
|
245
|
+
|
246
|
+
def size
|
247
|
+
@lock.synchronize { @work_state.size }
|
248
|
+
end
|
249
|
+
|
250
|
+
def clear
|
251
|
+
@lock.synchronize { @work_state.clear }
|
252
|
+
end
|
157
253
|
end
|
158
254
|
|
159
|
-
|
160
|
-
|
161
|
-
|
255
|
+
PROCESSED = Counter.new
|
256
|
+
FAILURE = Counter.new
|
257
|
+
WORK_STATE = SharedWorkState.new
|
162
258
|
|
163
|
-
def stats(
|
164
|
-
tid
|
165
|
-
WORKER_STATE[tid] = {:queue => queue, :payload => job, :run_at => Time.now.to_i }
|
259
|
+
def stats(jobstr, queue)
|
260
|
+
WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
|
166
261
|
|
167
262
|
begin
|
168
263
|
yield
|
169
264
|
rescue Exception
|
170
|
-
FAILURE.
|
265
|
+
FAILURE.incr
|
171
266
|
raise
|
172
267
|
ensure
|
173
|
-
|
174
|
-
PROCESSED.
|
268
|
+
WORK_STATE.delete(tid)
|
269
|
+
PROCESSED.incr
|
175
270
|
end
|
176
271
|
end
|
177
272
|
|
178
|
-
|
179
|
-
|
180
|
-
# been mutated by the worker.
|
181
|
-
def cloned(ary)
|
182
|
-
Marshal.load(Marshal.dump(ary))
|
183
|
-
end
|
273
|
+
def constantize(str)
|
274
|
+
return Object.const_get(str) unless str.include?("::")
|
184
275
|
|
276
|
+
names = str.split("::")
|
277
|
+
names.shift if names.empty? || names.first.empty?
|
278
|
+
|
279
|
+
names.inject(Object) do |constant, name|
|
280
|
+
# the false flag limits search for name to under the constant namespace
|
281
|
+
# which mimics Rails' behaviour
|
282
|
+
constant.const_get(name, false)
|
283
|
+
end
|
284
|
+
end
|
185
285
|
end
|
186
286
|
end
|