diff --git a/docs/index.rst b/docs/index.rst index 80f5d41d..fa77730e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,8 +24,10 @@ Sixty seconds of why you can read a ``trio`` program you can read a ``tractor`` one — that's the whole pitch. -Spawn one actor per core, crash the root on purpose, -and watch the runtime contain the blast: errors +Spawn one actor per core, open a ``Context`` into +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 — guaranteed (it's a bug otherwise). diff --git a/examples/parallelism/we_are_processes.py b/examples/parallelism/we_are_processes.py index 755b89cf..cce503b6 100644 --- a/examples/parallelism/we_are_processes.py +++ b/examples/parallelism/we_are_processes.py @@ -1,11 +1,11 @@ -""" +''' Run with a process monitor from a terminal using:: $TERM -e watch -n 0.1 "pstree -a $$" \ & python examples/parallelism/we_are_processes.py \ && kill $! -""" +''' from multiprocessing import cpu_count import os @@ -13,26 +13,70 @@ import tractor import trio -async def target(): - print( - f"Yo, i'm '{tractor.current_actor().name}' " - f"running in pid {os.getpid()}" - ) - +@tractor.context +async def endpoint( + ctx: tractor.Context, +): + actor_name: str = tractor.current_actor().name + pid: int = os.getpid() + await ctx.started((actor_name, pid)) 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(): + ''' + 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()): - await n.run_in_actor(target, name=f'worker_{i}') - - print('This process tree will self-destruct in 1 sec...') - await trio.sleep(1) - - # you could have done this yourself + tn.start_soon( + spawn_and_open_ep, + an, + i, + ) + 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') diff --git a/notes_to_self/howtodocs.md b/notes_to_self/howtodocs.md index 6c2f47a0..10f1b946 100644 --- a/notes_to_self/howtodocs.md +++ b/notes_to_self/howtodocs.md @@ -38,10 +38,15 @@ Rebuilds + refreshes the browser on every save: ``` 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 ``` +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 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 \ - 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