Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ enum {
#include <string.h>
#include <signal.h>
#include <stdatomic.h>
#ifndef _WIN32
#include <unistd.h>
#endif

#ifndef CBM_VERSION
#define CBM_VERSION "dev"
Expand Down Expand Up @@ -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;
Expand Down
25 changes: 24 additions & 1 deletion tests/test_parent_watchdog.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading