sidekiq-ultimate 0.0.1.alpha.19 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|