sidekiq-ultimate 0.0.1.alpha.19 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rspec.yml +58 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +42 -6
- data/ARCHITECTURE.md +1 -1
- data/Appraisals +13 -0
- data/CHANGES.md +6 -0
- data/Gemfile +5 -7
- data/README.md +80 -14
- data/Rakefile +1 -5
- data/docker-compose.yml +12 -0
- data/gemfiles/redis_4.8.0_sidekiq_6.5.0.gemfile +29 -0
- data/lib/sidekiq/ultimate/configuration.rb +67 -0
- data/lib/sidekiq/ultimate/empty_queues/refresh_timer_task.rb +26 -0
- data/lib/sidekiq/ultimate/empty_queues.rb +144 -0
- data/lib/sidekiq/ultimate/expirable_set.rb +2 -5
- data/lib/sidekiq/ultimate/fetch.rb +21 -21
- data/lib/sidekiq/ultimate/interval_with_jitter.rb +19 -0
- data/lib/sidekiq/ultimate/queue_name.rb +4 -5
- data/lib/sidekiq/ultimate/resurrector/common_constants.rb +13 -0
- data/lib/sidekiq/ultimate/resurrector/count.rb +22 -0
- data/lib/sidekiq/ultimate/resurrector/lock.rb +46 -0
- data/lib/sidekiq/ultimate/resurrector/lua_scripts/resurrect_with_counter.lua +22 -0
- data/lib/sidekiq/ultimate/resurrector/resurrection_script.rb +51 -0
- data/lib/sidekiq/ultimate/resurrector.rb +62 -69
- data/lib/sidekiq/ultimate/unit_of_work.rb +5 -4
- data/lib/sidekiq/ultimate/version.rb +1 -2
- data/lib/sidekiq/ultimate.rb +11 -2
- data/sidekiq-ultimate.gemspec +7 -9
- metadata +49 -22
- data/.travis.yml +0 -28
- /data/lib/sidekiq/ultimate/resurrector/{resurrect.lua → lua_scripts/resurrect.lua} +0 -0
- /data/lib/sidekiq/ultimate/resurrector/{safeclean.lua → lua_scripts/safeclean.lua} +0 -0
@@ -1,11 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "sidekiq"
|
3
4
|
require "sidekiq/throttled"
|
4
5
|
|
5
6
|
require "sidekiq/ultimate/expirable_set"
|
6
7
|
require "sidekiq/ultimate/queue_name"
|
7
8
|
require "sidekiq/ultimate/resurrector"
|
8
9
|
require "sidekiq/ultimate/unit_of_work"
|
10
|
+
require "sidekiq/ultimate/empty_queues"
|
11
|
+
require "sidekiq/ultimate/configuration"
|
9
12
|
|
10
13
|
module Sidekiq
|
11
14
|
module Ultimate
|
@@ -14,17 +17,11 @@ module Sidekiq
|
|
14
17
|
# Delay between fetch retries in case of no job received.
|
15
18
|
TIMEOUT = 2
|
16
19
|
|
17
|
-
# Delay between queue poll attempts if last poll returned no jobs for it.
|
18
|
-
QUEUE_TIMEOUT = 5
|
19
|
-
|
20
|
-
# Delay between queue poll attempts if it's last job was throttled.
|
21
|
-
THROTTLE_TIMEOUT = 15
|
22
|
-
|
23
20
|
def initialize(options)
|
24
|
-
@
|
25
|
-
|
21
|
+
@exhausted_by_throttling = ExpirableSet.new
|
22
|
+
@empty_queues = Sidekiq::Ultimate::EmptyQueues.instance
|
26
23
|
@strict = options[:strict] ? true : false
|
27
|
-
@queues = options[:queues]
|
24
|
+
@queues = options[:queues]
|
28
25
|
|
29
26
|
@queues.uniq! if @strict
|
30
27
|
|
@@ -39,8 +36,9 @@ module Sidekiq
|
|
39
36
|
if work&.throttled?
|
40
37
|
work.requeue_throttled
|
41
38
|
|
42
|
-
|
43
|
-
|
39
|
+
@exhausted_by_throttling.add(
|
40
|
+
work.queue_name, :ttl => Sidekiq::Ultimate::Configuration.instance.throttled_fetch_timeout_sec
|
41
|
+
)
|
44
42
|
|
45
43
|
return nil
|
46
44
|
end
|
@@ -48,24 +46,26 @@ module Sidekiq
|
|
48
46
|
work
|
49
47
|
end
|
50
48
|
|
51
|
-
|
49
|
+
# TODO: Requeue in batch or at least using pipeline
|
50
|
+
def bulk_requeue(units, _options)
|
52
51
|
units.each(&:requeue)
|
53
52
|
end
|
54
53
|
|
55
54
|
def self.setup!
|
56
|
-
|
55
|
+
fetcher = new(Sidekiq)
|
56
|
+
|
57
|
+
Sidekiq[:fetch] = fetcher
|
57
58
|
Resurrector.setup!
|
59
|
+
EmptyQueues.setup!
|
58
60
|
end
|
59
61
|
|
60
62
|
private
|
61
63
|
|
62
64
|
def retrieve
|
63
65
|
Sidekiq.redis do |redis|
|
64
|
-
|
66
|
+
queues_objects.each do |queue|
|
65
67
|
job = redis.rpoplpush(queue.pending, queue.inproc)
|
66
68
|
return UnitOfWork.new(queue, job) if job
|
67
|
-
|
68
|
-
@exhausted.add(queue, :ttl => QUEUE_TIMEOUT)
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
@@ -73,19 +73,19 @@ module Sidekiq
|
|
73
73
|
nil
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
77
|
-
queues = (@strict ? @queues : @queues.shuffle.uniq) - @
|
76
|
+
def queues_objects
|
77
|
+
queues = (@strict ? @queues : @queues.shuffle.uniq) - @exhausted_by_throttling.to_a - @empty_queues.queues
|
78
78
|
|
79
79
|
# Avoid calling heavier `paused_queue` if there's nothing to filter out
|
80
|
-
return
|
80
|
+
return [] if queues.empty?
|
81
81
|
|
82
|
-
queues - paused_queues
|
82
|
+
(queues - paused_queues).map { |name| QueueName.new(name) }
|
83
83
|
end
|
84
84
|
|
85
85
|
def paused_queues
|
86
86
|
return @paused_queues if Time.now.to_i < @paused_queues_expires_at
|
87
87
|
|
88
|
-
@paused_queues
|
88
|
+
@paused_queues = Sidekiq::Throttled::QueuesPauser.instance.paused_queues
|
89
89
|
@paused_queues_expires_at = Time.now.to_i + 60
|
90
90
|
|
91
91
|
@paused_queues
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Ultimate
|
5
|
+
# Util class to add a jitter to the interval
|
6
|
+
class IntervalWithJitter
|
7
|
+
RANDOM_OFFSET_RATIO = 0.1
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Returns execution interval with jitter.
|
11
|
+
# Jitter is +- RANDOM_OFFSET_RATIO from the original value.
|
12
|
+
def call(interval)
|
13
|
+
jitter_factor = 1 + rand(-RANDOM_OFFSET_RATIO..RANDOM_OFFSET_RATIO)
|
14
|
+
jitter_factor * interval
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "sidekiq/
|
3
|
+
require "sidekiq/component"
|
4
4
|
|
5
5
|
module Sidekiq
|
6
6
|
module Ultimate
|
@@ -10,11 +10,11 @@ module Sidekiq
|
|
10
10
|
class QueueName
|
11
11
|
# Regexp used to normalize (possibly) expanded queue name, e.g. the one
|
12
12
|
# that is returned upon redis BRPOP
|
13
|
-
QUEUE_PREFIX_RE = %r{.*queue:}
|
13
|
+
QUEUE_PREFIX_RE = %r{.*queue:}.freeze
|
14
14
|
private_constant :QUEUE_PREFIX_RE
|
15
15
|
|
16
16
|
# Internal helper context.
|
17
|
-
Helper = Module.new { extend Sidekiq::
|
17
|
+
Helper = Module.new { extend Sidekiq::Component }
|
18
18
|
private_constant :Helper
|
19
19
|
|
20
20
|
# Original stringified queue name.
|
@@ -29,8 +29,7 @@ module Sidekiq
|
|
29
29
|
|
30
30
|
# Create a new QueueName instance.
|
31
31
|
#
|
32
|
-
# @param normalized [#to_s] Normalized (without
|
33
|
-
# prefixes) queue name.
|
32
|
+
# @param normalized [#to_s] Normalized (without `queue:` prefixes) queue name.
|
34
33
|
# @param identity [#to_s] Sidekiq process identity.
|
35
34
|
def initialize(normalized, identity: Helper.identity)
|
36
35
|
@normalized = -normalized.to_s
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sidekiq/ultimate/resurrector/common_constants"
|
4
|
+
|
5
|
+
module Sidekiq
|
6
|
+
module Ultimate
|
7
|
+
module Resurrector
|
8
|
+
# Allows to get the count of times the job was resurrected
|
9
|
+
module Count
|
10
|
+
class << self
|
11
|
+
# @param job_id [String] job id
|
12
|
+
# @return [Integer] count of times the job was resurrected
|
13
|
+
def read(job_id:)
|
14
|
+
Sidekiq.redis do |redis|
|
15
|
+
redis.get("#{CommonConstants::MAIN_KEY}:counter:jid:#{job_id}").to_i
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redlock"
|
4
|
+
require "sidekiq/ultimate/resurrector/common_constants"
|
5
|
+
|
6
|
+
module Sidekiq
|
7
|
+
module Ultimate
|
8
|
+
module Resurrector
|
9
|
+
# Ensures exclusive access to resurrection process
|
10
|
+
class Lock
|
11
|
+
LOCK_KEY = "#{CommonConstants::MAIN_KEY}:lock"
|
12
|
+
private_constant :LOCK_KEY
|
13
|
+
|
14
|
+
LAST_RUN_KEY = "#{CommonConstants::MAIN_KEY}:last_run"
|
15
|
+
private_constant :LAST_RUN_KEY
|
16
|
+
|
17
|
+
LOCK_TTL = 30_000 # ms
|
18
|
+
private_constant :LOCK_TTL
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def acquire
|
22
|
+
Sidekiq.redis do |redis|
|
23
|
+
break if resurrected_recently?(redis) # Cheap check since lock will not be free most of the time
|
24
|
+
|
25
|
+
Redlock::Client.new([redis], :retry_count => 0).lock(LOCK_KEY, LOCK_TTL) do |locked|
|
26
|
+
break unless locked
|
27
|
+
break if resurrected_recently?(redis)
|
28
|
+
|
29
|
+
yield
|
30
|
+
|
31
|
+
redis.set(LAST_RUN_KEY, redis.time.first)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def resurrected_recently?(redis)
|
37
|
+
results = redis.pipelined { |pipeline| [pipeline.time, pipeline.get(LAST_RUN_KEY)] }
|
38
|
+
distance = results[0][0] - results[1].to_i
|
39
|
+
|
40
|
+
distance < CommonConstants::RESURRECTOR_INTERVAL
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
local resurrected_jobs = 0
|
2
|
+
|
3
|
+
while true do
|
4
|
+
local job_data = redis.call("LPOP", KEYS[1])
|
5
|
+
|
6
|
+
if job_data then
|
7
|
+
redis.call("RPUSH", KEYS[2], job_data)
|
8
|
+
|
9
|
+
resurrected_jobs = resurrected_jobs + 1
|
10
|
+
|
11
|
+
local _, jid_position = string.find(job_data, "\"jid\"")
|
12
|
+
jid_position = jid_position + 3
|
13
|
+
|
14
|
+
local jid = job_data:sub(jid_position, jid_position + 23)
|
15
|
+
local jid_key = KEYS[3] .. ':counter:jid:' .. jid
|
16
|
+
|
17
|
+
redis.call("INCR", jid_key)
|
18
|
+
redis.call("EXPIRE", jid_key, 86400)
|
19
|
+
else
|
20
|
+
return resurrected_jobs
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis_prescription"
|
4
|
+
require "sidekiq/ultimate/configuration"
|
5
|
+
require "sidekiq/ultimate/resurrector/common_constants"
|
6
|
+
|
7
|
+
module Sidekiq
|
8
|
+
module Ultimate
|
9
|
+
module Resurrector
|
10
|
+
# Lost jobs checker and resurrector
|
11
|
+
class ResurrectionScript
|
12
|
+
RESURRECT = RedisPrescription.new(File.read("#{__dir__}/lua_scripts/resurrect.lua"))
|
13
|
+
private_constant :RESURRECT
|
14
|
+
|
15
|
+
RESURRECT_WITH_COUNTER = RedisPrescription.new(File.read("#{__dir__}/lua_scripts/resurrect_with_counter.lua"))
|
16
|
+
private_constant :RESURRECT_WITH_COUNTER
|
17
|
+
|
18
|
+
def self.call(*args)
|
19
|
+
new.call(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(redis, keys:)
|
23
|
+
# redis-namespace can only namespace arguments of the lua script, so we need to pass the main key
|
24
|
+
keys += [CommonConstants::MAIN_KEY] if enable_resurrection_counter
|
25
|
+
script.call(redis, :keys => keys)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def script
|
31
|
+
enable_resurrection_counter ? RESURRECT_WITH_COUNTER : RESURRECT
|
32
|
+
end
|
33
|
+
|
34
|
+
def enable_resurrection_counter
|
35
|
+
return @enable_resurrection_counter if defined?(@enable_resurrection_counter)
|
36
|
+
|
37
|
+
@enable_resurrection_counter =
|
38
|
+
if enable_resurrection_counter_setting.respond_to?(:call)
|
39
|
+
enable_resurrection_counter_setting.call
|
40
|
+
else
|
41
|
+
enable_resurrection_counter_setting
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def enable_resurrection_counter_setting
|
46
|
+
Sidekiq::Ultimate::Configuration.instance.enable_resurrection_counter
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,41 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "
|
3
|
+
require "redis_prescription"
|
4
|
+
require "concurrent/timer_task"
|
5
5
|
|
6
|
+
require "sidekiq/component"
|
6
7
|
require "sidekiq/ultimate/queue_name"
|
8
|
+
require "sidekiq/ultimate/resurrector/lock"
|
9
|
+
require "sidekiq/ultimate/resurrector/common_constants"
|
10
|
+
require "sidekiq/ultimate/resurrector/resurrection_script"
|
11
|
+
require "sidekiq/ultimate/configuration"
|
12
|
+
require "sidekiq/ultimate/interval_with_jitter"
|
7
13
|
|
8
14
|
module Sidekiq
|
9
15
|
module Ultimate
|
10
|
-
# Lost jobs resurrector
|
16
|
+
# Lost jobs checker and resurrector
|
11
17
|
module Resurrector
|
12
|
-
|
13
|
-
"#{__dir__}/resurrector/resurrect.lua"
|
14
|
-
private_constant :RESURRECT
|
15
|
-
|
16
|
-
SAFECLEAN = Redis::Prescription.read \
|
17
|
-
"#{__dir__}/resurrector/safeclean.lua"
|
18
|
+
SAFECLEAN = RedisPrescription.new(File.read("#{__dir__}/resurrector/lua_scripts/safeclean.lua"))
|
18
19
|
private_constant :SAFECLEAN
|
19
20
|
|
20
|
-
|
21
|
-
private_constant :
|
22
|
-
|
23
|
-
LOCK_KEY = "#{MAIN_KEY}:lock"
|
24
|
-
private_constant :LOCK_KEY
|
21
|
+
DEFIBRILLATE_INTERVAL = 5
|
22
|
+
private_constant :DEFIBRILLATE_INTERVAL
|
25
23
|
|
26
|
-
|
27
|
-
|
24
|
+
ResurrectorTimerTask = Class.new(Concurrent::TimerTask)
|
25
|
+
HeartbeatTimerTask = Class.new(Concurrent::TimerTask)
|
28
26
|
|
29
27
|
class << self
|
30
28
|
def setup!
|
31
|
-
|
32
|
-
|
33
|
-
register_aed!
|
34
|
-
call_cthulhu!
|
29
|
+
register_process_heartbeat
|
30
|
+
register_resurrector
|
35
31
|
end
|
36
32
|
|
33
|
+
# go over all sidekiq processes (identities) that were shut down recently, get all their queues and
|
34
|
+
# try to resurrect them
|
37
35
|
def resurrect!
|
38
|
-
|
36
|
+
Sidekiq::Ultimate::Resurrector::Lock.acquire do
|
39
37
|
casualties.each do |identity|
|
40
38
|
log(:debug) { "Resurrecting #{identity}" }
|
41
39
|
|
@@ -48,102 +46,97 @@ module Sidekiq
|
|
48
46
|
raise
|
49
47
|
end
|
50
48
|
|
49
|
+
def current_process_identity
|
50
|
+
@current_process_identity ||= Object.new.tap { |o| o.extend Sidekiq::Component }.identity
|
51
|
+
end
|
52
|
+
|
51
53
|
private
|
52
54
|
|
53
|
-
def
|
54
|
-
|
55
|
+
def register_resurrector
|
56
|
+
resurrector_timer_task = nil
|
55
57
|
|
56
58
|
Sidekiq.on(:startup) do
|
57
|
-
|
59
|
+
resurrector_timer_task&.shutdown
|
58
60
|
|
59
|
-
|
61
|
+
resurrector_timer_task = ResurrectorTimerTask.new({
|
60
62
|
:run_now => true,
|
61
|
-
:execution_interval =>
|
63
|
+
:execution_interval => Sidekiq::Ultimate::IntervalWithJitter.call(CommonConstants::RESURRECTOR_INTERVAL)
|
62
64
|
}) { resurrect! }
|
65
|
+
resurrector_timer_task.execute
|
63
66
|
end
|
64
67
|
|
65
|
-
Sidekiq.on(:shutdown) {
|
68
|
+
Sidekiq.on(:shutdown) { resurrector_timer_task&.shutdown }
|
66
69
|
end
|
67
70
|
|
68
|
-
def
|
69
|
-
|
71
|
+
def register_process_heartbeat
|
72
|
+
heartbeat_timer_task = nil
|
70
73
|
|
71
74
|
Sidekiq.on(:heartbeat) do
|
72
|
-
|
75
|
+
heartbeat_timer_task&.shutdown
|
73
76
|
|
74
|
-
|
77
|
+
heartbeat_timer_task = HeartbeatTimerTask.new({
|
75
78
|
:run_now => true,
|
76
|
-
:execution_interval =>
|
77
|
-
}) {
|
79
|
+
:execution_interval => Sidekiq::Ultimate::IntervalWithJitter.call(DEFIBRILLATE_INTERVAL)
|
80
|
+
}) { save_watched_queues }
|
81
|
+
heartbeat_timer_task.execute
|
78
82
|
end
|
79
83
|
|
80
|
-
Sidekiq.on(:shutdown) {
|
84
|
+
Sidekiq.on(:shutdown) { heartbeat_timer_task&.shutdown }
|
81
85
|
end
|
82
86
|
|
83
|
-
|
87
|
+
# put current list of queues into resurrection candidates
|
88
|
+
def save_watched_queues
|
84
89
|
Sidekiq.redis do |redis|
|
85
90
|
log(:debug) { "Defibrillating" }
|
86
91
|
|
87
|
-
queues = JSON.dump(Sidekiq
|
88
|
-
redis.hset(MAIN_KEY,
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def lock
|
93
|
-
Sidekiq.redis do |redis|
|
94
|
-
Redis::Lockers.acquire(redis, LOCK_KEY, :ttl => 30_000) do
|
95
|
-
results = redis.pipelined { |r| [r.time, r.get(LAST_RUN_KEY)] }
|
96
|
-
distance = results[0][0] - results[1].to_i
|
97
|
-
|
98
|
-
break unless 60 < distance
|
99
|
-
|
100
|
-
yield
|
101
|
-
|
102
|
-
redis.set(LAST_RUN_KEY, redis.time.first)
|
103
|
-
end
|
92
|
+
queues = JSON.dump(Sidekiq[:queues].uniq)
|
93
|
+
redis.hset(CommonConstants::MAIN_KEY, current_process_identity, queues)
|
104
94
|
end
|
105
95
|
end
|
106
96
|
|
97
|
+
# list of processes that disappeared after latest #save_watched_queues
|
107
98
|
def casualties
|
108
99
|
Sidekiq.redis do |redis|
|
109
|
-
|
110
|
-
identities = redis.hkeys(MAIN_KEY)
|
100
|
+
sidekiq_processes = redis.hkeys(CommonConstants::MAIN_KEY)
|
111
101
|
|
112
|
-
redis.pipelined
|
113
|
-
|
102
|
+
sidekiq_processes_alive = redis.pipelined do |pipeline|
|
103
|
+
sidekiq_processes.each do |process|
|
104
|
+
pipeline.exists?(process)
|
105
|
+
end
|
106
|
+
end
|
114
107
|
|
115
|
-
|
108
|
+
sidekiq_processes.zip(sidekiq_processes_alive).reject { |(_, alive)| alive }.map(&:first)
|
116
109
|
end
|
117
110
|
end
|
118
111
|
|
112
|
+
# Get list of genuine sidekiq queues names for a given identity (sidekiq process id)
|
119
113
|
def queues_of(identity)
|
120
114
|
Sidekiq.redis do |redis|
|
121
|
-
queues = redis.hget(MAIN_KEY, identity)
|
115
|
+
queues = redis.hget(CommonConstants::MAIN_KEY, identity)
|
122
116
|
|
123
117
|
return [] unless queues
|
124
118
|
|
125
|
-
JSON.parse(queues).map
|
126
|
-
QueueName.new(q, :identity => identity)
|
127
|
-
end
|
119
|
+
JSON.parse(queues).map { |q| QueueName.new(q, :identity => identity) }
|
128
120
|
end
|
129
121
|
end
|
130
122
|
|
123
|
+
# Move jobs from inproc to pending
|
131
124
|
def resurrect(queue)
|
132
125
|
Sidekiq.redis do |redis|
|
133
|
-
|
134
|
-
:keys => [queue.inproc, queue.pending]
|
135
|
-
})
|
126
|
+
resurrected_jobs_count = ResurrectionScript.call(redis, :keys => [queue.inproc, queue.pending])
|
136
127
|
|
137
|
-
if
|
138
|
-
log(:info) { "Resurrected #{
|
128
|
+
if resurrected_jobs_count.positive?
|
129
|
+
log(:info) { "Resurrected #{resurrected_jobs_count} jobs from #{queue.inproc}" }
|
130
|
+
Sidekiq::Ultimate::Configuration.instance.on_resurrection&.call(queue.to_s, resurrected_jobs_count)
|
139
131
|
end
|
140
132
|
end
|
141
133
|
end
|
142
134
|
|
135
|
+
# Delete empty inproc queues and clean up identity key from resurrection candidates (CommonConstants::MAIN_KEY)
|
143
136
|
def cleanup(identity, inprocs)
|
144
137
|
Sidekiq.redis do |redis|
|
145
|
-
result = SAFECLEAN.
|
146
|
-
:keys => [MAIN_KEY, *inprocs],
|
138
|
+
result = SAFECLEAN.call(redis, {
|
139
|
+
:keys => [CommonConstants::MAIN_KEY, *inprocs],
|
147
140
|
:argv => [identity]
|
148
141
|
})
|
149
142
|
|
@@ -153,7 +146,7 @@ module Sidekiq
|
|
153
146
|
|
154
147
|
def log(level)
|
155
148
|
Sidekiq.logger.public_send(level) do
|
156
|
-
"[#{self}] @#{
|
149
|
+
"[#{self}] @#{current_process_identity} #{yield}"
|
157
150
|
end
|
158
151
|
end
|
159
152
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "redis_prescription"
|
4
4
|
|
5
5
|
require "sidekiq/throttled"
|
6
6
|
|
@@ -10,8 +10,7 @@ module Sidekiq
|
|
10
10
|
#
|
11
11
|
# @private
|
12
12
|
class UnitOfWork
|
13
|
-
REQUEUE =
|
14
|
-
"#{__dir__}/unit_of_work/requeue.lua"
|
13
|
+
REQUEUE = RedisPrescription.new(File.read("#{__dir__}/unit_of_work/requeue.lua"))
|
15
14
|
private_constant :REQUEUE
|
16
15
|
|
17
16
|
# JSON payload
|
@@ -90,12 +89,14 @@ module Sidekiq
|
|
90
89
|
|
91
90
|
private
|
92
91
|
|
92
|
+
# If the jobs was in the inproc queue, then delete it from there and
|
93
|
+
# push the job back to the queue using `command`.
|
93
94
|
def __requeue__(command)
|
94
95
|
@mutex.synchronize do
|
95
96
|
return if @requeued || @acked
|
96
97
|
|
97
98
|
Sidekiq.redis do |redis|
|
98
|
-
REQUEUE.
|
99
|
+
REQUEUE.call(redis, {
|
99
100
|
:keys => [@queue.pending, @queue.inproc],
|
100
101
|
:argv => [command, @job]
|
101
102
|
})
|
data/lib/sidekiq/ultimate.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
require "sidekiq/throttled"
|
4
4
|
|
5
|
-
require "sidekiq/ultimate/
|
5
|
+
require "sidekiq/ultimate/configuration"
|
6
|
+
require "sidekiq/ultimate/resurrector/count"
|
6
7
|
|
7
8
|
module Sidekiq
|
8
9
|
# Sidekiq ultimate experience.
|
@@ -10,10 +11,18 @@ module Sidekiq
|
|
10
11
|
class << self
|
11
12
|
# Sets up reliable throttled fetch and friends.
|
12
13
|
# @return [void]
|
13
|
-
def setup!
|
14
|
+
def setup!(&configuration_block)
|
15
|
+
configuration_block&.call(Sidekiq::Ultimate::Configuration.instance)
|
16
|
+
|
14
17
|
Sidekiq::Throttled::Communicator.instance.setup!
|
15
18
|
Sidekiq::Throttled::QueuesPauser.instance.setup!
|
16
19
|
|
20
|
+
sidekiq_configure_server
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def sidekiq_configure_server
|
17
26
|
Sidekiq.configure_server do |config|
|
18
27
|
require "sidekiq/ultimate/fetch"
|
19
28
|
Sidekiq::Ultimate::Fetch.setup!
|
data/sidekiq-ultimate.gemspec
CHANGED
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
|
6
6
|
require "sidekiq/ultimate/version"
|
7
7
|
|
8
|
-
Gem::Specification.new do |spec|
|
8
|
+
Gem::Specification.new do |spec| # rubocop:disable Gemspec/RequireMFA
|
9
9
|
spec.name = "sidekiq-ultimate"
|
10
10
|
spec.version = Sidekiq::Ultimate::VERSION
|
11
11
|
spec.authors = ["Alexey Zapparov"]
|
@@ -23,15 +23,13 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
25
|
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
26
|
-
spec.add_runtime_dependency "redis
|
27
|
-
spec.add_runtime_dependency "redis-prescription", "~>
|
28
|
-
spec.add_runtime_dependency "
|
29
|
-
|
30
|
-
|
31
|
-
# this gem instead.
|
32
|
-
spec.add_runtime_dependency "sidekiq-throttled", "~> 0.8"
|
26
|
+
spec.add_runtime_dependency "redis", "~> 4.8"
|
27
|
+
spec.add_runtime_dependency "redis-prescription", "~> 2.6"
|
28
|
+
spec.add_runtime_dependency "redlock", "~> 1.3"
|
29
|
+
spec.add_runtime_dependency "sidekiq", "~> 6.5.0"
|
30
|
+
spec.add_runtime_dependency "sidekiq-throttled", "~> 0.18.0"
|
33
31
|
|
34
32
|
spec.add_development_dependency "bundler", "~> 2.0"
|
35
33
|
|
36
|
-
spec.required_ruby_version = "~> 2.
|
34
|
+
spec.required_ruby_version = "~> 2.7"
|
37
35
|
end
|