fix(delegate): guard heartbeat join against unstarted thread

Pairs with the prior commit (start() now inside the try block).  If
threading.Thread.start() itself raises (OS thread exhaustion under
heavy delegation fanout), the finally would call .join() on a
never-started thread, which raises RuntimeError("cannot join thread
before it is started") — trading one rare bug for another.

Thread.ident is None until start() succeeds, so gate the join on it.
This commit is contained in:
teknium1 2026-05-15 15:01:09 -07:00 committed by Teknium
parent 2d7182f72c
commit 6068363311

View file

@ -1836,9 +1836,13 @@ def _run_single_child(
finally:
# Stop the heartbeat thread so it doesn't keep touching parent activity
# after the child has finished (or failed).
# after the child has finished (or failed). Guard the join: .start()
# now lives inside the try block, so if it raised (OS thread
# exhaustion) the thread was never started and Thread.join() would
# raise RuntimeError. ident is None until start() succeeds.
_heartbeat_stop.set()
_heartbeat_thread.join(timeout=5)
if _heartbeat_thread.ident is not None:
_heartbeat_thread.join(timeout=5)
# Drop the TUI-facing registry entry. Safe to call even if the
# child was never registered (e.g. ID missing on test doubles).