Skip to content

Fix: shutdown never confirms process actually died before removing it from tracking (#50) - #84

Open
agu2347 wants to merge 1 commit into
BYUCamachoLab:masterfrom
agu2347:fix-shutdown-confirm-process-died
Open

agu2347 wants to merge 1 commit into
BYUCamachoLab:masterfrom
agu2347:fix-shutdown-confirm-process-died

Conversation

@agu2347

@agu2347 agu2347 commented Sep 16, 2026

Copy link
Copy Markdown

Fixes #50.

Root cause

ProcessManager.shutdown_nameserver() and shutdown_daemon() sent the graceful None KILL sentinel via the message queue, waited a single fixed join(timeout), and then unconditionally popped the process from self.nameservers/self.daemons and returned True — regardless of whether the underlying OS process had actually exited.

A process wedged in a blocking call (e.g. a hardware driver read that never polls its message queue, which is exactly the scenario NameServerRunner/DaemonRunner are used for) would be silently orphaned: still holding its port or hardware handle, but reported to callers (and to the periodic checkup()) as successfully shut down.

Fix

shutdown_nameserver/shutdown_daemon now escalate the same way multiprocessing's own docs recommend:

  1. join(2 * msg_polling) after sending the sentinel
  2. if still alive: terminate(), then join(5)
  3. if still alive: kill(), then join()
  4. check is_alive() one final time

The entry is only removed from tracking (via .pop(key, None)) once the process is confirmed dead or escalation to SIGKILL is exhausted, and the return value now reflects whether the process was actually confirmed dead rather than being hardcoded to True.

Testing

  • Added tests/test_manager.py with 4 regression tests covering responsive/wedged process × nameserver/daemon shutdown paths, using fake multiprocessing.Process subclasses:
  • All 4 new tests pass locally.
  • tests/test_manager.py is the only active test suite in this repo currently (the other files under tests/ are empty or commented-out legacy files, and tests/drivers/** requires hardware SDKs and is correctly excluded via the pytest config), so there's no broader regression surface to check.
  • Verified via git stash that all 4 new tests fail (the wedged-process tests hang/timeout, and the responsive-process assertions about actual process death do not hold) against the pre-fix code, and pass cleanly against this fix.
  • Both changed files pass black --check --diff (v23.3.0, the version pinned in .pre-commit-config.yaml).

AI assistance disclosure

This fix (root-cause analysis, the escalating join/terminate/kill implementation, and the regression tests) was written with the assistance of an AI coding agent (Claude), and reviewed and submitted by me. Happy to make any changes the maintainers would like.

Signed-off-by: agu2347 94227848+agu2347@users.noreply.github.com

…before removing from tracking (BYUCamachoLab#50)

Root cause: ProcessManager.shutdown_nameserver() and shutdown_daemon() sent
the graceful KILL sentinel via the message queue, waited a single fixed
join(timeout), and then unconditionally popped the process from
self.nameservers/self.daemons and returned True, regardless of whether the
underlying OS process had actually exited. A process wedged in a blocking
call (e.g. a hardware driver read that never polls its message queue) would
be silently orphaned: still holding its port/hardware handle, but reported
to callers (and to checkup()) as successfully shut down.

Fix: escalate the shutdown the way multiprocessing's own docs recommend -
join(2*polling) -> if still alive, terminate() -> join(5) -> if still alive,
kill() -> join() - checking is_alive() at each step, and only removing the
entry from tracking (via pop()) once the process is confirmed dead (or
escalation to SIGKILL is exhausted). The return value now reflects whether
the process was actually confirmed dead.

Testing:
- Added tests/test_manager.py with 4 regression tests covering
  responsive/wedged process x nameserver/daemon shutdown paths, using fake
  multiprocessing.Process subclasses (_RespondingProcess polls its queue and
  exits promptly; _WedgedProcess never polls and can only be stopped by
  terminate()/kill()).
- All 4 new tests pass. tests/test_manager.py is the only active test suite
  in this repo (other files under tests/ are empty or commented-out legacy
  files; tests/drivers/** requires hardware SDKs and is correctly excluded
  via pytest config), confirming no regressions elsewhere.
- Verified via git stash that all 4 tests fail/hang against the pre-fix
  code and pass cleanly against the fix.
- Both changed files pass `black --check --diff` (v23.3.0, the version
  pinned in .pre-commit-config.yaml).

AI assistance disclosure: This fix (root-cause analysis, the escalating
join/terminate/kill implementation, and the regression tests) was written
with the assistance of an AI coding agent (Claude), reviewed and submitted
by me.

Signed-off-by: agu2347 <94227848+agu2347@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shutdown never confirms the process died, and leaks it if it did not

1 participant