Port docs off `run_in_actor` + `Portal.wait_for_result`

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
drop_ria_nursery
Gud Boi 2026-07-06 12:31:02 -04:00
parent a3057cb24f
commit d6bed7c4a0
8 changed files with 98 additions and 78 deletions

View File

@ -37,7 +37,6 @@ Spawning actors
.. autoclass:: ActorNursery
:members: start_actor,
run_in_actor,
cancel,
cancel_called,
cancelled_caught
@ -47,10 +46,22 @@ Spawning actors
:meth:`ActorNursery.start_actor` (daemon actor + portal) is the
blessed spawning primitive; pair it with
``Portal.open_context()`` for SC-linked remote tasks.
:meth:`ActorNursery.run_in_actor` is a *convenience* one-shot —
spawn, run a single task, auto-cancel after the result — slated
to be rebuilt as a high-level wrapper, so don't design around
it as the core model.
One-shot task actors
--------------------
.. autofunction:: tractor.to_actor.run
.. note::
:func:`tractor.to_actor.run` (parlance of
``trio.to_thread.run_sync()`` and friends) is the
*convenience* one-shot — spawn, run a single task, block on
its result, reap — built entirely on
:meth:`ActorNursery.start_actor` + :meth:`Portal.run` +
:meth:`Portal.cancel_actor`, so don't design around it as the
core model. It supersedes the removed (legacy, non-blocking)
``ActorNursery.run_in_actor()``.
.. deprecated:: 0.1.0a6
@ -71,14 +82,12 @@ flowing back `exactly like trio`_.
:members: run,
run_from_ns,
open_stream_from,
wait_for_result,
cancel_actor,
chan
.. deprecated:: 0.1.0a6
``Portal.result()`` warns; use :meth:`Portal.wait_for_result`.
The str-form ``Portal.run('mod.path', 'fn_name')`` also warns;
The str-form ``Portal.run('mod.path', 'fn_name')`` warns;
pass a function *object* whose module is listed in the target's
``enable_modules``. ``Portal.channel`` is the legacy spelling
of :attr:`Portal.chan`.

View File

@ -76,8 +76,8 @@ Just flip the flag on :meth:`tractor.ActorNursery.start_actor`:
infect_asyncio=True,
)
The one-shot convenience ``ActorNursery.run_in_actor()`` accepts
the same flag. The ``to_asyncio`` APIs may **only** be called from
The one-shot convenience ``tractor.to_actor.run()`` accepts the
same flag. The ``to_asyncio`` APIs may **only** be called from
tasks inside an infected actor; calling them anywhere else raises
a loud ``RuntimeError``. You can introspect at runtime with
``tractor.current_actor().is_infected_aio()``.
@ -229,7 +229,7 @@ dialog, skip the channel ceremony and use
It schedules the fn as an ``asyncio.Task``, waits for completion
and hands the return value back to ``trio``; think of it as the
cross-loop sibling of ``ActorNursery.run_in_actor()``. Errors and
cross-loop sibling of ``tractor.to_actor.run()``. Errors and
cancellation are translated exactly as for channels.
Cross-loop errors and cancellation

View File

@ -64,11 +64,13 @@ What's going on here?
- three healthy actors are spawned as daemons via
:meth:`tractor.ActorNursery.start_actor`; left alone they'd
happily idle forever,
- a fourth actor runs ``assert_err()`` via ``.run_in_actor()`` and
promptly trips its ``assert 0``,
- a fourth actor runs ``assert_err()`` via a blocking
``tractor.to_actor.run()`` one-shot and promptly trips its
``assert 0``,
- the resulting ``AssertionError`` ships back over IPC as a
serialized error msg and re-raises *boxed* inside the nursery
block as a :class:`tractor.RemoteActorError`,
serialized error msg and re-raises *boxed* right at the call
inside the nursery block as a
:class:`tractor.RemoteActorError`,
- the nursery reacts like any ``trio`` nursery would: it cancels
the three healthy siblings (graceful runtime-cancel requests,
acks awaited), reaps all four processes, then re-raises,

View File

@ -15,8 +15,8 @@ a single `structured concurrency`_ (SC) scope over IPC.
:alt: sequence diagram of the context handshake msg flow
Pretty much everything else is (or is slated to be) built on this
one primitive: ``ActorNursery.run_in_actor()`` is a convenience
for "spawn, open a context, await the result, tear down"; plain
one primitive: ``tractor.to_actor.run()`` is a convenience for
"spawn, run the lone task, await the result, tear down"; plain
``Portal.run()`` RPC is planned to be re-implemented on top of it;
the multi-process debugger's tree-wide REPL lock rides one. Grok
this page and the rest of the library reads as convenience

View File

@ -119,15 +119,16 @@ Run a func in a process
Even a pool can be overkill; "run this one async func in a
subprocess and give me the result" is a one-liner via
:meth:`tractor.ActorNursery.run_in_actor`,
:func:`tractor.to_actor.run`,
.. literalinclude:: ../../examples/parallelism/single_func.py
:caption: examples/parallelism/single_func.py
:language: python
``run_in_actor()`` is a *convenience wrapper* — spawn an actor, run
exactly one task in it, reap on result — not the core spawning
model (that's :meth:`tractor.ActorNursery.start_actor` plus
``to_actor.run()`` is a *convenience wrapper* — spawn an actor,
run exactly one task in it, block on and return its result, reap
— not the core spawning model (that's
:meth:`tractor.ActorNursery.start_actor` plus
:meth:`tractor.Portal.open_context`; see :doc:`/guide/context`).
But for this fire-and-collect shape it's exactly the right amount
of typing.

View File

@ -80,28 +80,30 @@ One special namespace exists: ``'self'`` resolves to the remote
how internal machinery (cancel requests, registry ops) travels;
don't build your app on it.
One-shot results: ``wait_for_result()``
---------------------------------------
A portal returned from
:meth:`~tractor.ActorNursery.run_in_actor` has exactly one
"main" task running remotely; that task's ``return`` value is
delivered as the portal's *final result*:
One-shot subactors: ``to_actor.run()``
--------------------------------------
When a subactor's *entire job* is a single function call, skip
the portal plumbing with :func:`tractor.to_actor.run`: spawn,
run the lone task, return its result and reap the process — all
in one blocking call:
.. code:: python
portal = await an.run_in_actor(fib, n=10)
final = await portal.wait_for_result()
final = await tractor.to_actor.run(fib, an=an, n=10)
Semantics worth knowing:
- it blocks until the remote task returns, re-raising any
remote error in the usual boxed form.
- once resolved it's idempotent: later calls return the same
cached value.
- a *daemon* portal (from ``start_actor()``) has no main task,
so there's no final result to wait for: you'll get a warning
plus a ``NoResult`` sentinel. Results of individual daemon
calls come straight back from each ``await portal.run()``.
remote error in the usual boxed form right in the calling
task.
- "placement" is composable: ``an=`` spawns from an existing
actor-nursery, ``portal=`` reuses an already-running actor
(no spawn/reap, just a ``Portal.run()``), and passing
neither opens a private call-scoped nursery (booting the
runtime if needed).
- concurrency composes the plain ``trio`` way: schedule
multiple ``run()`` calls into a local task nursery (see
``examples/parallelism/to_actor_one_shots.py``).
Pure RPC daemons: ``run_daemon()``
----------------------------------

View File

@ -103,19 +103,22 @@ What's going on here?
on him **forever**. Daemon lifetimes are *yours* to end; that
explicitness is the point.
``run_in_actor()``: quick one-shot parallelism
``to_actor.run()``: quick one-shot parallelism
----------------------------------------------
:meth:`~tractor.ActorNursery.run_in_actor` is the convenience
wrapper: spawn an actor, run exactly one async function in it,
then reap the process as soon as the result arrives.
:func:`tractor.to_actor.run` is the convenience wrapper: spawn
an actor, run exactly one async function in it, block on the
result, then reap the process — the distributed sibling of
``trio.to_thread.run_sync()``.
.. code:: python
async with tractor.open_nursery() as an:
portal = await an.run_in_actor(burn_cpu)
async with (
tractor.open_nursery() as an,
trio.open_nursery() as tn,
):
# burn rubber in the parent too...
await burn_cpu()
total = await portal.wait_for_result()
tn.start_soon(burn_cpu)
total = await tractor.to_actor.run(burn_cpu, an=an)
A few details worth knowing:
@ -124,18 +127,21 @@ A few details worth knowing:
- the function's module is auto-added to the child's
``enable_modules`` allowlist.
- extra ``**kwargs`` are forwarded to the function itself.
- the child is *auto-cancelled* once its "main" result lands;
at nursery exit these run-once children are always reaped
first (causality_ is paramount!).
- the call blocks until the result (or error) lands and the
child is *auto-cancelled* (reaped) right after — so remote
errors raise directly in your calling task (causality_ is
paramount!).
- "placement" composes: ``an=`` spawns from a caller-managed
actor-nursery, ``portal=`` reuses an already-running actor
(no spawn/reap), and passing neither opens a private
call-scoped nursery (booting the runtime if needed).
.. note::
``run_in_actor()`` is a convenience, **not** the core model.
The source literally marks it for an eventual rebuild as
a thin "hilevel" wrapper on top of
:meth:`~tractor.Portal.open_context` (the modern inter-actor
task API). Teach your fingers to use it for quick
fire-and-collect parallelism — think a per-function
``to_actor.run()`` is a convenience, **not** the core model —
it's built *entirely* on ``start_actor()`` + ``Portal.run()``
+ ``Portal.cancel_actor()``. Teach your fingers to use it for
quick fire-and-collect parallelism — think a per-function
trio-parallel_ style one-shot — and reach for
``start_actor()`` + ``open_context()`` for anything
long-lived, stateful or streaming
@ -145,9 +151,9 @@ Actor lifetimes and teardown order
----------------------------------
So we have two lifetime flavors:
- **run-once** (``run_in_actor()``): lives exactly as long as
- **one-shot** (``to_actor.run()``): lives exactly as long as
its single task; reaped the moment its result (or error)
arrives.
arrives back in the (blocking) call.
- **daemon** (``start_actor()``): lives until *someone* cancels
it — an explicit ``await portal.cancel_actor()``, a bulk
``await an.cancel()``, or the one-cancels-all strategy kicking
@ -155,11 +161,12 @@ So we have two lifetime flavors:
On a clean exit of the nursery block the teardown order is:
1. the nursery waits on every run-once actor's final result;
any errors from these are raised immediately so your code
(acting as supervisor) gets first crack at handling them.
2. then it waits on daemon actors — **indefinitely**. If you
spawned a daemon, you own its lifetime.
1. one-shot actors never make it to nursery exit: each is
reaped inside its own ``to_actor.run()`` call, any error
raising immediately in the calling task so your code
(acting as supervisor) gets first crack at handling it.
2. the nursery then waits on daemon actors — **indefinitely**.
If you spawned a daemon, you own its lifetime.
When a child *is* cancelled, teardown is graceful-first per SC
discipline: the runtime sends an IPC cancel request and gives

View File

@ -43,24 +43,20 @@ Run it::
What's going on here?
- ``trio.run(main)`` starts the **root actor**; the ``tractor``
runtime boots *implicitly* inside ``tractor.open_nursery()``
runtime boots *implicitly* inside ``tractor.to_actor.run()``
whenever it isn't already up. No special entrypoint, no
framework takeover - it's just a ``trio`` app,
- inside ``main()`` a *subactor* is spawned via
``ActorNursery.run_in_actor()`` and told to run exactly one
``tractor.to_actor.run()`` and told to run exactly one
function: ``cellar_door()``,
- you get back a ``Portal``: your handle for invoking tasks in
the new process's (separate!) memory domain. We lean on it
much harder in the next section,
- the subactor, *some_linguist*, boots a fresh ``trio.run()`` in
a **new process** and executes ``cellar_door()`` as its *main
task* (note the child proving it is *not* the root with
``tractor.is_root_process()``), then ships the return value
back over IPC,
- the parent grabs that *final result* with
``await portal.wait_for_result()``, much like you'd expect
from a "future" - except causality is preserved: the nursery
block only exits once the child is *done*, dead, and reaped.
- the call *blocks* until that final result arrives, then
returns it - causality is preserved: your task only proceeds
once the child is *done*, dead, and reaped.
.. margin:: Just need a worker pool?
@ -71,19 +67,22 @@ What's going on here?
.. note::
``run_in_actor()`` is the *convenience* wrapper: one-shot
``to_actor.run()`` (parlance of ``trio.to_thread`` and
friends) is the *convenience* wrapper: one-shot
spawn-run-reap semantics for when a subactor's entire job is
a single function call. The core primitives are
``ActorNursery.start_actor()`` (next up) paired with
``ActorNursery.start_actor()`` (next up) — which hands you
a ``Portal``, your handle for invoking tasks in the new
process's (separate!) memory domain — paired with
``Portal.open_context()`` for full, SC-linked cross-actor
dialogs - see :doc:`/guide/context`.
Daemon actors and RPC
---------------------
A ``run_in_actor()``-spawned actor terminates when its main task
returns. But often you want long-lived *daemon* actors instead:
spawned once, then serving (allowlisted) RPC requests until told
otherwise. That's ``start_actor()``:
A ``to_actor.run()`` one-shot subactor terminates when its lone
task returns. But often you want long-lived *daemon* actors
instead: spawned once, then serving (allowlisted) RPC requests
until told otherwise. That's ``start_actor()``:
.. literalinclude:: ../../examples/actor_spawning_and_causality_with_daemon.py
:caption: examples/actor_spawning_and_causality_with_daemon.py
@ -91,9 +90,9 @@ otherwise. That's ``start_actor()``:
Two lifetime rules to internalize:
- a ``run_in_actor()`` actor lives exactly as long as its main
task; the nursery waits for that function (and thus the
process) to complete before unblocking,
- a ``to_actor.run()`` one-shot actor lives exactly as long as
its lone task; the call blocks until that function (and thus
the process) completes,
- a ``start_actor()`` actor *lives forever* - an RPC daemon the
nursery will happily wait on **indefinitely** - until some
task explicitly cancels it via ``Portal.cancel_actor()`` (as