Rework landing example onto the `Context` API

`run_in_actor()` is slated to become a hilevel wrapper
(`runtime/_supervise.py` "TODO: DEPRECATE THIS"), so the showcase
`we_are_processes.py` shouldn't lead with it. Move it to the modern
API: each `worker_<i>` subactor runs a `@tractor.context`
`endpoint()` that `started()`-hands its name + pid back over
`Portal.open_context()` and parks; the root sleeps then raises on
purpose so the runtime reaps the whole tree (zero zombies).

The subs spawn concurrently from bg `trio.Task`s so each child's
cold `import tractor` (~0.4s, see #470) overlaps instead of
stacking; a comment flags the coming `main_thread_forkserver`
backend (#463) which'll make serial spawns cheap enough to just
loop.

Match the landing prose to the snippet — name the `Context` +
`started()` handshake it now leads with.

Also, document `--watch examples` on the `sphinx-autobuild` cmds so
edits to `literalinclude`-d example scripts (which live outside
`docs/`) live-reload too.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Gud Boi 2026-06-27 21:59:04 -04:00
parent f406a2d357
commit b16173482b
3 changed files with 72 additions and 20 deletions

View File

@ -24,8 +24,10 @@ Sixty seconds of why
you can read a ``trio`` program you can read a you can read a ``trio`` program you can read a
``tractor`` one — that's the whole pitch. ``tractor`` one — that's the whole pitch.
Spawn one actor per core, crash the root on purpose, Spawn one actor per core, open a ``Context`` into
and watch the runtime contain the blast: errors each — the child ``started()``-handshakes its name
and pid back — then crash the root on purpose and
watch the runtime contain the blast: errors
propagate, *every* child is reaped, zero zombies — propagate, *every* child is reaped, zero zombies —
guaranteed (it's a bug otherwise). guaranteed (it's a bug otherwise).

View File

@ -1,11 +1,11 @@
""" '''
Run with a process monitor from a terminal using:: Run with a process monitor from a terminal using::
$TERM -e watch -n 0.1 "pstree -a $$" \ $TERM -e watch -n 0.1 "pstree -a $$" \
& python examples/parallelism/we_are_processes.py \ & python examples/parallelism/we_are_processes.py \
&& kill $! && kill $!
""" '''
from multiprocessing import cpu_count from multiprocessing import cpu_count
import os import os
@ -13,26 +13,70 @@ import tractor
import trio import trio
async def target(): @tractor.context
print( async def endpoint(
f"Yo, i'm '{tractor.current_actor().name}' " ctx: tractor.Context,
f"running in pid {os.getpid()}" ):
) actor_name: str = tractor.current_actor().name
pid: int = os.getpid()
await ctx.started((actor_name, pid))
await trio.sleep_forever() await trio.sleep_forever()
async def spawn_and_open_ep(
an: tractor.ActorNursery,
i: int,
) -> None:
'''
Spawn a subactor, start a remote `endpoint()`-task in it.
'''
ptl: tractor.Portal = await an.start_actor(
name=f'worker_{i}',
enable_modules=[__name__],
)
ctx: tractor.Context
async with ptl.open_context(endpoint) as (
ctx,
(sub_name, sub_pid),
):
print(
f'Started ep-task in subactor,\n'
f'{i}::{sub_name!r}@{sub_pid}\n'
)
await ctx.wait_for_result()
async def main(): async def main():
'''
Spawn a subactor-per-CPU then self-destruct the cluster.
async with tractor.open_nursery() as n: '''
tn: trio.Nursery
an: tractor.ActorNursery
async with (
tractor.open_nursery(
# XXX coming soon!
# https://github.com/goodboy/tractor/pull/463
# start_method='main_thread_forkserver',
) as an,
# spawn subs concurrently (in bg `trio.Task`s) so each
# actor's cold `import tractor` (~0.4s, see #470) overlaps
# instead of stacking; once forkserver (#463) lands, spawn
# is cheap enough to just loop sequentially.
trio.open_nursery() as tn,
):
for i in range(cpu_count()): for i in range(cpu_count()):
await n.run_in_actor(target, name=f'worker_{i}') tn.start_soon(
spawn_and_open_ep,
print('This process tree will self-destruct in 1 sec...') an,
await trio.sleep(1) i,
)
# you could have done this yourself destruct_in: int = 2
print(
f'This tree will self-destruct in {destruct_in}s..\n'
)
await trio.sleep(destruct_in)
raise Exception('Self Destructed') raise Exception('Self Destructed')

View File

@ -38,10 +38,15 @@ Rebuilds + refreshes the browser on every save:
``` ```
nix develop .#docs -c uv run --with sphinx-autobuild \ nix develop .#docs -c uv run --with sphinx-autobuild \
--group docs sphinx-autobuild docs docs/_build/html --group docs sphinx-autobuild docs docs/_build/html \
--watch examples
# then open http://127.0.0.1:8000 # then open http://127.0.0.1:8000
``` ```
The `--watch examples` is what makes edits to ``literalinclude``-d
example scripts live-reload too: those files live *outside* `docs/`,
so autobuild won't notice them changing without it.
## Share it on your LAN ## Share it on your LAN
To let someone on your subnet view the docs, bind the server to To let someone on your subnet view the docs, bind the server to
@ -52,7 +57,8 @@ Live-reload, LAN-visible:
``` ```
nix develop .#docs -c uv run --with sphinx-autobuild --group docs \ nix develop .#docs -c uv run --with sphinx-autobuild --group docs \
sphinx-autobuild docs docs/_build/html --host 0.0.0.0 --port 8000 sphinx-autobuild docs docs/_build/html --host 0.0.0.0 --port 8000 \
--watch examples
``` ```
Or just statically serve an already-built `docs/_build/html` (no Or just statically serve an already-built `docs/_build/html` (no