Module: SidekiqSolidFetch
- Defined in:
- lib/sidekiq_solid_fetch.rb,
lib/sidekiq_solid_fetch/fetcher.rb,
lib/sidekiq_solid_fetch/version.rb,
lib/sidekiq_solid_fetch/throttled.rb,
lib/sidekiq_solid_fetch/unit_of_work.rb
Defined Under Namespace
Modules: Throttled
Classes: Fetcher, UnitOfWork
Constant Summary
collapse
- PROCESSING_QUEUE_PREFIX =
"sidekiq_solid_fetch:processing"
- REGISTRY_KEY =
Registry of every live processing queue. A Redis hash where each field is
a processing queue key and each value is a JSON blob with the original
queue, the worker identity and the registration timestamp. Recovery reads
this registry instead of parsing key names, which would be ambiguous
because both queue names and identities can contain colons.
"sidekiq_solid_fetch:registry"
- RECOVERY_LOCK_KEY =
"sidekiq_solid_fetch:recovery_lock"
- GRACE_PERIOD =
Entries younger than this are never considered orphaned, so a freshly
booted worker whose first heartbeat hasn't landed yet can't have its
in-flight jobs stolen by another process running recovery.
60
- RECOVERY_INTERVAL =
How often each process attempts orphan recovery after boot. Runs are
deduplicated across processes with RECOVERY_LOCK_KEY.
300
- VERSION =
"0.2.0"
Class Method Summary
collapse
Class Method Details
.acquire_recovery_lock(config) ⇒ Object
103
104
105
|
# File 'lib/sidekiq_solid_fetch.rb', line 103
def self.acquire_recovery_lock(config)
config.redis { |conn| conn.set(RECOVERY_LOCK_KEY, "1", "NX", "EX", RECOVERY_INTERVAL) }
end
|
.enable!(config) ⇒ Object
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
# File 'lib/sidekiq_solid_fetch.rb', line 28
def self.enable!(config)
config[:fetch_class] = SidekiqSolidFetch::Fetcher
config.on(:startup) do
config.logger.info("SidekiqSolidFetch enabled")
requeue_orphaned_jobs(config)
@recovery_thread = start_recovery_thread(config)
end
config.on(:shutdown) do
@recovery_thread&.kill
@recovery_thread = nil
end
end
|
.parse_registry_entry(raw_entry) ⇒ Object
107
108
109
110
111
112
113
|
# File 'lib/sidekiq_solid_fetch.rb', line 107
def self.parse_registry_entry(raw_entry)
entry = JSON.parse(raw_entry)
return nil unless entry.is_a?(Hash) && entry["queue"] && entry["identity"]
entry
rescue JSON::ParserError
nil
end
|
.processing_queue_name(queue, identity, capsule_name) ⇒ Object
77
78
79
|
# File 'lib/sidekiq_solid_fetch.rb', line 77
def self.processing_queue_name(queue, identity, capsule_name)
"#{PROCESSING_QUEUE_PREFIX}:#{capsule_name}:#{identity}:#{queue}"
end
|
.register_processing_queue(conn, processing_queue, queue, identity) ⇒ Object
81
82
83
84
|
# File 'lib/sidekiq_solid_fetch.rb', line 81
def self.register_processing_queue(conn, processing_queue, queue, identity)
entry = {"queue" => queue, "identity" => identity, "registered_at" => Time.now.to_i}
conn.hset(REGISTRY_KEY, processing_queue, JSON.generate(entry))
end
|
.requeue_orphaned_jobs(config) ⇒ Object
Requeue jobs from processing queues whose worker is dead. A worker is
considered dead when its heartbeat key (which Sidekiq maintains with a
TTL) no longer exists. Membership in the processes set is not used
because entries for crashed workers linger there until Sidekiq's
rate-limited cleanup runs.
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
# File 'lib/sidekiq_solid_fetch.rb', line 48
def self.requeue_orphaned_jobs(config)
logger = config.logger
logger.info("SidekiqSolidFetch: Checking for orphaned jobs from dead workers")
count = 0
now = Time.now.to_i
config.redis do |conn|
conn.hgetall(REGISTRY_KEY).each do |processing_queue, raw_entry|
entry = parse_registry_entry(raw_entry)
unless entry
conn.hdel(REGISTRY_KEY, processing_queue)
next
end
next if now - entry["registered_at"].to_i < GRACE_PERIOD
next if conn.exists(entry["identity"]) > 0
queue = entry["queue"]
while conn.lmove(processing_queue, queue, "LEFT", "RIGHT")
count += 1
logger.info { "SidekiqSolidFetch: Moved orphaned job from #{processing_queue} back to #{queue}" }
end
conn.hdel(REGISTRY_KEY, processing_queue)
end
end
logger.info("SidekiqSolidFetch: Re-queued #{count} orphaned jobs")
end
|
.start_recovery_thread(config) ⇒ Object
90
91
92
93
94
95
96
97
98
99
100
101
|
# File 'lib/sidekiq_solid_fetch.rb', line 90
def self.start_recovery_thread(config)
Thread.new do
loop do
sleep(RECOVERY_INTERVAL)
begin
requeue_orphaned_jobs(config) if acquire_recovery_lock(config)
rescue => ex
config.logger.warn("SidekiqSolidFetch: Orphan recovery failed: #{ex.message}")
end
end
end
end
|
.unregister_processing_queues(conn, processing_queues) ⇒ Object
86
87
88
|
# File 'lib/sidekiq_solid_fetch.rb', line 86
def self.unregister_processing_queues(conn, processing_queues)
conn.hdel(REGISTRY_KEY, *processing_queues) unless processing_queues.empty?
end
|