Compare commits

..

No commits in common. "f136063239da97626793bf97bdc71fe3da380087" and "98bc642e72f72eb55fc4cd9e0bc9d2acda454dab" have entirely different histories.

9 changed files with 17 additions and 24 deletions

View File

@ -230,23 +230,22 @@ Graceful first, hard as a last resort
The hard-kill path is *skipped* whenever an actor in the tree The hard-kill path is *skipped* whenever an actor in the tree
holds the debug-REPL lock (``debug_mode=True`` flavors): holds the debug-REPL lock (``debug_mode=True`` flavors):
Process signals raining down on a tree mid-``pdb`` session would SIGTERM raining down on a tree mid-``pdb`` session would
clobber your prompt. See :doc:`/guide/debugging`. clobber your prompt. See :doc:`/guide/debugging`.
Owned-child teardown in ``tractor`` begins with the same graceful Every process teardown in ``tractor`` walks the same escalation
steps, then selects the escalation path used by its supervisor, ladder, top rung first,
1. **graceful cancel request**: a runtime-cancel msg over IPC; the 1. **graceful cancel request**: a runtime-cancel msg over IPC; the
target actor cancels its tasks, closes its channels and exits target actor cancels its tasks, closes its channels and exits
its :func:`trio.run` cleanly, its :func:`trio.run` cleanly,
2. **soft wait**: the parent waits (bounded) for the child process 2. **soft wait**: the parent waits (bounded) for the child process
to exit on its own, to exit on its own,
3. **actor-nursery hard reap**: no cancel ack within the bounded wait 3. **SIGTERM**: no ack within the bounded wait (internally an
(internally an ``ActorTooSlowError``) escalates directly to ``ActorTooSlowError``) escalates to ``proc.terminate()``,
``proc.kill()`` before the child monitor joins the process, 4. **SIGKILL ultimatum**: still alive after the hard-kill timeout
4. **legacy soft-kill path**: older teardown callers may first issue (~1.6s)? The runtime logs that the "T-800" has been deployed to
``proc.terminate()`` and then deploy the "T-800" ``proc.kill()`` collect the zombie and issues ``proc.kill()``. No survivors.
ultimatum if the process survives that additional bounded wait.
The result is the **no-zombies guarantee**: ``tractor`` tries to The result is the **no-zombies guarantee**: ``tractor`` tries to
protect you from zombies, no matter what. Quoting the project protect you from zombies, no matter what. Quoting the project

View File

@ -100,7 +100,7 @@ Semantics worth knowing:
- it blocks until the remote task returns, re-raising any - it blocks until the remote task returns, re-raising any
remote error in the usual boxed form right in the calling remote error in the usual boxed form right in the calling
task. task.
- lifetime mode also determines process ownership: ``an=`` spawns and - placement also determines process ownership: ``an=`` spawns and
reaps a fresh child in an existing actor nursery, while passing reaps a fresh child in an existing actor nursery, while passing
neither does the same in a private call-scoped nursery (booting neither does the same in a private call-scoped nursery (booting
the runtime if needed). ``portal=`` instead runs one linked task the runtime if needed). ``portal=`` instead runs one linked task
@ -108,7 +108,7 @@ Semantics worth knowing:
the portal's owner remains responsible for its lifetime. the portal's owner remains responsible for its lifetime.
- concurrency composes the plain ``trio`` way: schedule - concurrency composes the plain ``trio`` way: schedule
multiple ``run()`` calls into a local task nursery (see multiple ``run()`` calls into a local task nursery (see
``examples/parallelism/concurrent_toactor_primes.py``). ``examples/parallelism/to_actor_one_shots.py``).
A reused actor must expose both the target module and the A reused actor must expose both the target module and the
``to_actor`` context trampoline: ``to_actor`` context trampoline:

View File

@ -35,12 +35,8 @@ async def main():
for name in ('donny', 'gretchen') for name in ('donny', 'gretchen')
} }
async def run_and_print( async def run_and_print(name: str, other_actor: str):
name: str,
other_actor: str,
) -> None:
print( print(
# RPC through an existing actor's `Portal`.
await portals[name].run( await portals[name].run(
say_hello, say_hello,
other_actor=other_actor, other_actor=other_actor,

View File

@ -37,8 +37,6 @@ async def spawn_until(depth=0):
) )
) )
# Let the background one-shot enter `breakpoint_forever()`
# before its sibling raises and cancellation propagates.
await trio.sleep(0.5) await trio.sleep(0.5)
# rx and propagate error from child # rx and propagate error from child
await tractor.to_actor.run( await tractor.to_actor.run(

View File

@ -44,7 +44,7 @@ async def main():
async with ( async with (
tractor.open_nursery( tractor.open_nursery(
debug_mode=True, debug_mode=True,
enable_transports=['uds'], # TODO, pass this via osenv? enable_transports=['uds'], # TODO, apss this via osenv?
loglevel='devx', # XXX, required for test! loglevel='devx', # XXX, required for test!
) as an, ) as an,
trio.open_nursery() as tn, trio.open_nursery() as tn,

View File

@ -31,7 +31,7 @@ async def main():
tn.start_soon(burn_cpu) tn.start_soon(burn_cpu)
# run the same func as the lone task in a subactor, # run the same func as the lone task in a subactor,
# block on and collect its PID as the caller-side result # block on (and collect) its result
pid = await tractor.to_actor.run(burn_cpu) pid = await tractor.to_actor.run(burn_cpu)
print(f"Collected subproc {pid}") print(f"Collected subproc {pid}")

View File

@ -1,5 +1,5 @@
''' '''
`tractor.to_actor.run()`: concurrent one-shot prime checks `tractor.to_actor.run()`: one-shot single-task subactor
invocation, the SC-parallelism sibling of invocation, the SC-parallelism sibling of
`trio.to_thread.run_sync()` (and `anyio.to_process`). `trio.to_thread.run_sync()` (and `anyio.to_process`).

View File

@ -207,7 +207,7 @@ def test_dup_name_cancel_cascade_escalates_to_hard_kill(
Post-fix, `Portal.cancel_actor()` raises `ActorTooSlowError` on Post-fix, `Portal.cancel_actor()` raises `ActorTooSlowError` on
the bounded-wait timeout, and `ActorNursery.cancel()`'s the bounded-wait timeout, and `ActorNursery.cancel()`'s
per-child wrapper escalates directly to `proc.kill()` (hard-reap). per-child wrapper escalates to `proc.terminate()` (hard-kill).
The full nursery teardown therefore stays bounded even under The full nursery teardown therefore stays bounded even under
pathological timing. 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. # post-teardown sanity: every child proc must be reaped.
# If escalation worked, even timed-out cancel-RPCs would # If escalation worked, even timed-out cancel-RPCs would
# have triggered `proc.kill()` and the procs are dead. # have triggered `proc.terminate()` and the procs are dead.
for p in portals: for p in portals:
# `Portal.channel.connected()` -> False once the # `Portal.channel.connected()` -> False once the
# underlying chan disconnected (clean exit OR # underlying chan disconnected (clean exit OR

View File

@ -292,7 +292,7 @@ class Portal:
- `True`: on bounded-wait expiry, raise `ActorTooSlowError` - `True`: on bounded-wait expiry, raise `ActorTooSlowError`
so the caller MUST handle the failure explicitly. so the caller MUST handle the failure explicitly.
`ActorNursery.cancel()` opts in so it can escalate via `ActorNursery.cancel()` opts in so it can escalate via
direct `proc.kill()` hard-reaping per SC-discipline. `proc.terminate()` per SC-discipline.
''' '''
__runtimeframe__: int = 1 # noqa __runtimeframe__: int = 1 # noqa