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
parent
f406a2d357
commit
b16173482b
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue