wurk 1.1.0 → 1.1.2
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/README.md +25 -0
- data/app/controllers/concerns/wurk/same_origin_guard.rb +40 -0
- data/app/controllers/concerns/wurk/sse_streaming.rb +48 -0
- data/app/controllers/concerns/wurk/stream_concurrency_guard.rb +53 -0
- data/app/controllers/wurk/api/pagination.rb +60 -11
- data/app/controllers/wurk/api/serializers.rb +5 -1
- data/app/controllers/wurk/api_controller.rb +51 -70
- data/app/controllers/wurk/application_controller.rb +26 -0
- data/app/controllers/wurk/dashboard_controller.rb +23 -1
- data/app/controllers/wurk/extensions_controller.rb +3 -12
- data/app/controllers/wurk/profiles_controller.rb +6 -1
- data/lib/wurk/batch/death_handler.rb +13 -0
- data/lib/wurk/batch/server_middleware.rb +9 -5
- data/lib/wurk/batch.rb +3 -0
- data/lib/wurk/capsule.rb +34 -21
- data/lib/wurk/cli.rb +16 -3
- data/lib/wurk/client/buffered.rb +30 -10
- data/lib/wurk/client.rb +45 -1
- data/lib/wurk/component.rb +23 -9
- data/lib/wurk/configuration.rb +83 -18
- data/lib/wurk/cron.rb +13 -1
- data/lib/wurk/dead_set.rb +16 -1
- data/lib/wurk/engine.rb +17 -1
- data/lib/wurk/fetcher/reliable.rb +46 -13
- data/lib/wurk/fetcher.rb +5 -0
- data/lib/wurk/health.rb +74 -22
- data/lib/wurk/history.rb +4 -21
- data/lib/wurk/job_retry.rb +3 -5
- data/lib/wurk/launcher.rb +26 -3
- data/lib/wurk/limiter/server_middleware.rb +16 -1
- data/lib/wurk/limiter.rb +6 -5
- data/lib/wurk/lua/loader.rb +11 -5
- data/lib/wurk/lua.rb +100 -9
- data/lib/wurk/manager.rb +59 -19
- data/lib/wurk/metrics/history.rb +24 -38
- data/lib/wurk/metrics/queue_rollup.rb +4 -21
- data/lib/wurk/metrics/rollup.rb +4 -21
- data/lib/wurk/processor.rb +8 -8
- data/lib/wurk/profile_set.rb +21 -6
- data/lib/wurk/profiler.rb +7 -5
- data/lib/wurk/rails_boot.rb +176 -0
- data/lib/wurk/railtie.rb +19 -47
- data/lib/wurk/redis_connection.rb +6 -9
- data/lib/wurk/redis_pool.rb +148 -28
- data/lib/wurk/scheduled.rb +35 -12
- data/lib/wurk/swarm/backoff.rb +70 -0
- data/lib/wurk/swarm/child_boot.rb +92 -13
- data/lib/wurk/swarm/orphan_guard.rb +105 -0
- data/lib/wurk/swarm/restart.rb +196 -0
- data/lib/wurk/swarm.rb +194 -78
- data/lib/wurk/timer_loop.rb +49 -0
- data/lib/wurk/version.rb +1 -1
- data/lib/wurk/web/extension.rb +4 -1
- data/lib/wurk/web/pool_scope.rb +46 -0
- data/lib/wurk/web/rack_app.rb +2 -1
- data/lib/wurk/web/search.rb +77 -18
- data/lib/wurk/web.rb +1 -0
- data/lib/wurk/worker/setter.rb +6 -1
- data/lib/wurk.rb +6 -2
- data/vendor/assets/dashboard/assets/ArgsValue-DYfBiXrJ.js +1 -0
- data/vendor/assets/dashboard/assets/BatchDetail-DTZ2HzcD.js +1 -0
- data/vendor/assets/dashboard/assets/Batches-BrnXA332.js +1 -0
- data/vendor/assets/dashboard/assets/Busy-C57G8Xb3.js +1 -0
- data/vendor/assets/dashboard/assets/Cron-DlyH88oo.js +1 -0
- data/vendor/assets/dashboard/assets/Dashboard-B887pxWf.js +1 -0
- data/vendor/assets/dashboard/assets/Dead-BPA7gs-X.js +1 -0
- data/vendor/assets/dashboard/assets/Extension-Bunf6XuU.js +1 -0
- data/vendor/assets/dashboard/assets/FilterBox-FCDi4ZCU.js +1 -0
- data/vendor/assets/dashboard/assets/JobDetailModal-DuMdKUMm.js +2 -0
- data/vendor/assets/dashboard/assets/Limiters-Br0aCPMK.js +1 -0
- data/vendor/assets/dashboard/assets/Metrics-DxBmuywH.js +1 -0
- data/vendor/assets/dashboard/assets/Modal-t4FI_LaY.js +1 -0
- data/vendor/assets/dashboard/assets/PageHeader-CsDvJSOA.js +1 -0
- data/vendor/assets/dashboard/assets/Profiles-Bkhoqjlq.js +1 -0
- data/vendor/assets/dashboard/assets/Queues-BvhA-vfI.js +1 -0
- data/vendor/assets/dashboard/assets/Retries-JEpB-1Yl.js +1 -0
- data/vendor/assets/dashboard/assets/Scheduled-DN_FbSwP.js +1 -0
- data/vendor/assets/dashboard/assets/Search-DJuK0YCJ.js +1 -0
- data/vendor/assets/dashboard/assets/Skeleton-DOYDkzg1.js +1 -0
- data/vendor/assets/dashboard/assets/charts-CVK0zAnC.js +1 -0
- data/vendor/assets/dashboard/assets/index-BnPX9Ptn.css +1 -0
- data/vendor/assets/dashboard/assets/index-CZTcs-pM.js +141 -0
- data/vendor/assets/dashboard/assets/useResetPageOnEmpty-CoZU4b3a.js +1 -0
- data/vendor/assets/dashboard/assets/useSort-wQcnbdsa.js +1 -0
- data/vendor/assets/dashboard/index.html +3 -3
- data/vendor/assets/dashboard/wurk-manifest.json +2 -2
- metadata +55 -26
- data/vendor/assets/dashboard/assets/ArgsValue-BUqJa-eG.js +0 -1
- data/vendor/assets/dashboard/assets/BatchDetail-C5dkqAzp.js +0 -1
- data/vendor/assets/dashboard/assets/Batches-bgkXn8tc.js +0 -1
- data/vendor/assets/dashboard/assets/Busy-QSHBFXhy.js +0 -1
- data/vendor/assets/dashboard/assets/Cron-CUHppvTA.js +0 -1
- data/vendor/assets/dashboard/assets/Dashboard-CzPudckV.js +0 -1
- data/vendor/assets/dashboard/assets/Dead-BIq4Nz_i.js +0 -1
- data/vendor/assets/dashboard/assets/Extension-CW36i9R1.js +0 -1
- data/vendor/assets/dashboard/assets/JobDetailModal-BhNdWSp7.js +0 -2
- data/vendor/assets/dashboard/assets/Limiters-CiI_DfUK.js +0 -1
- data/vendor/assets/dashboard/assets/Metrics-BzZ8ugms.js +0 -1
- data/vendor/assets/dashboard/assets/Modal-DzVfgsSF.js +0 -1
- data/vendor/assets/dashboard/assets/PageHeader-Dp3qhX3e.js +0 -1
- data/vendor/assets/dashboard/assets/Profiles-BTtIlTdR.js +0 -1
- data/vendor/assets/dashboard/assets/Queues-BuXoxQ4W.js +0 -1
- data/vendor/assets/dashboard/assets/Retries-D4HAPaOQ.js +0 -1
- data/vendor/assets/dashboard/assets/Scheduled-6NCZYVJh.js +0 -1
- data/vendor/assets/dashboard/assets/Search-JaB_-52c.js +0 -1
- data/vendor/assets/dashboard/assets/charts-6uvCyY0x.js +0 -1
- data/vendor/assets/dashboard/assets/i18n-gIeA5VLo.js +0 -1
- data/vendor/assets/dashboard/assets/index-BDG9tvBA.css +0 -1
- data/vendor/assets/dashboard/assets/index-DWfWAGBc.js +0 -141
- data/vendor/assets/dashboard/assets/useJobSetActions-DTDaAoWr.js +0 -1
- data/vendor/assets/dashboard/assets/usePageParam-BcAvRko-.js +0 -1
- data/vendor/assets/dashboard/assets/useSort-D5Am4bGq.js +0 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wurk
|
|
4
|
+
class Swarm
|
|
5
|
+
# Orphan protection for a swarm child. A SIGKILL'd (or crashed, or OOM'd)
|
|
6
|
+
# supervisor leaves its children with no parent — they keep fetching
|
|
7
|
+
# forever, and the next redeploy that boots a fresh supervisor then runs
|
|
8
|
+
# doubled concurrency against the same queues. This arms two independent
|
|
9
|
+
# mechanisms so a child self-terminates the moment it is orphaned:
|
|
10
|
+
#
|
|
11
|
+
# * Linux: PR_SET_PDEATHSIG delivers SIGTERM the instant the forking
|
|
12
|
+
# thread dies (kernel-level, zero latency). It is set right after the
|
|
13
|
+
# fork; the fork race — the parent can die in the window before the
|
|
14
|
+
# syscall lands, so the signal is never queued — is closed by the
|
|
15
|
+
# watchdog's immediate getppid check below.
|
|
16
|
+
# * Everywhere: a watchdog thread compares getppid against the PID the
|
|
17
|
+
# parent captured *before* forking (race-free — never the reaper PID).
|
|
18
|
+
# On its first tick, and every `interval` seconds after, a mismatch
|
|
19
|
+
# means the parent is gone. This is the only mechanism on non-Linux
|
|
20
|
+
# (JRuby, macOS dev) and a backstop for pdeathsig's per-thread caveat.
|
|
21
|
+
#
|
|
22
|
+
# Both funnel through the child's own SIGTERM handler (Process.kill self),
|
|
23
|
+
# so orphan death is an ordinary graceful drain — in-flight jobs finish and
|
|
24
|
+
# the private list is requeued — not a hard kill.
|
|
25
|
+
class OrphanGuard
|
|
26
|
+
WATCHDOG_INTERVAL = 5
|
|
27
|
+
# <linux/prctl.h>: PR_SET_PDEATHSIG is option 1.
|
|
28
|
+
PR_SET_PDEATHSIG = 1
|
|
29
|
+
|
|
30
|
+
# pdeathsig via libc/prctl is Linux-only. Off elsewhere; the watchdog
|
|
31
|
+
# thread covers those platforms on its own.
|
|
32
|
+
def self.pdeathsig_supported?
|
|
33
|
+
::RbConfig::CONFIG['host_os'].include?('linux')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# `on_orphan` is injectable so unit tests can observe the trip without
|
|
37
|
+
# actually signalling the test process. Production leaves it nil and
|
|
38
|
+
# self-TERMs, routing through the child's normal graceful-drain handler.
|
|
39
|
+
def initialize(parent_pid, logger:, interval: WATCHDOG_INTERVAL,
|
|
40
|
+
pdeathsig: pdeathsig_supported?, on_orphan: nil)
|
|
41
|
+
@parent_pid = parent_pid
|
|
42
|
+
@logger = logger
|
|
43
|
+
@interval = interval
|
|
44
|
+
@pdeathsig = pdeathsig
|
|
45
|
+
@on_orphan = on_orphan || -> { ::Process.kill('TERM', ::Process.pid) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Arm both mechanisms. Returns the watchdog thread so the caller can
|
|
49
|
+
# retain it (otherwise GC could reap it).
|
|
50
|
+
def arm
|
|
51
|
+
set_pdeathsig
|
|
52
|
+
start_watchdog
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def orphaned?
|
|
56
|
+
::Process.ppid != @parent_pid
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def pdeathsig_supported?
|
|
62
|
+
self.class.pdeathsig_supported?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# `sleep until orphaned?` checks first, so an already-orphaned child
|
|
66
|
+
# (the pdeathsig fork race) drains on the very first tick with no wait.
|
|
67
|
+
def start_watchdog
|
|
68
|
+
::Thread.new do
|
|
69
|
+
::Thread.current.name = 'wurk-orphan-watchdog'
|
|
70
|
+
::Thread.current.report_on_exception = false
|
|
71
|
+
sleep @interval until orphaned?
|
|
72
|
+
drain
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def drain
|
|
77
|
+
@logger&.warn { "swarm child #{::Process.pid}: parent #{@parent_pid} gone; draining orphan" }
|
|
78
|
+
@on_orphan.call
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
@logger&.error { "swarm child #{::Process.pid}: orphan drain failed: #{e.class}: #{e.message}" }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def set_pdeathsig
|
|
84
|
+
return unless @pdeathsig
|
|
85
|
+
|
|
86
|
+
arm_pdeathsig
|
|
87
|
+
rescue ::StandardError, ::LoadError => e
|
|
88
|
+
@logger&.debug { "swarm child #{::Process.pid}: PR_SET_PDEATHSIG unavailable (#{e.class}: #{e.message})" }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# prctl(int option, unsigned long arg2, …) — option is int, the rest are
|
|
92
|
+
# unsigned long. Fiddle opens the already-loaded libc (dlopen nil).
|
|
93
|
+
def arm_pdeathsig
|
|
94
|
+
require 'fiddle'
|
|
95
|
+
libc = ::Fiddle.dlopen(nil)
|
|
96
|
+
func = ::Fiddle::Function.new(
|
|
97
|
+
libc['prctl'],
|
|
98
|
+
[::Fiddle::TYPE_INT, ::Fiddle::TYPE_LONG, ::Fiddle::TYPE_LONG, ::Fiddle::TYPE_LONG, ::Fiddle::TYPE_LONG],
|
|
99
|
+
::Fiddle::TYPE_INT
|
|
100
|
+
)
|
|
101
|
+
func.call(PR_SET_PDEATHSIG, ::Signal.list.fetch('TERM'), 0, 0, 0)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Wurk
|
|
4
|
+
class Swarm
|
|
5
|
+
# Non-blocking rolling-restart / recycle state machine. One slot is in
|
|
6
|
+
# flight at a time; `advance` moves it a single phase per supervise tick so
|
|
7
|
+
# the supervisor never blocks on a restart and keeps honoring TERM:
|
|
8
|
+
#
|
|
9
|
+
# spawn_replacement → await_heartbeat(heartbeat_wait) → term_old
|
|
10
|
+
# → await_exit(drain_timeout) → done
|
|
11
|
+
#
|
|
12
|
+
# The reaper reports child exits via `claim_exit`, so a replacement that
|
|
13
|
+
# dies before it heartbeats is seen as dead (not "slow"): the old child is
|
|
14
|
+
# kept, a per-slot backoff applied, and the slot retried. `abort` drops
|
|
15
|
+
# everything — the swarm's TERM handler then drains the in-flight
|
|
16
|
+
# replacement + old as ordinary children.
|
|
17
|
+
#
|
|
18
|
+
# Collaborators are injected (Config) so the machine is decoupled from the
|
|
19
|
+
# swarm's fork/kill/Redis internals and unit-testable against fakes.
|
|
20
|
+
class Restart
|
|
21
|
+
Config = Struct.new(:spawn, :kill, :heartbeat, :describe, :now, :logger,
|
|
22
|
+
:heartbeat_wait, :drain_timeout, :backoff, keyword_init: true)
|
|
23
|
+
|
|
24
|
+
def initialize(config)
|
|
25
|
+
@spawn = config.spawn # ->(slot, idx) => replacement pid (registered by the swarm)
|
|
26
|
+
@kill = config.kill # ->(pid, sig)
|
|
27
|
+
@heartbeat = config.heartbeat # ->(pid) => truthy once the child has beaten
|
|
28
|
+
@describe = config.describe # ->(pid) => { slot:, index: } | nil
|
|
29
|
+
@now = config.now # -> monotonic seconds
|
|
30
|
+
@logger = config.logger
|
|
31
|
+
@heartbeat_wait = config.heartbeat_wait
|
|
32
|
+
@drain_timeout = config.drain_timeout
|
|
33
|
+
@backoff = config.backoff
|
|
34
|
+
@queue = []
|
|
35
|
+
@current = nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def idle?
|
|
39
|
+
@current.nil? && @queue.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Queue live child PIDs for restart, skipping any already queued or in
|
|
43
|
+
# flight so recycle + rolling restart can't double up on one slot.
|
|
44
|
+
def enqueue(pids)
|
|
45
|
+
pids.each do |pid|
|
|
46
|
+
next if in_flight?(pid) || @queue.include?(pid)
|
|
47
|
+
|
|
48
|
+
@queue << pid
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Reaper hook. Returns true when `pid` belonged to the in-flight restart
|
|
53
|
+
# so the swarm skips crash-respawn for it. A replacement is only claimed
|
|
54
|
+
# while awaiting its heartbeat — once it takes over the slot (await_exit),
|
|
55
|
+
# its death is an ordinary crash the swarm respawns.
|
|
56
|
+
def claim_exit(pid) # rubocop:disable Naming/PredicateMethod
|
|
57
|
+
return false unless @current
|
|
58
|
+
|
|
59
|
+
if pid == @current[:old_pid]
|
|
60
|
+
@current[:old_exited] = true
|
|
61
|
+
true
|
|
62
|
+
elsif pid == @current[:replacement] && @current[:phase] == :await_heartbeat
|
|
63
|
+
@current[:replacement_dead] = true
|
|
64
|
+
true
|
|
65
|
+
else
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def advance
|
|
71
|
+
start_next if @current.nil?
|
|
72
|
+
return if @current.nil?
|
|
73
|
+
|
|
74
|
+
case @current[:phase]
|
|
75
|
+
when :await_heartbeat then advance_await_heartbeat
|
|
76
|
+
when :await_exit then advance_await_exit
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# TERM/INT: forget queued + in-flight work. The replacement and old child
|
|
81
|
+
# are ordinary children the swarm's shutdown TERMs and drains.
|
|
82
|
+
def abort
|
|
83
|
+
@queue.clear
|
|
84
|
+
@current = nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def in_flight?(pid)
|
|
90
|
+
return false unless @current
|
|
91
|
+
|
|
92
|
+
pid == @current[:old_pid] || pid == @current[:replacement]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def start_next
|
|
96
|
+
pid = next_ready_pid
|
|
97
|
+
return unless pid
|
|
98
|
+
|
|
99
|
+
meta = @describe.call(pid)
|
|
100
|
+
replacement = spawn_replacement(pid, meta)
|
|
101
|
+
return unless replacement
|
|
102
|
+
|
|
103
|
+
@current = { old_pid: pid, index: meta[:index], replacement: replacement,
|
|
104
|
+
phase: :await_heartbeat, deadline: now + @heartbeat_wait }
|
|
105
|
+
@logger.info { "swarm: restarting slot #{meta[:index]} (old #{pid} -> new #{replacement})" }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Spawn the replacement, requeuing the slot on a fork/resource failure so
|
|
109
|
+
# the restart work item isn't lost (and the error can't escape the
|
|
110
|
+
# supervise loop). A per-slot backoff paces the retry — next_ready_pid
|
|
111
|
+
# leaves the head queued until it elapses.
|
|
112
|
+
def spawn_replacement(pid, meta)
|
|
113
|
+
@spawn.call(meta[:slot], meta[:index])
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
delay = @backoff.fail(meta[:index], lifetime: 0.0)
|
|
116
|
+
@queue.unshift(pid)
|
|
117
|
+
@logger.warn do
|
|
118
|
+
"swarm: replacement spawn for slot #{meta[:index]} failed (#{e.class}: #{e.message}); retry in #{delay}s"
|
|
119
|
+
end
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Head of the queue if its old child still exists and its retry backoff
|
|
124
|
+
# has elapsed. Drops a stale head whose old child already vanished; leaves
|
|
125
|
+
# the head queued (returns nil) while a retry backoff is pending, so a
|
|
126
|
+
# slot whose replacement won't boot doesn't cascade into the next slot.
|
|
127
|
+
def next_ready_pid
|
|
128
|
+
pid = @queue.first
|
|
129
|
+
return nil unless pid
|
|
130
|
+
|
|
131
|
+
meta = @describe.call(pid)
|
|
132
|
+
if meta.nil?
|
|
133
|
+
@queue.shift
|
|
134
|
+
return nil
|
|
135
|
+
end
|
|
136
|
+
return nil unless @backoff.ready?(meta[:index])
|
|
137
|
+
|
|
138
|
+
@queue.shift
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def advance_await_heartbeat
|
|
142
|
+
cur = @current
|
|
143
|
+
return retry_slot if cur[:replacement_dead]
|
|
144
|
+
|
|
145
|
+
seen = @heartbeat.call(cur[:replacement])
|
|
146
|
+
timed_out = now >= cur[:deadline]
|
|
147
|
+
return unless seen || timed_out
|
|
148
|
+
|
|
149
|
+
warn_heartbeat_timeout(cur) if timed_out && !seen
|
|
150
|
+
@kill.call(cur[:old_pid], 'TERM')
|
|
151
|
+
cur[:phase] = :await_exit
|
|
152
|
+
cur[:deadline] = now + @drain_timeout
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def advance_await_exit
|
|
156
|
+
cur = @current
|
|
157
|
+
return finish(cur) if cur[:old_exited]
|
|
158
|
+
return if now < cur[:deadline]
|
|
159
|
+
|
|
160
|
+
# SIGKILL an overrunning old child once, then wait for the reaper to
|
|
161
|
+
# confirm the exit before finishing — finishing early would let the
|
|
162
|
+
# later reap look like a fresh crash and spawn a surplus child.
|
|
163
|
+
return if cur[:killed]
|
|
164
|
+
|
|
165
|
+
@kill.call(cur[:old_pid], 'KILL')
|
|
166
|
+
cur[:killed] = true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def finish(cur)
|
|
170
|
+
@backoff.clear(cur[:index])
|
|
171
|
+
@current = nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def retry_slot
|
|
175
|
+
cur = @current
|
|
176
|
+
delay = @backoff.fail(cur[:index], lifetime: 0.0)
|
|
177
|
+
@queue.unshift(cur[:old_pid])
|
|
178
|
+
@logger.warn do
|
|
179
|
+
"swarm: replacement #{cur[:replacement]} for slot #{cur[:index]} died; " \
|
|
180
|
+
"keeping #{cur[:old_pid]}, retry in #{delay}s"
|
|
181
|
+
end
|
|
182
|
+
@current = nil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def warn_heartbeat_timeout(cur)
|
|
186
|
+
@logger.warn do
|
|
187
|
+
"swarm: replacement #{cur[:replacement]} heartbeat not seen in #{@heartbeat_wait}s; proceeding to TERM old"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def now
|
|
192
|
+
@now.call
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|