Class: Wurk::Swarm::OrphanGuard

Inherits:
Object
  • Object
show all
Defined in:
lib/wurk/swarm/orphan_guard.rb

Overview

Orphan protection for a swarm child. A SIGKILL'd (or crashed, or OOM'd) supervisor leaves its children with no parent — they keep fetching forever, and the next redeploy that boots a fresh supervisor then runs doubled concurrency against the same queues. This arms two independent mechanisms so a child self-terminates the moment it is orphaned:

* Linux: PR_SET_PDEATHSIG delivers SIGTERM the instant the forking
thread dies (kernel-level, zero latency). It is set right after the
fork; the fork race — the parent can die in the window before the
syscall lands, so the signal is never queued — is closed by the
watchdog's immediate getppid check below.
* Everywhere: a watchdog thread compares getppid against the PID the
parent captured *before* forking (race-free — never the reaper PID).
On its first tick, and every `interval` seconds after, a mismatch
means the parent is gone. This is the only mechanism on non-Linux
(JRuby, macOS dev) and a backstop for pdeathsig's per-thread caveat.

Both funnel through the child's own SIGTERM handler (Process.kill self), so orphan death is an ordinary graceful drain — in-flight jobs finish and the private list is requeued — not a hard kill.

Constant Summary collapse

WATCHDOG_INTERVAL =
5
PR_SET_PDEATHSIG =

<linux/prctl.h>: PR_SET_PDEATHSIG is option 1.

1

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent_pid, logger:, interval: WATCHDOG_INTERVAL, pdeathsig: pdeathsig_supported?, , on_orphan: nil) ⇒ OrphanGuard

on_orphan is injectable so unit tests can observe the trip without actually signalling the test process. Production leaves it nil and self-TERMs, routing through the child's normal graceful-drain handler.



39
40
41
42
43
44
45
46
# File 'lib/wurk/swarm/orphan_guard.rb', line 39

def initialize(parent_pid, logger:, interval: WATCHDOG_INTERVAL,
               pdeathsig: pdeathsig_supported?, on_orphan: nil)
  @parent_pid = parent_pid
  @logger = logger
  @interval = interval
  @pdeathsig = pdeathsig
  @on_orphan = on_orphan || -> { ::Process.kill('TERM', ::Process.pid) }
end

Class Method Details

.pdeathsig_supported?Boolean

pdeathsig via libc/prctl is Linux-only. Off elsewhere; the watchdog thread covers those platforms on its own.

Returns:

  • (Boolean)


32
33
34
# File 'lib/wurk/swarm/orphan_guard.rb', line 32

def self.pdeathsig_supported?
  ::RbConfig::CONFIG['host_os'].include?('linux')
end

Instance Method Details

#armObject

Arm both mechanisms. Returns the watchdog thread so the caller can retain it (otherwise GC could reap it).



50
51
52
53
# File 'lib/wurk/swarm/orphan_guard.rb', line 50

def arm
  set_pdeathsig
  start_watchdog
end

#orphaned?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/wurk/swarm/orphan_guard.rb', line 55

def orphaned?
  ::Process.ppid != @parent_pid
end