From b88423e4a1484988c4daa6e2ac2029ef4e69015e Mon Sep 17 00:00:00 2001 From: goodboy Date: Thu, 2 Jul 2026 22:16:08 -0400 Subject: [PATCH] Make sub-spawn strategy toggleable in `we_are_processes` The #470 boot-latency example hard-coded spawning each `worker_` subactor concurrently from a bg `trio.Task` (so each child's cold `import tractor` overlaps). Add a `main()` `spawn_subs_in_bg_tasks` flag so the serial-spawn path can be demo'd/compared too: flip it `False` to `start_actor()` each sub inline in the loop before handing the ready `Portal` to the bg task. Deats, - factor an `open_ep(ptl, i)` helper out of `spawn_and_open_ep()` - just the `Portal.open_context()` + `wait_for_result()` half, now that the spawn step is caller-optional. - `spawn_and_open_ep()` grows a `maybe_ptl: Portal|None = None` param: spawn the subactor itself when unset (bg-task path), OW reuse the pre-spawned one (serial path). - move the "overlap cold imports" rationale comment onto the new `main()` param where the toggle now lives. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code --- examples/parallelism/we_are_processes.py | 52 +++++++++++++++++------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/examples/parallelism/we_are_processes.py b/examples/parallelism/we_are_processes.py index cce503b6..45485512 100644 --- a/examples/parallelism/we_are_processes.py +++ b/examples/parallelism/we_are_processes.py @@ -23,18 +23,10 @@ async def endpoint( await trio.sleep_forever() -async def spawn_and_open_ep( - an: tractor.ActorNursery, +async def open_ep( + ptl: tractor.Portal, 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, @@ -47,7 +39,33 @@ async def spawn_and_open_ep( await ctx.wait_for_result() -async def main(): +async def spawn_and_open_ep( + an: tractor.ActorNursery, + i: int, + maybe_ptl: tractor.Portal|None = None, +) -> None: + ''' + Spawn a subactor, start a remote `endpoint()`-task in it. + + ''' + if maybe_ptl is None: + maybe_ptl: tractor.Portal = await an.start_actor( + name=f'worker_{i}', + enable_modules=[__name__], + ) + await open_ep( + ptl=maybe_ptl, + i=i, + ) + + +async def main( + # 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. + spawn_subs_in_bg_tasks: bool = True, +): ''' Spawn a subactor-per-CPU then self-destruct the cluster. @@ -60,17 +78,21 @@ async def main(): # 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()): + + maybe_ptl: tractor.Portal|None = None + if not spawn_subs_in_bg_tasks: + maybe_ptl: tractor.Portal = await an.start_actor( + name=f'worker_{i}', + enable_modules=[__name__], + ) tn.start_soon( spawn_and_open_ep, an, i, + maybe_ptl, ) destruct_in: int = 2 print(