diff --git a/docs/guide/cancellation.rst b/docs/guide/cancellation.rst index 3b97b380..bd749405 100644 --- a/docs/guide/cancellation.rst +++ b/docs/guide/cancellation.rst @@ -230,22 +230,23 @@ Graceful first, hard as a last resort The hard-kill path is *skipped* whenever an actor in the tree holds the debug-REPL lock (``debug_mode=True`` flavors): - SIGTERM raining down on a tree mid-``pdb`` session would + Process signals raining down on a tree mid-``pdb`` session would clobber your prompt. See :doc:`/guide/debugging`. -Every process teardown in ``tractor`` walks the same escalation -ladder, top rung first, +Owned-child teardown in ``tractor`` begins with the same graceful +steps, then selects the escalation path used by its supervisor, 1. **graceful cancel request**: a runtime-cancel msg over IPC; the target actor cancels its tasks, closes its channels and exits its :func:`trio.run` cleanly, 2. **soft wait**: the parent waits (bounded) for the child process to exit on its own, -3. **SIGTERM**: no ack within the bounded wait (internally an - ``ActorTooSlowError``) escalates to ``proc.terminate()``, -4. **SIGKILL ultimatum**: still alive after the hard-kill timeout - (~1.6s)? The runtime logs that the "T-800" has been deployed to - collect the zombie and issues ``proc.kill()``. No survivors. +3. **actor-nursery hard reap**: no cancel ack within the bounded wait + (internally an ``ActorTooSlowError``) escalates directly to + ``proc.kill()`` before the child monitor joins the process, +4. **legacy soft-kill path**: older teardown callers may first issue + ``proc.terminate()`` and then deploy the "T-800" ``proc.kill()`` + ultimatum if the process survives that additional bounded wait. The result is the **no-zombies guarantee**: ``tractor`` tries to protect you from zombies, no matter what. Quoting the project diff --git a/tests/discovery/test_multi_program.py b/tests/discovery/test_multi_program.py index 1030de6e..f4b31ef8 100644 --- a/tests/discovery/test_multi_program.py +++ b/tests/discovery/test_multi_program.py @@ -207,7 +207,7 @@ def test_dup_name_cancel_cascade_escalates_to_hard_kill( Post-fix, `Portal.cancel_actor()` raises `ActorTooSlowError` on the bounded-wait timeout, and `ActorNursery.cancel()`'s - per-child wrapper escalates to `proc.terminate()` (hard-kill). + per-child wrapper escalates directly to `proc.kill()` (hard-reap). The full nursery teardown therefore stays bounded even under pathological timing. @@ -266,7 +266,7 @@ def test_dup_name_cancel_cascade_escalates_to_hard_kill( # post-teardown sanity: every child proc must be reaped. # If escalation worked, even timed-out cancel-RPCs would - # have triggered `proc.terminate()` and the procs are dead. + # have triggered `proc.kill()` and the procs are dead. for p in portals: # `Portal.channel.connected()` -> False once the # underlying chan disconnected (clean exit OR diff --git a/tractor/runtime/_portal.py b/tractor/runtime/_portal.py index fffaf98b..7b4c8ff0 100644 --- a/tractor/runtime/_portal.py +++ b/tractor/runtime/_portal.py @@ -292,7 +292,7 @@ class Portal: - `True`: on bounded-wait expiry, raise `ActorTooSlowError` so the caller MUST handle the failure explicitly. `ActorNursery.cancel()` opts in so it can escalate via - `proc.terminate()` per SC-discipline. + direct `proc.kill()` hard-reaping per SC-discipline. ''' __runtimeframe__: int = 1 # noqa