Gralkor.OrphanReaper (gralkor_ex v2.0.5)

Copy Markdown View Source

Pre-OTP cleanup for a stale uvicorn left bound to Gralkor's port.

Intended to run before the OTP supervision tree starts — specifically before Gralkor.Server's boot sequence, whose :port_in_use check refuses to clean up foreign holders and crashes.

Rationale: a BEAM abort (Ctrl+C → a, SIGKILL, crash) doesn't reliably run Gralkor.Server's graceful-shutdown path, which is the only path that SIGTERMs the uvicorn OS pid. So aborts sometimes leave uvicorn orphaned (reparented to launchd) with port 4000 still bound. The reaper looks for such an orphan, verifies it is ours (command line contains every one of @identifiers — the invariant shape of the uvicorn invocation that Gralkor.Server spawns, regardless of mix layout or symlinked priv paths), and SIGKILLs it. If the holder is anything else, the reaper raises — we don't kill foreign processes.

Path-based identification was tried first and dropped: under path deps, mix symlinks the priv dir, and ps reports the resolved physical path while :code.priv_dir(:gralkor_ex) returns the symlink — substring match fails on the same directory. Command-line identifiers are layout-independent.

System.cmd/3 is injectable via opts[:shell] so the logic is unit-testable without side effects.

Summary

Types

shell()

@type shell() :: (String.t(), [String.t()], keyword() -> {String.t(), integer()})

Functions

reap(opts \\ [])

@spec reap(keyword()) :: :ok | no_return()