--- id: "bug-1575" status: "fixed" status_updated: "2026-09-24" status_note: "" component: "unknown" severity: "unknown" timestamp: "2026-08-18T03:15:00Z" last_seen: "2026-09-19T09:00:00Z" occurrences: 1 files: [".claude/skills/xce-test-resources/run-resource-verify.sh"] tags: ["trap", "cleanup", "pkill", "SIGTERM", "signal-handler", "race-condition", "run-resource-verify.sh", "resource-value-verification", "bash"] related: ["bug-1436"] legacy: true --- # bug-1575: run-resource-verify.sh's trap-based cleanup() (EXIT/INT/TERM) non-deterministically left the… ## Description run-resource-verify.sh's trap-based cleanup() (EXIT/INT/TERM) non-deterministically left the scheduler and/or apiserver process running after the script was interrupted mid-deployment via an external SIGTERM -- confirmed processes still alive (e.g. `xce_scheduler --peers ... --logfile .../scheduler.log`, `/tmp/apiserver -d .../apiserver`) seconds after the orchestrator's own `wait` returned with exit code 143. The SAME cleanup() code, reached via a normal fail()/exit-1 path (no external signal involved) inside the same single foreground invocation, worked perfectly every time (verified zero leftover processes). ## Environment _Not recorded (migrated from buglog.json)._ ## Reproduction _Not recorded (migrated from buglog.json)._ ## Root cause cleanup() issued four SEPARATE sequential `pkill -9 -f -- ""` invocations (one per component: controller/worker/scheduler/apiserver), each forking+execing its own `pkill` subprocess. When cleanup() ran as a normal function call/return within the script's own top-to-bottom control flow (e.g. from fail()), all four completed reliably. When cleanup() instead ran from on_exit as the EXIT trap fired by an asynchronously-delivered SIGTERM (via a separate `trap 'exit 143' TERM` translating the signal into an explicit exit), the sequence of pkill forks was intermittently and non-deterministically cut short partway through (confirmed via added debug instrumentation: one run stopped after the 1st pkill, another after the 3rd, another after the 2nd -- no fixed failure point, consistent with a timing race rather than a deterministic pattern-matching bug). Root cause not fully isolated at the syscall level (ruled out: pattern-matching correctness -- each individual pkill pattern was independently verified correct via direct pgrep; ruled out: cgroup pids/resource exhaustion -- process count stayed low, ~20-25 total throughout). Most likely a bash/signal-trap interaction where starting new child processes (fork/exec for each pkill) from within a trap handler invoked asynchronously (as opposed to synchronous top-level exit) has some window of unreliability in this environment. ## Fix Rewrote cleanup() to (1) combine all four per-component patterns into ONE `pkill -9 -f` call using extended-regex alternation (`"pat1|pat2|pat3|pat4"`) instead of four separate invocations -- fewer fork/exec round trips through the trap-handler code path -- and (2) wrap it in a retry-until-verified loop (`pgrep -f` check, up to 10 attempts at 0.2s intervals) so a slow/racy single kill attempt can no longer silently leave a process behind. Verified reliable across 5+ repeated live runs deliberately interrupted via external SIGTERM at different points mid-deployment (right after controller-only confirmation, and after controller+scheduler confirmation) -- zero leftover processes every time after the fix, versus non-deterministic leftovers before it. ## Verification _Not recorded (migrated from buglog.json)._ ## References - Related: bug-1436