sidekiq 4.2.2 → 6.3.1
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 +5 -5
- data/Changes.md +516 -0
- data/LICENSE +2 -2
- data/README.md +23 -36
- data/bin/sidekiq +26 -2
- data/bin/sidekiqload +28 -38
- data/bin/sidekiqmon +8 -0
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +1 -1
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +2 -2
- data/lib/generators/sidekiq/worker_generator.rb +21 -13
- data/lib/sidekiq/api.rb +401 -243
- data/lib/sidekiq/cli.rb +228 -212
- data/lib/sidekiq/client.rb +76 -53
- data/lib/sidekiq/delay.rb +41 -0
- data/lib/sidekiq/exception_handler.rb +12 -16
- 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 +12 -4
- data/lib/sidekiq/fetch.rb +39 -31
- data/lib/sidekiq/job.rb +13 -0
- data/lib/sidekiq/job_logger.rb +63 -0
- data/lib/sidekiq/job_retry.rb +259 -0
- data/lib/sidekiq/launcher.rb +170 -71
- data/lib/sidekiq/logger.rb +166 -0
- data/lib/sidekiq/manager.rb +17 -20
- data/lib/sidekiq/middleware/chain.rb +20 -8
- data/lib/sidekiq/middleware/current_attributes.rb +52 -0
- data/lib/sidekiq/middleware/i18n.rb +5 -7
- data/lib/sidekiq/monitor.rb +133 -0
- data/lib/sidekiq/paginator.rb +18 -14
- data/lib/sidekiq/processor.rb +169 -78
- data/lib/sidekiq/rails.rb +41 -36
- data/lib/sidekiq/redis_connection.rb +65 -20
- data/lib/sidekiq/scheduled.rb +85 -34
- 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 +52 -26
- data/lib/sidekiq/util.rb +48 -15
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +15 -17
- data/lib/sidekiq/web/application.rb +114 -92
- data/lib/sidekiq/web/csrf_protection.rb +180 -0
- data/lib/sidekiq/web/helpers.rb +151 -83
- data/lib/sidekiq/web/router.rb +27 -19
- data/lib/sidekiq/web.rb +85 -76
- data/lib/sidekiq/worker.rb +233 -43
- data/lib/sidekiq.rb +88 -64
- data/sidekiq.gemspec +24 -22
- data/web/assets/images/apple-touch-icon.png +0 -0
- data/web/assets/javascripts/application.js +86 -59
- data/web/assets/javascripts/dashboard.js +81 -85
- data/web/assets/stylesheets/application-dark.css +147 -0
- data/web/assets/stylesheets/application-rtl.css +242 -0
- data/web/assets/stylesheets/application.css +319 -141
- data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
- data/web/assets/stylesheets/bootstrap.css +2 -2
- data/web/locales/ar.yml +87 -0
- data/web/locales/de.yml +14 -2
- data/web/locales/en.yml +8 -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/ru.yml +4 -0
- data/web/locales/ur.yml +80 -0
- data/web/locales/vi.yml +83 -0
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +4 -3
- data/web/views/_nav.erb +4 -18
- 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 +60 -22
- data/web/views/dashboard.erb +23 -15
- data/web/views/dead.erb +3 -3
- data/web/views/layout.erb +14 -3
- data/web/views/morgue.erb +19 -12
- data/web/views/queue.erb +24 -14
- data/web/views/queues.erb +14 -4
- data/web/views/retries.erb +22 -13
- data/web/views/retry.erb +4 -4
- data/web/views/scheduled.erb +7 -4
- metadata +44 -194
- data/.github/contributing.md +0 -32
- data/.github/issue_template.md +0 -4
- data/.gitignore +0 -12
- data/.travis.yml +0 -12
- data/3.0-Upgrade.md +0 -70
- data/4.0-Upgrade.md +0 -53
- data/COMM-LICENSE +0 -95
- data/Ent-Changes.md +0 -146
- 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 -570
- data/Rakefile +0 -9
- data/bin/sidekiqctl +0 -99
- data/code_of_conduct.md +0 -50
- data/lib/sidekiq/core_ext.rb +0 -106
- 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/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 -418
- 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 -95
- 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 -666
- data/test/test_web_helpers.rb +0 -54
data/lib/sidekiq/scheduled.rb
CHANGED
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
2
|
+
|
|
3
|
+
require "sidekiq"
|
|
4
|
+
require "sidekiq/util"
|
|
5
|
+
require "sidekiq/api"
|
|
5
6
|
|
|
6
7
|
module Sidekiq
|
|
7
8
|
module Scheduled
|
|
8
|
-
SETS = %w
|
|
9
|
+
SETS = %w[retry schedule]
|
|
9
10
|
|
|
10
11
|
class Enq
|
|
11
|
-
|
|
12
|
+
LUA_ZPOPBYSCORE = <<~LUA
|
|
13
|
+
local key, now = KEYS[1], ARGV[1]
|
|
14
|
+
local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1)
|
|
15
|
+
if jobs[1] then
|
|
16
|
+
redis.call("zrem", key, jobs[1])
|
|
17
|
+
return jobs[1]
|
|
18
|
+
end
|
|
19
|
+
LUA
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@lua_zpopbyscore_sha = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = SETS)
|
|
12
26
|
# A job's "score" in Redis is the time at which it should be processed.
|
|
13
27
|
# Just check Redis for the set of jobs with a timestamp before now.
|
|
14
28
|
Sidekiq.redis do |conn|
|
|
15
29
|
sorted_sets.each do |sorted_set|
|
|
16
|
-
# Get
|
|
30
|
+
# Get next item in the queue with score (time to execute) <= now.
|
|
17
31
|
# We need to go through the list one at a time to reduce the risk of something
|
|
18
32
|
# going wrong between the time jobs are popped from the scheduled queue and when
|
|
19
33
|
# they are pushed onto a work queue and losing the jobs.
|
|
20
|
-
while job = conn
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# the queue, it's because another process already popped it so we can move on to the
|
|
24
|
-
# next one.
|
|
25
|
-
if conn.zrem(sorted_set, job)
|
|
26
|
-
Sidekiq::Client.push(Sidekiq.load_json(job))
|
|
27
|
-
Sidekiq::Logging.logger.debug { "enqueued #{sorted_set}: #{job}" }
|
|
28
|
-
end
|
|
34
|
+
while (job = zpopbyscore(conn, keys: [sorted_set], argv: [now]))
|
|
35
|
+
Sidekiq::Client.push(Sidekiq.load_json(job))
|
|
36
|
+
Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" }
|
|
29
37
|
end
|
|
30
38
|
end
|
|
31
39
|
end
|
|
32
40
|
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def zpopbyscore(conn, keys: nil, argv: nil)
|
|
45
|
+
@lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE) if @lua_zpopbyscore_sha.nil?
|
|
46
|
+
|
|
47
|
+
conn.evalsha(@lua_zpopbyscore_sha, keys: keys, argv: argv)
|
|
48
|
+
rescue Redis::CommandError => e
|
|
49
|
+
raise unless e.message.start_with?("NOSCRIPT")
|
|
50
|
+
|
|
51
|
+
@lua_zpopbyscore_sha = nil
|
|
52
|
+
retry
|
|
53
|
+
end
|
|
33
54
|
end
|
|
34
55
|
|
|
35
56
|
##
|
|
@@ -47,6 +68,7 @@ module Sidekiq
|
|
|
47
68
|
@sleeper = ConnectionPool::TimedStack.new
|
|
48
69
|
@done = false
|
|
49
70
|
@thread = nil
|
|
71
|
+
@count_calls = 0
|
|
50
72
|
end
|
|
51
73
|
|
|
52
74
|
# Shut down this instance, will pause until the thread is dead.
|
|
@@ -61,28 +83,24 @@ module Sidekiq
|
|
|
61
83
|
end
|
|
62
84
|
|
|
63
85
|
def start
|
|
64
|
-
@thread ||= safe_thread("scheduler")
|
|
86
|
+
@thread ||= safe_thread("scheduler") {
|
|
65
87
|
initial_wait
|
|
66
88
|
|
|
67
|
-
|
|
89
|
+
until @done
|
|
68
90
|
enqueue
|
|
69
91
|
wait
|
|
70
92
|
end
|
|
71
93
|
Sidekiq.logger.info("Scheduler exiting...")
|
|
72
|
-
|
|
94
|
+
}
|
|
73
95
|
end
|
|
74
96
|
|
|
75
97
|
def enqueue
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
ex.backtrace.each do |bt|
|
|
83
|
-
logger.error(bt)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
98
|
+
@enq.enqueue_jobs
|
|
99
|
+
rescue => ex
|
|
100
|
+
# Most likely a problem with redis networking.
|
|
101
|
+
# Punt and try again at the next interval
|
|
102
|
+
logger.error ex.message
|
|
103
|
+
handle_exception(ex)
|
|
86
104
|
end
|
|
87
105
|
|
|
88
106
|
private
|
|
@@ -95,13 +113,38 @@ module Sidekiq
|
|
|
95
113
|
# if poll_interval_average hasn't been calculated yet, we can
|
|
96
114
|
# raise an error trying to reach Redis.
|
|
97
115
|
logger.error ex.message
|
|
98
|
-
|
|
116
|
+
handle_exception(ex)
|
|
99
117
|
sleep 5
|
|
100
118
|
end
|
|
101
119
|
|
|
102
|
-
# Calculates a random interval that is ±50% the desired average.
|
|
103
120
|
def random_poll_interval
|
|
104
|
-
|
|
121
|
+
# We want one Sidekiq process to schedule jobs every N seconds. We have M processes
|
|
122
|
+
# and **don't** want to coordinate.
|
|
123
|
+
#
|
|
124
|
+
# So in N*M second timespan, we want each process to schedule once. The basic loop is:
|
|
125
|
+
#
|
|
126
|
+
# * sleep a random amount within that N*M timespan
|
|
127
|
+
# * wake up and schedule
|
|
128
|
+
#
|
|
129
|
+
# We want to avoid one edge case: imagine a set of 2 processes, scheduling every 5 seconds,
|
|
130
|
+
# so N*M = 10. Each process decides to randomly sleep 8 seconds, now we've failed to meet
|
|
131
|
+
# that 5 second average. Thankfully each schedule cycle will sleep randomly so the next
|
|
132
|
+
# iteration could see each process sleep for 1 second, undercutting our average.
|
|
133
|
+
#
|
|
134
|
+
# So below 10 processes, we special case and ensure the processes sleep closer to the average.
|
|
135
|
+
# In the example above, each process should schedule every 10 seconds on average. We special
|
|
136
|
+
# case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
|
|
137
|
+
# As we run more processes, the scheduling interval average will approach an even spread
|
|
138
|
+
# between 0 and poll interval so we don't need this artifical boost.
|
|
139
|
+
#
|
|
140
|
+
if process_count < 10
|
|
141
|
+
# For small clusters, calculate a random interval that is ±50% the desired average.
|
|
142
|
+
poll_interval_average * rand + poll_interval_average.to_f / 2
|
|
143
|
+
else
|
|
144
|
+
# With 10+ processes, we should have enough randomness to get decent polling
|
|
145
|
+
# across the entire timespan
|
|
146
|
+
poll_interval_average * rand
|
|
147
|
+
end
|
|
105
148
|
end
|
|
106
149
|
|
|
107
150
|
# We do our best to tune the poll interval to the size of the active Sidekiq
|
|
@@ -125,9 +168,18 @@ module Sidekiq
|
|
|
125
168
|
# This minimizes a single point of failure by dispersing check-ins but without taxing
|
|
126
169
|
# Redis if you run many Sidekiq processes.
|
|
127
170
|
def scaled_poll_interval
|
|
128
|
-
|
|
171
|
+
process_count * Sidekiq.options[:average_scheduled_poll_interval]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def process_count
|
|
175
|
+
# The work buried within Sidekiq::ProcessSet#cleanup can be
|
|
176
|
+
# expensive at scale. Cut it down by 90% with this counter.
|
|
177
|
+
# NB: This method is only called by the scheduler thread so we
|
|
178
|
+
# don't need to worry about the thread safety of +=.
|
|
179
|
+
pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size
|
|
129
180
|
pcount = 1 if pcount == 0
|
|
130
|
-
|
|
181
|
+
@count_calls += 1
|
|
182
|
+
pcount
|
|
131
183
|
end
|
|
132
184
|
|
|
133
185
|
def initial_wait
|
|
@@ -141,7 +193,6 @@ module Sidekiq
|
|
|
141
193
|
@sleeper.pop(total)
|
|
142
194
|
rescue Timeout::Error
|
|
143
195
|
end
|
|
144
|
-
|
|
145
196
|
end
|
|
146
197
|
end
|
|
147
198
|
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The MIT License
|
|
4
|
+
#
|
|
5
|
+
# Copyright (c) 2017, 2018, 2019, 2020 Agis Anastasopoulos
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
8
|
+
# this software and associated documentation files (the "Software"), to deal in
|
|
9
|
+
# the Software without restriction, including without limitation the rights to
|
|
10
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
11
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
|
12
|
+
# subject to the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
# copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
19
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
20
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
21
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
22
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
# This is a copy of https://github.com/agis/ruby-sdnotify as of commit a7d52ee
|
|
25
|
+
# The only changes made was "rehoming" it within the Sidekiq module to avoid
|
|
26
|
+
# namespace collisions and applying standard's code formatting style.
|
|
27
|
+
|
|
28
|
+
require "socket"
|
|
29
|
+
|
|
30
|
+
# SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to
|
|
31
|
+
# notify systemd about state changes. Methods of this package are no-op on
|
|
32
|
+
# non-systemd systems (eg. Darwin).
|
|
33
|
+
#
|
|
34
|
+
# The API maps closely to the original implementation of sd_notify(3),
|
|
35
|
+
# therefore be sure to check the official man pages prior to using SdNotify.
|
|
36
|
+
#
|
|
37
|
+
# @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
|
38
|
+
module Sidekiq
|
|
39
|
+
module SdNotify
|
|
40
|
+
# Exception raised when there's an error writing to the notification socket
|
|
41
|
+
class NotifyError < RuntimeError; end
|
|
42
|
+
|
|
43
|
+
READY = "READY=1"
|
|
44
|
+
RELOADING = "RELOADING=1"
|
|
45
|
+
STOPPING = "STOPPING=1"
|
|
46
|
+
STATUS = "STATUS="
|
|
47
|
+
ERRNO = "ERRNO="
|
|
48
|
+
MAINPID = "MAINPID="
|
|
49
|
+
WATCHDOG = "WATCHDOG=1"
|
|
50
|
+
FDSTORE = "FDSTORE=1"
|
|
51
|
+
|
|
52
|
+
def self.ready(unset_env = false)
|
|
53
|
+
notify(READY, unset_env)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.reloading(unset_env = false)
|
|
57
|
+
notify(RELOADING, unset_env)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.stopping(unset_env = false)
|
|
61
|
+
notify(STOPPING, unset_env)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param status [String] a custom status string that describes the current
|
|
65
|
+
# state of the service
|
|
66
|
+
def self.status(status, unset_env = false)
|
|
67
|
+
notify("#{STATUS}#{status}", unset_env)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @param errno [Integer]
|
|
71
|
+
def self.errno(errno, unset_env = false)
|
|
72
|
+
notify("#{ERRNO}#{errno}", unset_env)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param pid [Integer]
|
|
76
|
+
def self.mainpid(pid, unset_env = false)
|
|
77
|
+
notify("#{MAINPID}#{pid}", unset_env)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.watchdog(unset_env = false)
|
|
81
|
+
notify(WATCHDOG, unset_env)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.fdstore(unset_env = false)
|
|
85
|
+
notify(FDSTORE, unset_env)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @return [Boolean] true if the service manager expects watchdog keep-alive
|
|
89
|
+
# notification messages to be sent from this process.
|
|
90
|
+
#
|
|
91
|
+
# If the $WATCHDOG_USEC environment variable is set,
|
|
92
|
+
# and the $WATCHDOG_PID variable is unset or set to the PID of the current
|
|
93
|
+
# process
|
|
94
|
+
#
|
|
95
|
+
# @note Unlike sd_watchdog_enabled(3), this method does not mutate the
|
|
96
|
+
# environment.
|
|
97
|
+
def self.watchdog?
|
|
98
|
+
wd_usec = ENV["WATCHDOG_USEC"]
|
|
99
|
+
wd_pid = ENV["WATCHDOG_PID"]
|
|
100
|
+
|
|
101
|
+
return false unless wd_usec
|
|
102
|
+
|
|
103
|
+
begin
|
|
104
|
+
wd_usec = Integer(wd_usec)
|
|
105
|
+
rescue
|
|
106
|
+
return false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
return false if wd_usec <= 0
|
|
110
|
+
return true if !wd_pid || wd_pid == $$.to_s
|
|
111
|
+
|
|
112
|
+
false
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Notify systemd with the provided state, via the notification socket, if
|
|
116
|
+
# any.
|
|
117
|
+
#
|
|
118
|
+
# Generally this method will be used indirectly through the other methods
|
|
119
|
+
# of the library.
|
|
120
|
+
#
|
|
121
|
+
# @param state [String]
|
|
122
|
+
# @param unset_env [Boolean]
|
|
123
|
+
#
|
|
124
|
+
# @return [Fixnum, nil] the number of bytes written to the notification
|
|
125
|
+
# socket or nil if there was no socket to report to (eg. the program wasn't
|
|
126
|
+
# started by systemd)
|
|
127
|
+
#
|
|
128
|
+
# @raise [NotifyError] if there was an error communicating with the systemd
|
|
129
|
+
# socket
|
|
130
|
+
#
|
|
131
|
+
# @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
|
132
|
+
def self.notify(state, unset_env = false)
|
|
133
|
+
sock = ENV["NOTIFY_SOCKET"]
|
|
134
|
+
|
|
135
|
+
return nil unless sock
|
|
136
|
+
|
|
137
|
+
ENV.delete("NOTIFY_SOCKET") if unset_env
|
|
138
|
+
|
|
139
|
+
begin
|
|
140
|
+
Addrinfo.unix(sock, :DGRAM).connect do |s|
|
|
141
|
+
s.close_on_exec = true
|
|
142
|
+
s.write(state)
|
|
143
|
+
end
|
|
144
|
+
rescue => e
|
|
145
|
+
raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Sidekiq's systemd integration allows Sidekiq to inform systemd:
|
|
3
|
+
# 1. when it has successfully started
|
|
4
|
+
# 2. when it is starting shutdown
|
|
5
|
+
# 3. periodically for a liveness check with a watchdog thread
|
|
6
|
+
#
|
|
7
|
+
module Sidekiq
|
|
8
|
+
def self.start_watchdog
|
|
9
|
+
usec = Integer(ENV["WATCHDOG_USEC"])
|
|
10
|
+
return Sidekiq.logger.error("systemd Watchdog too fast: " + usec) if usec < 1_000_000
|
|
11
|
+
|
|
12
|
+
sec_f = usec / 1_000_000.0
|
|
13
|
+
# "It is recommended that a daemon sends a keep-alive notification message
|
|
14
|
+
# to the service manager every half of the time returned here."
|
|
15
|
+
ping_f = sec_f / 2
|
|
16
|
+
Sidekiq.logger.info "Pinging systemd watchdog every #{ping_f.round(1)} sec"
|
|
17
|
+
Thread.new do
|
|
18
|
+
loop do
|
|
19
|
+
sleep ping_f
|
|
20
|
+
Sidekiq::SdNotify.watchdog
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/sidekiq/testing.rb
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'securerandom'
|
|
3
|
-
require 'sidekiq'
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "sidekiq"
|
|
6
5
|
|
|
6
|
+
module Sidekiq
|
|
7
7
|
class Testing
|
|
8
8
|
class << self
|
|
9
9
|
attr_accessor :__test_mode
|
|
10
10
|
|
|
11
11
|
def __set_test_mode(mode)
|
|
12
12
|
if block_given?
|
|
13
|
-
current_mode =
|
|
13
|
+
current_mode = __test_mode
|
|
14
14
|
begin
|
|
15
15
|
self.__test_mode = mode
|
|
16
16
|
yield
|
|
@@ -35,19 +35,19 @@ module Sidekiq
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def enabled?
|
|
38
|
-
|
|
38
|
+
__test_mode != :disable
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def disabled?
|
|
42
|
-
|
|
42
|
+
__test_mode == :disable
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def fake?
|
|
46
|
-
|
|
46
|
+
__test_mode == :fake
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def inline?
|
|
50
|
-
|
|
50
|
+
__test_mode == :inline
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def server_middleware
|
|
@@ -55,6 +55,15 @@ module Sidekiq
|
|
|
55
55
|
yield @server_chain if block_given?
|
|
56
56
|
@server_chain
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
def constantize(str)
|
|
60
|
+
names = str.split("::")
|
|
61
|
+
names.shift if names.empty? || names.first.empty?
|
|
62
|
+
|
|
63
|
+
names.inject(Object) do |constant, name|
|
|
64
|
+
constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
58
67
|
end
|
|
59
68
|
end
|
|
60
69
|
|
|
@@ -63,29 +72,31 @@ module Sidekiq
|
|
|
63
72
|
|
|
64
73
|
class EmptyQueueError < RuntimeError; end
|
|
65
74
|
|
|
66
|
-
|
|
67
|
-
alias_method :raw_push_real, :raw_push
|
|
68
|
-
|
|
75
|
+
module TestingClient
|
|
69
76
|
def raw_push(payloads)
|
|
70
77
|
if Sidekiq::Testing.fake?
|
|
71
78
|
payloads.each do |job|
|
|
72
|
-
|
|
79
|
+
job = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
80
|
+
job["enqueued_at"] = Time.now.to_f unless job["at"]
|
|
81
|
+
Queues.push(job["queue"], job["class"], job)
|
|
73
82
|
end
|
|
74
83
|
true
|
|
75
84
|
elsif Sidekiq::Testing.inline?
|
|
76
85
|
payloads.each do |job|
|
|
77
|
-
klass = job[
|
|
78
|
-
job[
|
|
86
|
+
klass = Sidekiq::Testing.constantize(job["class"])
|
|
87
|
+
job["id"] ||= SecureRandom.hex(12)
|
|
79
88
|
job_hash = Sidekiq.load_json(Sidekiq.dump_json(job))
|
|
80
89
|
klass.process_job(job_hash)
|
|
81
90
|
end
|
|
82
91
|
true
|
|
83
92
|
else
|
|
84
|
-
|
|
93
|
+
super
|
|
85
94
|
end
|
|
86
95
|
end
|
|
87
96
|
end
|
|
88
97
|
|
|
98
|
+
Sidekiq::Client.prepend TestingClient
|
|
99
|
+
|
|
89
100
|
module Queues
|
|
90
101
|
##
|
|
91
102
|
# The Queues class is only for testing the fake queue implementation.
|
|
@@ -244,27 +255,26 @@ module Sidekiq
|
|
|
244
255
|
# Then I should receive a welcome email to "foo@example.com"
|
|
245
256
|
#
|
|
246
257
|
module ClassMethods
|
|
247
|
-
|
|
248
258
|
# Queue for this worker
|
|
249
259
|
def queue
|
|
250
|
-
|
|
260
|
+
get_sidekiq_options["queue"]
|
|
251
261
|
end
|
|
252
262
|
|
|
253
263
|
# Jobs queued for this worker
|
|
254
264
|
def jobs
|
|
255
|
-
Queues.jobs_by_worker[
|
|
265
|
+
Queues.jobs_by_worker[to_s]
|
|
256
266
|
end
|
|
257
267
|
|
|
258
268
|
# Clear all jobs for this worker
|
|
259
269
|
def clear
|
|
260
|
-
Queues.clear_for(queue,
|
|
270
|
+
Queues.clear_for(queue, to_s)
|
|
261
271
|
end
|
|
262
272
|
|
|
263
273
|
# Drain and run all jobs for this worker
|
|
264
274
|
def drain
|
|
265
275
|
while jobs.any?
|
|
266
276
|
next_job = jobs.first
|
|
267
|
-
Queues.delete_for(next_job["jid"], next_job["queue"],
|
|
277
|
+
Queues.delete_for(next_job["jid"], next_job["queue"], to_s)
|
|
268
278
|
process_job(next_job)
|
|
269
279
|
end
|
|
270
280
|
end
|
|
@@ -273,16 +283,16 @@ module Sidekiq
|
|
|
273
283
|
def perform_one
|
|
274
284
|
raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty?
|
|
275
285
|
next_job = jobs.first
|
|
276
|
-
Queues.delete_for(next_job["jid"], queue,
|
|
286
|
+
Queues.delete_for(next_job["jid"], queue, to_s)
|
|
277
287
|
process_job(next_job)
|
|
278
288
|
end
|
|
279
289
|
|
|
280
290
|
def process_job(job)
|
|
281
291
|
worker = new
|
|
282
|
-
worker.jid = job[
|
|
283
|
-
worker.bid = job[
|
|
284
|
-
Sidekiq::Testing.server_middleware.invoke(worker, job, job[
|
|
285
|
-
execute_job(worker, job[
|
|
292
|
+
worker.jid = job["jid"]
|
|
293
|
+
worker.bid = job["bid"] if worker.respond_to?(:bid=)
|
|
294
|
+
Sidekiq::Testing.server_middleware.invoke(worker, job, job["queue"]) do
|
|
295
|
+
execute_job(worker, job["args"])
|
|
286
296
|
end
|
|
287
297
|
end
|
|
288
298
|
|
|
@@ -307,10 +317,26 @@ module Sidekiq
|
|
|
307
317
|
worker_classes = jobs.map { |job| job["class"] }.uniq
|
|
308
318
|
|
|
309
319
|
worker_classes.each do |worker_class|
|
|
310
|
-
|
|
320
|
+
Sidekiq::Testing.constantize(worker_class).drain
|
|
311
321
|
end
|
|
312
322
|
end
|
|
313
323
|
end
|
|
314
324
|
end
|
|
315
325
|
end
|
|
326
|
+
|
|
327
|
+
module TestingExtensions
|
|
328
|
+
def jobs_for(klass)
|
|
329
|
+
jobs.select do |job|
|
|
330
|
+
marshalled = job["args"][0]
|
|
331
|
+
marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer)
|
|
337
|
+
Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING
|
|
341
|
+
warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1)
|
|
316
342
|
end
|
data/lib/sidekiq/util.rb
CHANGED
|
@@ -1,27 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
require "socket"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require "sidekiq/exception_handler"
|
|
6
7
|
|
|
7
8
|
module Sidekiq
|
|
8
9
|
##
|
|
9
10
|
# This module is part of Sidekiq core and not intended for extensions.
|
|
10
11
|
#
|
|
12
|
+
|
|
13
|
+
class RingBuffer
|
|
14
|
+
include Enumerable
|
|
15
|
+
extend Forwardable
|
|
16
|
+
def_delegators :@buf, :[], :each, :size
|
|
17
|
+
|
|
18
|
+
def initialize(size, default = 0)
|
|
19
|
+
@size = size
|
|
20
|
+
@buf = Array.new(size, default)
|
|
21
|
+
@index = 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def <<(element)
|
|
25
|
+
@buf[@index % @size] = element
|
|
26
|
+
@index += 1
|
|
27
|
+
element
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def buffer
|
|
31
|
+
@buf
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reset(default = 0)
|
|
35
|
+
@buf.fill(default)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
11
39
|
module Util
|
|
12
40
|
include ExceptionHandler
|
|
13
41
|
|
|
14
|
-
EXPIRY = 60 * 60 * 24
|
|
15
|
-
|
|
16
42
|
def watchdog(last_words)
|
|
17
43
|
yield
|
|
18
44
|
rescue Exception => ex
|
|
19
|
-
handle_exception(ex, {
|
|
45
|
+
handle_exception(ex, {context: last_words})
|
|
20
46
|
raise ex
|
|
21
47
|
end
|
|
22
48
|
|
|
23
49
|
def safe_thread(name, &block)
|
|
24
50
|
Thread.new do
|
|
51
|
+
Thread.current.name = name
|
|
25
52
|
watchdog(name, &block)
|
|
26
53
|
end
|
|
27
54
|
end
|
|
@@ -34,8 +61,12 @@ module Sidekiq
|
|
|
34
61
|
Sidekiq.redis(&block)
|
|
35
62
|
end
|
|
36
63
|
|
|
64
|
+
def tid
|
|
65
|
+
Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
|
|
66
|
+
end
|
|
67
|
+
|
|
37
68
|
def hostname
|
|
38
|
-
ENV[
|
|
69
|
+
ENV["DYNO"] || Socket.gethostname
|
|
39
70
|
end
|
|
40
71
|
|
|
41
72
|
def process_nonce
|
|
@@ -43,18 +74,20 @@ module Sidekiq
|
|
|
43
74
|
end
|
|
44
75
|
|
|
45
76
|
def identity
|
|
46
|
-
@@identity ||= "#{hostname}:#{
|
|
77
|
+
@@identity ||= "#{hostname}:#{::Process.pid}:#{process_nonce}"
|
|
47
78
|
end
|
|
48
79
|
|
|
49
|
-
def fire_event(event,
|
|
80
|
+
def fire_event(event, options = {})
|
|
81
|
+
reverse = options[:reverse]
|
|
82
|
+
reraise = options[:reraise]
|
|
83
|
+
|
|
50
84
|
arr = Sidekiq.options[:lifecycle_events][event]
|
|
51
85
|
arr.reverse! if reverse
|
|
52
86
|
arr.each do |block|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
87
|
+
block.call
|
|
88
|
+
rescue => ex
|
|
89
|
+
handle_exception(ex, {context: "Exception during Sidekiq lifecycle event.", event: event})
|
|
90
|
+
raise ex if reraise
|
|
58
91
|
end
|
|
59
92
|
arr.clear
|
|
60
93
|
end
|
data/lib/sidekiq/version.rb
CHANGED