From f607c0fc32b1a3454c47125ef6205ec04e99c664 Mon Sep 17 00:00:00 2001 From: SS-42 Date: Thu, 2 Jul 2026 21:32:28 +0300 Subject: [PATCH] fix(main): exit promptly after parent death Signed-off-by: SS-42 --- src/main.c | 9 ++++++--- tests/test_parent_watchdog.sh | 25 ++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index a58acdbbd..55b499cfe 100644 --- a/src/main.c +++ b/src/main.c @@ -50,6 +50,9 @@ enum { #include #include #include +#ifndef _WIN32 +#include +#endif #ifndef CBM_VERSION #define CBM_VERSION "dev" @@ -120,9 +123,9 @@ static void *parent_watchdog_thread(void *arg) { /* initial_ppid > 1 guards against an already-orphaned start (ppid==1), * where a changing ppid carries no signal. */ if (initial_ppid > 1 && getppid() != initial_ppid) { - cbm_log_warn("parent.exited", "reason", "ppid_changed"); - request_shutdown(); - exit(0); + static const char msg[] = "level=warn msg=parent.exited reason=ppid_changed\n"; + (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); + _exit(0); } } return NULL; diff --git a/tests/test_parent_watchdog.sh b/tests/test_parent_watchdog.sh index aa1eb0070..51181a14b 100755 --- a/tests/test_parent_watchdog.sh +++ b/tests/test_parent_watchdog.sh @@ -73,16 +73,39 @@ if ! kill -0 "${child_pid}" 2>/dev/null; then exit 3 fi +# Wait until the child has reached startup far enough to have installed the +# parent watchdog. The PID file is written immediately after fork; killing the +# wrapper before the child runs main() is an untestable early-reparent race where +# the child never observed the original parent PID. +for _ in {1..50}; do + if [[ -s "${tmpdir}/child.err" ]] && grep -q "mem.init" "${tmpdir}/child.err"; then + break + fi + sleep 0.1 +done +if ! grep -q "mem.init" "${tmpdir}/child.err" 2>/dev/null; then + echo "child did not reach watchdog-ready startup point" >&2 + [[ -s "${tmpdir}/child.err" ]] && cat "${tmpdir}/child.err" >&2 + exit 3 +fi + # Kill the wrapper parent: the orphaned child must now self-exit. kill -9 "${wrapper_pid}" wait "${wrapper_pid}" 2>/dev/null || true -deadline=$((SECONDS + 6)) +deadline=$((SECONDS + 15)) while (( SECONDS < deadline )); do if ! kill -0 "${child_pid}" 2>/dev/null; then echo "ok: child ${child_pid} exited after parent death" exit 0 fi + # A zombie no longer holds stdin or runs the MCP loop; kill -0 still reports + # it until launchd/test parent reaps it, so treat that as a successful exit. + child_state="$(ps -p "${child_pid}" -o stat= 2>/dev/null | tr -d '[:space:]' || true)" + if [[ "${child_state}" == Z* ]]; then + echo "ok: child ${child_pid} exited after parent death (zombie awaiting reap)" + exit 0 + fi sleep 0.2 done