Drop run_in_actor() + the ria reap-cluster#484
Open
goodboy wants to merge 28 commits into
Open
Conversation
Split from the step-A code commit to keep the runtime diff free of `ai/` meta noise, - `ai/conc-anal/ria_nursery_removal_plan.md`: agent-verified machinery map + 3-step (A/B/C) design + probe history (reap-relocation deadlock -> sequencing fix -> bound tighten) + risk register for the `._ria_nursery` excision. - `ai/prompt-io/claude/20260702T165806Z_a34aaf98_*`: NLNet provenance (log + unedited raw) for the step-A work. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Step A of the `._ria_nursery` removal (issue #477 follow-up, see `ai/conc-anal/ria_nursery_removal_plan.md`): `.run_in_actor()` children now spawn via the default daemon nursery and their result-reaping moves up into the `ActorNursery` machinery, - new `_supervise._reap_ria_portals()`: one `_spawn.cancel_on_completion()` task per ria child, run AFTER `._join_procs` is set — replacing the per-child reaper task the backends formerly spawned (keyed off `._cancel_after_result_on_exit` membership) which required routing such children into `._ria_nursery`. - happy path: reap awaited right after `._join_procs.set()`, preserving "collect ria results before daemon join" sequencing. - error path: snapshot ria `(portal, subactor)` pairs (backend `finally`s pop `._children` as procs reap), `await an.cancel()`, THEN a 0.5s-bounded reap over the snapshot; anything collectable is already queued in the local ctx and a parked reaper self-cleans (`trio.Cancelled` results are never stashed). NB: a concurrent reap+cancel variant deadlocked `test_multierror` and a 3s bound blew `test_cancel_while_childs_child_in_sync_sleep`'s deadline — deats in the plan doc's probe history. - `spawn/_trio.py` + `spawn/_mp.py`: drop the membership branch, per-child reaper nursery + now-unused `cancel_on_completion` imports; the join phase is a bare `soft_kill()`. `._ria_nursery` is now vestigial (zero spawn users): step B deletes it + `start_actor()`'s `nursery=` escape hatch and merges the supervisor's two error handlers. Prompt-IO: ai/prompt-io/claude/20260702T165806Z_a34aaf98_prompt_io.md (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Split from the step-B code commit to keep the runtime diff free of `ai/` meta noise, - `ai/conc-anal/ria_nursery_removal_plan.md`: add a "Step-B outcome" section — the empty-nursery collapse, why it's behavior-preserving, the deliberate handler-merge deferral, and the targeted-gate result. - `ai/prompt-io/claude/20260702T172233Z_5cd190c5_*`: NLNet provenance (log + unedited raw) for the step-B work. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Step B of the `._ria_nursery` removal (issue #477; see `ai/conc-anal/ria_nursery_removal_plan.md`). With step A having rerouted `.run_in_actor()` children onto the daemon nursery, the 2ndary "run-in-actor" nursery spawns nothing and its stored ref is never read — pure dead weight, - collapse the inner `async with trio.open_nursery() as ria_nursery` layer in `_open_and_supervise_one_cancels_all_nursery`; `da_nursery` is now the single nursery for ALL subactors. - `ActorNursery.__init__` loses the `ria_nursery` param + the `self._ria_nursery` attr; `start_actor()` loses its `nursery=` escape-hatch (spawns via `self._da_nursery` directly). - `._cancel_after_result_on_exit` stays — still the ria-child discriminator for `_reap_ria_portals()`. Behavior-preserving: a zero-task `trio.open_nursery()` only adds a checkpoint. The two error handlers are KEPT (now nested under the single nursery); merging them changes error/cancel propagation and is deferred to its own PR (TODO left at the outer `except`). Prompt-IO: ai/prompt-io/claude/20260702T172233Z_5cd190c5_prompt_io.md (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Split from the step-B2 code commit to keep the runtime diff free of `ai/` meta noise, - `ai/conc-anal/ria_nursery_removal_plan.md`: add a "Step-B2 outcome" section — the dead-outer-`raise` trace, why the merge is behavior-preserving, and the gate results. - `ai/prompt-io/claude/20260702T222544Z_9201a2ed_*`: NLNet provenance (log + unedited raw) for the step-B2 work. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Step B2 of the `._ria_nursery` removal (issue #477; see `ai/conc-anal/ria_nursery_removal_plan.md`). With the 2ndary nursery gone (step B), the two nested error handlers in `_open_and_supervise_one_cancels_all_nursery` collapse to one, - the outer `except (Exception, BaseExceptionGroup, trio.Cancelled)` existed to catch errors bubbling from the old `._ria_nursery.__aexit__` reaper-group; that nursery no longer exists. - trace shows the outer handler's `raise` was already DEAD: the inner handler records `errors[uid]` as its first action, so `errors` is always non-empty by the time anything could reach the outer handler, and the `finally`'s raise-from-`errors` always superseded the outer `raise`. - so fold both into a single `except BaseException as _scope_err` guarding the lone daemon nursery; the `finally` (unchanged) still raises the collected `errors` as a single exc or `BaseExceptionGroup`. - drop the now-unused `outer_err`/`inner_err` locals. Behaviour-preserving (net ~30 lines lighter); the big diff is the one-level de-indent of the handler body. The two remaining `maybe_wait_for_debugger()` guards collapse to the single pre-teardown wait. Prompt-IO: ai/prompt-io/claude/20260702T222544Z_9201a2ed_prompt_io.md (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Wrap `test_tractor_cancels_aio`'s `main()` in a `trio.fail_after(9 * cpu_perf_headroom())` so a wedged remote runtime can't hang the test forever. This is the blessed anti-hang guard here bc `pytest-timeout`'s global cap is intentionally off (it breaks `trio` under the fork backends, per the `pyproject` NOTE). The cap is generous + CPU-headroom-scaled bc it's an anti-hang guard, not a perf assertion. Motivated by the `._ria_nursery`-removal regression where a wedged ria-reaper once hung this exact test indefinitely. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
First test-file of the #477 `.run_in_actor()` removal (blocking `to_actor.run()` is the successor; the legacy non-blocking one-shot is dropped, not replaced). All 9 call-sites migrated, - blocking result/error/streaming-result tests -> `to_actor.run(fn, an=an, ...)`; the "streaming" ones stream aio<->trio INSIDE the subactor so the caller only awaits the final result. - forever-task + cancel tests (`test_tractor_cancels_aio`, `test_trio_cancels_aio`) -> `start_actor()` + `Portal.open_context()` + cancel — can't block on a never-returning task. Adds a small `sleep_forever_aio_ctx` `@context` shim. - greens the red `test_tractor_cancels_aio` anti-hang guard from the prior commit: under the correctly-scoped API the wait is bounded by the caller's cancel scope, so the hang is structurally gone — not patched. Suite: 34 passed, 2 xfailed (trio backend). (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Sole call-site: an inlined `run_in_actor(...).result()` -> blocking `tractor.to_actor.run(fn, an=an, ...)` (#477 removal). Behaviour identical — the one-shot's result/error is awaited in the caller's task rather than reaped at nursery teardown; the enclosing `move_on_after` still cancels the sub in the `error_in_child=False` case. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Sole call-site: `run_in_actor(sleep_back_actor, ...)` -> blocking `tractor.to_actor.run(..., an=n, ...)` (#477 removal). The RPC-callback subactor is awaited in-caller instead of reaped at nursery teardown; `name=`/`enable_modules=` map to `to_actor.run()`'s same-named params, the rest to `**fn_kwargs`. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Migrate all 4 sites to blocking `tractor.to_actor.run()` (#477 removal), - rename the two API-named tests to `test_to_actor_run_*` (`same_func_in_child`, `can_skip_parent_main_inheritance`) — they exercise the same spawn / `inherit_parent_main` path via the successor API. - the recursive `spawn()` helper drops its white-box `an._children` / portal-`_peers` asserts (which probed `run_in_actor`'s portal + nursery-tracking internals); `to_actor.run()` returns the result and reaps internally, so keep the user-facing `result == 10` check. - `test_most_beautiful_word` drops the 2nd `wait_for_result()` (the legacy result-cache re-fetch) — `to_actor.run()` delivers the value once, no cache. Suite: 9 passed. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
`test_multi_actor_subs_arbiter_pub` used `run_in_actor()` to spawn two forever-ish subscriber actors and hold their portals for a later `cancel_actor()` (its `.result()` was commented out exactly because `subs()` never cleanly returns). That deferred-spawn + cancel shape isn't a blocking `to_actor.run()`, so convert to the successor primitives (#477 removal), - `start_actor()` per subscriber — keeps the portal for the existing `cancel_actor()` teardown, - run `subs()` on each via a background `Portal.run()` task in a local `trio` nursery so both subscribe concurrently with the test's `wait_for_actor` / topic checks, - each bg runner swallows the `RemoteActorError`/`ContextCancelled` that `cancel_actor()` relays; a trailing `tn.cancel_scope.cancel()` drops any lingering runner. Suite: 8 passed. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Two sites migrated (#477 removal), - `test_trynamic_trio`: donny + gretchen each wait on the *other* to register, so they must run CONCURRENTLY — was two non-blocking `run_in_actor()`s awaited after; now two `to_actor.run()` one-shots scheduled into a local `trio` task-nursery. - the unregister-on-cancel cluster test: its non-streaming branch spawned `run_in_actor(trio.sleep_forever)` purely to keep each subactor alive + registered — a `start_actor()` daemon does that without a "main" task, so the spawn loop collapses to the same `start_actor()` the streaming branch already used. Suite: 16 passed. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
First group of the `test_cancellation.py` `run_in_actor` removal (#477), - `test_remote_error` -> blocking `to_actor.run()` (single erroring one-shot; a bad-arg `TypeError` still relays as a `RemoteActorError`). - `test_multierror` -> concurrent fan-out via `gather_contexts([p.open_context(assert_err_ctx) ...])` over `start_actor()` portals. NB `gather_contexts` is cancel-on-first so the 2nd errorer is usually cancelled before relaying its own exc and the pair collapses to a single `RemoteActorError` (vs the legacy reap-all-at-teardown `BEG`-of-N) — the assertion now accepts either shape. - delete `test_multierror_fast_nursery` — a 25-actor stress test of `run_in_actor`'s teardown-reap; no analogous surface under the `to_actor` fan-out. - add an `assert_err_ctx` `@context` shim for the `open_context` fan-out. Remaining `test_cancellation` groups (some_cancels_all, nested, SIGINT, sync-blocking) still on `run_in_actor` — ported next. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Append two sections to the ria-removal plan capturing the 2026-07-02 hang episode + the resulting design pivot. Regression writeup: the full-suite hang on `test_tractor_cancels_aio` root-caused to the step-A reaper hoist (`5cd190c5`), not the B2 handler merge. The happy-path `_reap_ria_portals()` parks unbounded on `wait_for_result()` after a user `portal.cancel_actor()`; the old spawn-backend reaper raced `soft_kill()`'s scope-cancel, the hoist dropped it. Records the `proc.poll()` death-watch fix + why poll (not the event `wait_func`) bc `soft_kill` already awaits `proc.sentinel` (a 2nd `wait_readable` -> `BusyResourceError`). Pause writeup: user's insight that the hoist landed in the wrong scope — result-waiting belongs in the `to_actor` one-shot scope (`_invoke_in_subactor()`), beside `an` + a local task-nursery + cancel-scope, where bounding the wait is trivial + the hang dissolves. So the poll fix is likely SUPERSEDED (flagged do-not-land); the anti-hang guard commit (`d1fb4a1a`) stays red-first per the failing-test convention. (this patch was generated in some part by `claude-code` using `claude-opus-4-8` (`anthropic`))
Second `test_cancellation.py` group of the `run_in_actor` removal (#477), - one-shot subactors now run as concurrent `to_actor.run(fn, an=an)` tasks in a local `trio` task-nursery, so their errors raise WHILE the actor-nursery block is open (vs the legacy teardown-reap) and the first error cancels sibling one-shots. - wrap the task-nursery in `collapse_eg()` so the deterministic single-error cases still surface a bare `RemoteActorError`. - loosen the group-shape assertion: the relay-vs-cancel race populates anywhere from 1 to `num_actors` `RemoteActorError`s (the exact-`num_actors` BEG was `run_in_actor`'s reap-all-at-teardown); group members are always `RemoteActorError` now since sibling `trio.Cancelled`s are absorbed by the task-nursery. - move the daemon-portal call loop inside the task-nursery body so the sleep-forever one-shot case is cancelled by the daemon error raise. - rename the `*run_in_actor*` param ids to `*one_shot*`. Gate: 6 passed on both `trio` + `mp_spawn` backends. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
`test_nested_multierrors`'s backend/depth budget `match` only carries arms for the `trio` + `main_thread_forkserver` spawn backends, so running under any other (e.g. `mp_spawn`) leaves `timeout` unbound and crashes with an `UnboundLocalError` at the headroom-scaling below. Add default per-depth arms riding the MTF budgets (same per-spawn round-trip cost class). (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Third `test_cancellation.py` group of the `run_in_actor` removal (#477), - `spawn_and_error` fans out each level's erroring one-shots as concurrent `to_actor.run(fn, an=an)` tasks in a local `trio` task-nursery (recursing per spawner subactor), as does the test-body's top-level spawner loop. - the deterministic exact-breadth nested-BEG shape dies with the legacy teardown-reap: each level now groups whatever subset of sub-tree errors relay before the first one's cancel wins, and a single-member group gets unwrapped by the runtime's own `collapse_eg()` at every actor boundary — so a fully-raced tree relays a bare `RemoteActorError` chain. - loosen the shape walk accordingly: accept a lone `RemoteActorError` or a 1..breadth group whose members box `ExceptionGroup` (multi-relay), `AssertionError` (collapsed leaf chain), `RemoteActorError` (re-boxed collapsed chain) or `BaseExceptionGroup` (runtime reap-deadline `Cancelled` upgrade); fold the windows-only tolerances into the same walk. - raced sibling `trio.Cancelled`s are now ABSORBED by the task-nursery instead of landing in the group, so the MTF shape-mismatch xfail should consistently xpass — note added to drop the marker once CI confirms. - add an `else: pytest.fail()` so a silently-clean tree can no longer pass. Gate: both depths green on `trio` (10 consecutive runs) + `mp_spawn`. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Final `test_cancellation.py` group of the `run_in_actor` removal (#477) — cancel-mechanics tests, so clean conversions, - `test_cancel_via_SIGINT_other_task`: the 3 keep-alive `run_in_actor(sleep_forever)` one-shots become plain `start_actor()` daemons (an idle daemon needs no "main" task, and no longer shares a single dup'd `namesucka` name). - `spawn_sub_with_sync_blocking_task`: the middle layer's spawn becomes a blocking `to_actor.run(spin_for, an=an)` which parks awaiting the sync-sleeping grandchild's result until cancelled from above. - `test_cancel_while_childs_child_in_sync_sleep`: the fire-and-forget middle-actor spawn becomes a bg `to_actor.run()` task in a local task-nursery; the root's `assert 0` cancels it, driving the same graceful-cancel-then-zombie-reap cascade on the sync-blocked grandchild. The `man_cancel_outer` xfail param is unchanged. Zero live `run_in_actor()` call-sites remain in this suite. Gate: full `test_cancellation.py` module green on both `trio` (18p/1xf) + `mp_spawn` (18p/1xf). (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
The known-flaky dynamic pubsub test's 3 fire-and-forget spawn sites (#477 removal), - the forever-streaming `publisher` + N `consumer` one-shots now bg-schedule as `to_actor.run(fn, an=n)` tasks in a local `trio` task-nursery (`publisher`'s rendezvous name still derives from `fn.__name__`). - the simulated user-cancel raise (`KeyboardInterrupt` / `TooSlowError` params) cancels the task-nursery, each one-shot reaping its subactor via `to_actor.run()`'s shielded `Portal.cancel_actor()`; `_run_and_match()`'s existing `BaseExceptionGroup.split()` walk covers the (possibly nested) relay shapes unchanged. - spawns now issue concurrently rather than sequentially — comment on the fork-backend budget updated to match. Gate: both params x4 runs green on `trio` + x1 on `mp_spawn`; full module green. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
4 example scripts of the #477 removal sweep, each exercised by `test_docs_examples.py`, - `actor_spawning_and_causality.py`: the simplest possible `to_actor.run()` demo — private call-scoped nursery, block on and print the one-shot's result. - `remote_error_propagation.py`: blocking `to_actor.run(an=n)` raises the boxed `AssertionError` in the caller's task, cancelling the sibling daemons. - `parallelism/single_func.py`: bg-burn a core in the parent via a local task-nursery while the one-shot burns (and returns from) a subactor. - `a_trynamic_first_scene.py`: donny + gretchen wait on each *other* so their one-shots run concurrently in a local task-nursery against a shared `an` (mirrors the migrated `test_trynamic_trio`). Gate: all 4 green via the example-runner suite. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
The 8 `examples/debugging/` scripts driven by the pexpect'd `test_debugger.py` REPL-flows (#477 removal), - blocking one-shots (`subactor_error`, `subactor_breakpoint`, `shielded_pause`): straight `to_actor.run(fn, an=an)` — the boxed error/`BdbQuit` raises in the root's task. - `multi_subactors`: introduces the "collect all errors" pattern — each one-shot catches + stashes its `RemoteActorError` (vs raising) so no child's crash cancels its siblings before they've had their own REPL sessions, then a `BaseExceptionGroup` of the lot raises at the end; preserves the legacy teardown-reap REPL flow exactly (28/28 debugger suite unchanged). - `multi_nested_subactors_error_up_through_nurseries` + `root_cancelled_but_child_is_in_tty_lock`: recursive `spawn_until` levels each block on their one-shot child; the parallel spawner-trees run as bg task-nursery one-shots where the first tree's error cancels the other. - `multi_subactor_root_errors` + `root_timeout_while_child_crashed`: `start_actor()` + bg `Portal.run()` tasks so the root's own error/timeout races the already-crashed children, same as before. - `sync_bp`: TODO-comment x-ref update only. `test_debugger.py`: the nested-nurseries test's final-output patterns update to the new relay shape — the LAST-released leaf REPL's error chain wins each level's relay-vs-cancel race and relays as a `collapse_eg()`-annotated collapsed chain, while the sibling tree is cancelled + absorbed. (The legacy teardown-reap grouped BOTH the `name_error` and bp-quit chains — explaining the previously-mysterious "extra" `src_uid`/`relay_uid` patterns noted in the old TODO.) Gate: `tests/devx/test_debugger.py` = 28 passed, 6 skipped — identical to the pre-migration baseline. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
The 8-page docs sweep of the #477 removal, ahead of the API's excision, - `start/quickstart.rst`: the first-actor-tree walkthrough now narrates the (migrated) `to_actor.run()` example — no portal in hand until the daemon section introduces `start_actor()`. - `guide/spawning.rst`: the one-shot section becomes `to_actor.run()` (blocking call, placement opts, "built on the primitives" note); lifetime/teardown rules update — one-shots never make it to nursery exit since each is reaped inside its own call. - `guide/rpc.rst`: the `wait_for_result()` section (an API that dies with the reap cluster, incl. the `NoResult` sentinel) becomes a `to_actor.run()` one-shot section. - `api/core.rst`: drop `run_in_actor`/`wait_for_result` from the autodoc member lists, drop the `Portal.result()` deprecation note, add a "One-shot task actors" `tractor.to_actor.run` autodoc section. - `guide/{asyncio,context,cancellation,parallelism}.rst`: mention swaps to the successor API. Gate: `make -C docs html` builds clean; `to_actor.run` autodoc renders in `api/core.html`. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
The `_pubsub.pub` decorator's usage example predates several API generations: ancient positional-arg-order `run_in_actor()` (a missing `await` too) plus the deprecated `portal.result()` — and `run_in_actor()` never allowed streaming funcs anyway. Show the canonical `start_actor()` + `Portal.open_stream_from()` consumption instead (#477 removal sweep). (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
The final excision of #477: with zero in-repo callers left (all tests/examples/docs migrated to `to_actor.run()` et al) the entire legacy one-shot machinery drops out, - `runtime/_supervise.py`: `ActorNursery.run_in_actor()`, the `._cancel_after_result_on_exit` portal-set and the `_reap_ria_portals()` teardown-reaper (both its happy-path block-exit call AND the error-path snapshot + 0.5s-bounded collection) are deleted — one-shot result-waiting now lives entirely in the caller's task via `to_actor.run()`, whose enclosing cancel-scope bounds the wait by construction (the correct-scoping fix for the unbounded-reap hang class; the `d1fb4a1a` guard test now passes structurally). - `runtime/_portal.py`: `Portal._submit_for_result()`, `._expect_result_ctx`, `._final_result_msg/_pld`, `.wait_for_result()` + the deprecated `.result()` alias are gone — a `Portal` no longer has any "main result" notion. NB `Context.wait_for_result()` is a different (very alive) API and is untouched. - `spawn/_spawn.py`: `exhaust_portal()` + `cancel_on_completion()` (the reaper tasks) deleted; backend comment sweeps in `_trio.py`/`_mp.py`. - `_exceptions.py`: the `NoResult` sentinel dies with its lone reader. - `tests/test_ringbuf.py`: drop a daemon-portal `.result()` call that was already a warn + `NoResult` no-op (the ctx-acm exit does the real result-wait); unshadow the 2nd `sctx` as `rctx`. - comment/docstring x-ref sweeps: `msg/types.py`, `_context.py`, `to_actor/`, `tests/test_to_actor.py`. Gate: `test_to_actor test_spawning test_cancellation test_infected_asyncio test_local test_rpc` = 81 passed, 3 xfailed on `trio`; +`test_ringbuf` = 70 passed, 3 skipped, 3 xfailed on `mp_spawn`. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
The `test_trynamic_trio` + `a_trynamic_first_scene.py` migration to paired `to_actor.run()` one-shots carries a race the legacy `run_in_actor()` shape never had: donny + gretchen each `wait_for_actor()` (then DIAL) the *other*, but a one-shot is reaped the instant its own hello returns — so the slower peer can resolve the winner's registry entry and connect to an already-dead sockaddr -> `ConnectionRefusedError` boxed as a `RemoteActorError` (or a reg-wait `TooSlowError`), flaking ~1-in-3 standalone runs. Mutual-rendezvous peers must OUTLIVE both dialogs, so pin the lifetimes explicitly: `start_actor()` both as daemons, run both hellos concurrently via bg `Portal.run()` tasks, then reap with `an.cancel()` only after the task-nursery joins. (The legacy teardown-reap provided this pinning implicitly — one of the few places its semantics were ever actually relied upon.) Gate: `-k trynamic` standalone x8 green (was flaking); full `test_registrar` module + the example-runner green. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Convention sweep (user req): all `tractor.open_nursery()` bindings in test + example code use `an: ActorNursery` (`n`, `nursery` + several tractor-nurseries confusingly named `tn` are renamed); `trio.open_nursery()` bindings stay `tn` (incl. `concurrent_actors_primes.py`'s inner trio nursery, renamed `n` -> `tn` to match). Purely mechanical, function-scoped renames — prose "nursery"/ "an" in docstrings/comments untouched; func-arg kwargs like `portal.run(func, n=value)` untouched. Gate: renamed test modules green on `trio`; full debugger suite (28p/6s) + example-runner (21p) green. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
Fold the endeavour's resolution into the plan doc + log the session per prompt-io policy, - `ria_nursery_removal_plan.md`: RESOLVED section — migrate everything, remove the API; the migration-pattern table (blocking / fire-and-forget / fan-out / collect-don't-cancel / mutual-rendezvous), the semantic deltas (cancel-on-first + `collapse_eg()` chain collapse vs the old teardown-reap BEG), the excision inventory and the structural dissolution of the reap-hang class. - adds the `to_actor.open_one_shot()` follow-up sketch: an `@acm` + private task-nursery over the existing blocking `run()` — done-`trio.Event` as a result memo (NOT a cancel-relay), no `Portal` in the iface, errors always propagate at scope exit; zero `_supervise` coupling. - prompt-io entry `20260706T172818Z_ad42871e` (+ raw diff-ref companion) covering commits `d01a2123..ad42871`. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
3 tasks
There was a problem hiding this comment.
Pull request overview
Removes the legacy non-blocking ActorNursery.run_in_actor() API and its associated “result reaping” machinery, migrating the codebase to the newer tractor.to_actor.run() one-shot API and simplifying the runtime/supervision + spawn backends accordingly.
Changes:
- Excise
run_in_actor()and the portal “main-result” cluster (Portal.wait_for_result()/ deprecatedPortal.result()/ related sentinels & helpers). - Simplify actor supervision/spawn paths to use a single daemon nursery for all subactors; drop backend-local reaper logic.
- Migrate in-repo tests/examples/docs to
to_actor.run()or explicitstart_actor()+Portal.run()patterns.
Reviewed changes
Copilot reviewed 63 out of 63 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tractor/to_actor/_api.py | Refresh docs to reflect removal of legacy run_in_actor(). |
| tractor/to_actor/init.py | Update package docstring to describe to_actor as the replacement API. |
| tractor/spawn/_trio.py | Remove run_in_actor()-specific reaper branch; rely on standard reap/kill flow. |
| tractor/spawn/_spawn.py | Delete cancel_on_completion() / exhaust_portal() helpers and update backend import note. |
| tractor/spawn/_mp.py | Mirror trio backend changes: remove run_in_actor()-specific reaper branch. |
| tractor/runtime/_supervise.py | Remove run_in_actor() and the secondary nursery layer; unify supervision around one nursery. |
| tractor/runtime/_portal.py | Remove portal “main result” machinery (wait_for_result, deprecated result, etc.). |
| tractor/msg/types.py | Update message-type doc references to to_actor.run(). |
| tractor/experimental/_pubsub.py | Update pubsub docstring example to use start_actor() + streaming context manager. |
| tractor/_exceptions.py | Remove NoResult sentinel (no longer needed without portal final-result API). |
| tractor/_context.py | Update comments referencing legacy APIs to to_actor.run(). |
| tests/test_to_actor.py | Update test module docs to reflect legacy removal. |
| tests/test_spawning.py | Migrate spawn recursion and one-shot invocations to to_actor.run(). |
| tests/test_runtime.py | Migrate one-shot usage to to_actor.run(). |
| tests/test_rpc.py | Migrate one-shot usage to to_actor.run() with an= placement. |
| tests/test_ringbuf.py | Remove no-op portal .result() usage; rely on context manager teardown semantics. |
| tests/test_pubsub.py | Replace legacy one-shot usage with daemon actors + background Portal.run() tasks. |
| tests/test_inter_peer_cancellation.py | Naming sweep (n → an) for ActorNursery bindings. |
| tests/test_infected_asyncio.py | Migrate one-shot usage; add context shim for infected-asyncio “forever” cancellation patterns. |
| tests/test_child_manages_service_nursery.py | Naming sweep (n → an) for ActorNursery bindings. |
| tests/test_cancellation.py | Migrate fan-out/error tests to to_actor.run() + local task nurseries / gather_contexts(). |
| tests/test_advanced_streaming.py | Migrate “forever streaming” patterns to bg one-shots via local task nurseries. |
| tests/discovery/test_registrar.py | Naming sweep (n → an) and migrate lingering patterns away from legacy APIs. |
| tests/devx/test_debugger.py | Update expected patterns to match new error-group/collapse behavior. |
| examples/rpc_bidir_streaming.py | Naming sweep (n → an). |
| examples/remote_error_propagation.py | Migrate one-shot example to to_actor.run(). |
| examples/parallelism/single_func.py | Demonstrate local+remote concurrency via trio.open_nursery() + to_actor.run(). |
| examples/parallelism/concurrent_actors_primes.py | Naming sweep (tn → an) and minor nursery variable cleanup. |
| examples/integration/open_context_and_sleep.py | Comment-only rename in illustrative snippet. |
| examples/infected_asyncio_echo_server.py | Naming sweep (n → an). |
| examples/debugging/sync_bp.py | Update TODO comment to reference to_actor.run(). |
| examples/debugging/subactor_error.py | Migrate to to_actor.run() and remove portal-final-result calls. |
| examples/debugging/subactor_breakpoint.py | Migrate to to_actor.run() and remove portal-final-result calls. |
| examples/debugging/subactor_bp_in_ctx.py | Naming sweep (n → an). |
| examples/debugging/shielded_pause.py | Migrate to to_actor.run() and remove portal-final-result calls. |
| examples/debugging/root_timeout_while_child_crashed.py | Use daemon + bg Portal.run() for erroring child while root blocks. |
| examples/debugging/root_cancelled_but_child_is_in_tty_lock.py | Use bg to_actor.run() task to preserve legacy concurrent-tree behavior. |
| examples/debugging/per_actor_debug.py | Naming sweep (tn → an). |
| examples/debugging/open_ctx_modnofound.py | Naming sweep (n → an). |
| examples/debugging/multi_subactors.py | Replace teardown “reap-all” behavior with an explicit “collect don’t cancel” pattern. |
| examples/debugging/multi_subactor_root_errors.py | Use daemons + bg Portal.run() and align behavior with new scoping. |
| examples/debugging/multi_nested_subactors_error_up_through_nurseries.py | Use concurrent to_actor.run() tasks for nested-tree error relays. |
| examples/asynchronous_generators.py | Naming sweep (n → an). |
| examples/actor_spawning_and_causality.py | Replace run_in_actor() flow with a single blocking to_actor.run() call. |
| examples/actor_spawning_and_causality_with_daemon.py | Naming sweep (n → an). |
| examples/a_trynamic_first_scene.py | Fix mutual-rendezvous lifetime by using daemons + concurrent Portal.run()s. |
| docs/start/quickstart.rst | Update quickstart narrative to reference to_actor.run() and one-shot semantics. |
| docs/guide/spawning.rst | Replace run_in_actor() docs with to_actor.run() and updated semantics. |
| docs/guide/rpc.rst | Replace portal final-result docs with to_actor.run() one-shot explanation. |
| docs/guide/parallelism.rst | Update one-shot parallelism docs to use to_actor.run(). |
| docs/guide/context.rst | Update references to the one-shot convenience API. |
| docs/guide/cancellation.rst | Update cancellation narrative to reflect one-shot scoping via to_actor.run(). |
| docs/guide/asyncio.rst | Update infected-asyncio docs to reference to_actor.run() instead of legacy API. |
| docs/api/core.rst | Remove run_in_actor from API docs; add to_actor.run as the one-shot function. |
| ai/prompt-io/claude/20260706T172818Z_ad42871e_prompt_io.raw.md | Add prompt I/O artifact for provenance of generated changes. |
| ai/prompt-io/claude/20260706T172818Z_ad42871e_prompt_io.md | Add summarized prompt I/O artifact. |
| ai/prompt-io/claude/20260702T222544Z_9201a2ed_prompt_io.raw.md | Add earlier prompt I/O artifact. |
| ai/prompt-io/claude/20260702T222544Z_9201a2ed_prompt_io.md | Add earlier summarized prompt I/O artifact. |
| ai/prompt-io/claude/20260702T172233Z_5cd190c5_prompt_io.raw.md | Add earlier prompt I/O artifact. |
| ai/prompt-io/claude/20260702T172233Z_5cd190c5_prompt_io.md | Add earlier summarized prompt I/O artifact. |
| ai/prompt-io/claude/20260702T165806Z_a34aaf98_prompt_io.raw.md | Add earlier prompt I/O artifact. |
| ai/prompt-io/claude/20260702T165806Z_a34aaf98_prompt_io.md | Add earlier summarized prompt I/O artifact. |
| ai/conc-anal/ria_nursery_removal_plan.md | Add/extend internal analysis doc documenting the removal plan and outcomes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+282
to
+283
| # drop the bg `subs()` runners now the subs are cancelled | ||
| tn.cancel_scope.cancel() |
| registry_addrs=[reg_addr], | ||
| ) as an: | ||
| portal = await an.run_in_actor( | ||
| # should raise RAE diectly |
| registry_addrs=[reg_addr], | ||
| ) as an: | ||
| portal = await an.run_in_actor( | ||
| # should raise RAE diectly |
| # enable_stack_on_sig=True, | ||
| ) as an: | ||
| portal = await an.run_in_actor( | ||
| # should raise RAE diectly |
|
|
||
| except tractor.RemoteActorError as err: | ||
| assert err.boxed_type == err_type | ||
| # we only expect this first error to propogate |
| else: | ||
| if expect_error: | ||
| pytest.fail( | ||
| "Deamon call should fail at checkpoint?") |
| async with ( | ||
| tractor.open_nursery( | ||
| debug_mode=True, | ||
| enable_transports=['uds'], # TODO, apss this via osenv? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Drop
run_in_actor()+ the ria reap-clusterMotivation
The legacy non-blocking
ActorNursery.run_in_actor()predates ourstreaming support and always sat awkwardly across multiple runtime
layers: it returned a
Portalwhose "main result" was owned byinvisible machinery (
._expect_result_ctx+ a teardown reap-taskraced against
soft_kill()inside each spawn backend) and reapedinto the nursery's error-collection only at block exit. Issue #477
prescribed decoupling one-shot invocation from the nursery internals;
the sibling PR #481 (this PR's base) delivered the blocking successor
tractor.to_actor.run().Attempting to keep the legacy API during the nursery-machinery
simplification exposed why it had to go: hoisting the backend-local
reapers up into
_supervisedropped the reaper-vs-proc-death raceand produced an unbounded
wait_for_result()hang whenever aone-shot child was cancelled out-of-band. The correct scoping — a
result-wait living in the caller's task inside its own cancel-scope
— is exactly what
to_actor.run()already is, so instead of boltinga race back on we migrate every in-repo caller and remove the API
(and its whole reap cluster) outright.
Src of research
ActorNursery.run_in_actor()(and possiblyPortal.run()) into a newtractor.to_actorwrapper API #477.run_in_actor()remote task #287 (comment)step-A/B/B2 probes, the reap-hang post-mortem and the final
migration-pattern table):
ai/conc-anal/ria_nursery_removal_plan.mdSummary of changes
ria-reaping out of the spawn backends, delete the vestigial 2ndary
._ria_nurserylayer (a singleda_nurserynow hosts ALLsubactors), and merge the two nested scope-error handlers into one
(the outer's
raisewas provably dead).test_tractor_cancels_aiowrappedin
trio.fail_after()so the (since dissolved) unbounded-reap hangclass fails fast instead of wedging the suite.
run_in_actor()caller (one commit persuite/group for bisectability): 9 test modules, all 12 examples
(incl. the 8 pexpect-driven
debugging/REPL scripts), 8 docspages + the
experimental/_pubsubdocstring. Recurring conversionpatterns,
to_actor.run(fn, an=an, ...).to_actor.run()task in a localtriotask-nursery (orstart_actor()+ bgPortal.run()whena portal handle is needed).
gather_contexts([p.open_context(..)]).(see
examples/debugging/multi_subactors.py).BEG-of-N semantic died: local task-nurseries are cancel-on-first,raced siblings'
trio.Cancelleds are absorbed, and the runtime'scollapse_eg()unwraps every single-member group at each actorboundary — a fully-raced nested tree relays a bare (annotated)
RemoteActorErrorchain.test_multierror_fast_nursery(purereap-stress) is deleted;
test_nested_multierrorsis re-purposedas deep-tree cancel-cascade stress with a race-tolerant shape walk.
run_in_actor(),._cancel_after_result_on_exit,_reap_ria_portals(),Portal._submit_for_result()/._expect_result_ctx/.wait_for_result()/deprecated.result(),exhaust_portal(),cancel_on_completion()and theNoResultsentinel. NB
Context.wait_for_result()is a different (veryalive) API and is untouched.
(a297a32a): paired one-shots that dial each other
flaked since the winner is reaped while the loser still dials its
registry-resolved (dead) sockaddr — such peers now pin lifetimes
via
start_actor()daemons + concurrentPortal.run()s + explicitan.cancel().UnboundLocalError(timeoutmatchlacked anon-trio/MTF default arm), fix the ancient
@pubdocstringexample, and sweep all test/example
ActorNurserybindings to theannaming convention.Gates: full suite on
trio= 413 passed, 0 failures, 0 hangs;tests/devx/test_debugger.pybyte-identical to the pre-migrationbaseline (28 passed, 6 skipped);
mp_spawnmodule-level gates greenthroughout. (The full-session
mp_spawnrun mass-fails identicallyat this PR's base — A/B-probed pre-existing and unexercised by CI's
trio-only matrix.)
TODOs before landing
tcp+uds, macos tcp, sdist, sphinx).
-x-less full-suite pass on a normal-speed box iflocal runs looked thermally throttled (see the plan doc's B2
notes).
Future follow up
.run_in_actor()? #172) + deferred-result (run-async)parity are absorbed into the
to_actor.open_taskman()design +plan tracked in Add
to_actor.open_taskman(): an explicit remote-task supervision scope #485.test_nested_multierrors' MTF shape-mismatch xfail markeronce CI shows it consistently xpassing — post-Factor
ActorNursery.run_in_actor()(and possiblyPortal.run()) into a newtractor.to_actorwrapper API #477 the racedsibling's
Cancelledis absorbed instead of landing in the group(see the in-test NB comment).
mp_spawnbreakage (pre-existing,KeyErrorin
an.cancel()'s not-fully-spawned branch masking spawn death)belongs in the Trying out sub-interpreters (subints), maybe
fork()can be hacked now?' #379-era backend-revival bucket.Links
ActorNursery.run_in_actor()(and possiblyPortal.run()) into a newtractor.to_actorwrapper API #477 (see the updated task-listcomment)
tractor.to_actorone-shot task API subpkg #481 (wkt/to_actor_subpkg, theto_actorsubpkgitself)
to_actor.open_taskman(): an explicit remote-task supervision scope #485(this pr content was generated in some part by
claude-code)