Compare commits

..

3 Commits

Author SHA1 Message Date
Gud Boi c62f93a8fb Doc the #477 migration outcome + one-shot-acm sketch
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..ad42871e`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-07-06 14:31:34 -04:00
Gud Boi ad42871e47 Name every `ActorNursery` binding `an` in tests/examples
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
2026-07-06 13:26:55 -04:00
Gud Boi a297a32af6 Fix mutual-rendezvous premature-reap race (#477)
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
2026-07-06 13:21:31 -04:00
27 changed files with 338 additions and 122 deletions

View File

@ -367,9 +367,94 @@ user's failing-test-first convention). The poll-based reap
fix in `_supervise.py` is UNCOMMITTED and likely SUPERSEDED fix in `_supervise.py` is UNCOMMITTED and likely SUPERSEDED
by the re-scoping — do NOT land it as-is. by the re-scoping — do NOT land it as-is.
## RESOLVED (2026-07-06): migrate everything, remove the API
The PAUSED re-assessment concluded decisively: rather than
re-scope `_reap_ria_portals` (or bolt any hack onto it), the
`run_in_actor()` API itself was REMOVED — its non-blocking
"result at teardown" semantic predates streaming and confused
more than it served. Every in-repo caller was migrated
per-file/-group (each its own commit, each gated):
- tests: `test_infected_asyncio` `test_runtime` `test_rpc`
`test_spawning` `test_pubsub` `test_registrar`
`test_cancellation` (3 groups) `test_advanced_streaming`.
- examples: 4 non-debugging + all 8 `debugging/` REPL scripts
(debugger suite byte-identical green, 28p/6s).
- docs: 8 rst pages + the `experimental/_pubsub` docstring.
Migration patterns (the `run_in_actor` shape -> successor):
- blocking result -> `to_actor.run(fn, an=an, ...)`
- fire-&-forget/forever -> bg `to_actor.run()` task in a local
`trio` task-nursery (or `start_actor`
+ bg `Portal.run()` when a portal
handle is needed)
- concurrent fan-out -> N bg `to_actor.run()` tasks / or
`gather_contexts([p.open_context(..)])`
- reap-all-error-collect -> the "collect don't cancel" pattern:
each one-shot catches + stashes its
`RemoteActorError`, group raised
after the task-nursery joins (see
`examples/debugging/multi_subactors.py`)
- mutual-rendezvous -> peers must OUTLIVE both dialogs:
`start_actor()` daemons + concurrent
`Portal.run()`s + explicit
`an.cancel()` (eager one-shot reap
races the slower peer's dial of the
winner's dead sockaddr; found via
`test_trynamic_trio` flake).
Semantic deltas (tests loosened accordingly):
- teardown-reap-all BEG-of-N is GONE: local task-nurseries are
cancel-on-first, raced siblings' `Cancelled`s are absorbed,
and the runtime's `collapse_eg()` unwraps every single-member
group at each actor boundary — a fully-raced nested tree
relays a bare (annotated) `RemoteActorError` chain.
- `test_multierror_fast_nursery` deleted (pure reap-stress);
`test_nested_multierrors` re-purposed as deep-tree
cancel-cascade stress w/ a race-tolerant shape walk.
Final excision (after zero callers remained): `run_in_actor()`,
`._cancel_after_result_on_exit`, `_reap_ria_portals()`,
`Portal._submit_for_result/._expect_result_ctx/
.wait_for_result()/.result()`, `exhaust_portal()`,
`cancel_on_completion()`, `NoResult` — net -402 lines. The
reap-hang class (unbounded `wait_for_result` in machinery
scope) dissolves structurally: the only result-wait left lives
in the caller's task inside its own cancel-scope; the
`d1fb4a1a` anti-hang guard test passes by construction. The
poll-vs-`proc_waiter` debate is moot as predicted.
## Follow-up sketch: `to_actor.open_one_shot()` (run-async parity)
If deferred-result parity is ever wanted, the design that needs
NO runtime coupling, NO returned `Portal` and NO cancel-relay
`trio.Event` machinery:
async with to_actor.open_one_shot(
fn, an=an, **kws,
) as one_shot:
... # concurrent caller work
val = await one_shot.wait() # optional; errors always
# propagate at scope exit
an `@acm` that opens a private task-nursery, `start_soon`s ONE
task running the existing blocking `run()` and stashes the
value in a slot + sets a done-`trio.Event` (a memo, not a
cancel relay). Cancellation = plain scope-cancel of the acm's
nursery (the parked `Portal.run()` unwinds via `Cancelled`, the
shielded `cancel_actor()` reap still runs); a child error
raises into the acm scope so an un-`wait()`ed one-shot can
never silently drop its error. i.e. the old reaper's job is
done by scoping, not machinery. ~40 lines, all in
`to_actor/_api.py`, zero `_supervise` involvement.
## Verification gate ## Verification gate
- `tests/test_cancellation.py test_spawning.py test_local.py - per-migration-commit module gates on `trio` (+ `mp_spawn`
test_rpc.py` on `trio` + `mp_spawn` + `mp_forkserver` spot-gates incl. `test_infected_asyncio` per the B2 lesson);
backends, then full suite; `tests/devx/test_debugger.py` `tests/devx/test_debugger.py` for the REPL flows.
for risk 3. - full suite on `trio` + `mp_spawn` at branch tip + CI matrix
via draft PR #484.

View File

@ -0,0 +1,81 @@
---
model: claude-fable-5
service: claude
session: 6db64ac6-6986-4505-9343-df4ee31e67db
timestamp: 2026-07-06T17:28:18Z
git_ref: ad42871e
scope: code
substantive: true
raw_file: 20260706T172818Z_ad42871e_prompt_io.raw.md
---
## Prompt
Resuming the `drop_ria_nursery` (wkt) branch after the human's
review pause at `ba4af0ad`, with the directive:
> we need to resolve the remaining outstandings ... to finish
> this endeavour we ideally,
> - finish the ria-tn hoist into the machinery in `to_actor` if
> possible.
> - avoid supporting the `.run_in_actor()` non-blocking API
> going forward other then if we can re-wrap `to_actor.run()`
> per prior discussion with opus4.8.
> - get the test harness completely green locally and in CI -
> AND ESPECIALLY without any hangs due to the "the old
> spawn-backend reaper raced `soft_kill()`'s scope-cancel"
> - update all docs to reflect these changes once we're happy.
plus mid-flight additions:
> make sure both all the test and example code uses the naming
> `an: ActorNursery` for variable names
> where we at in terms of a potential hackaround design for
> supporting a to_actor.run_async() ... without requiring the
> same malarky ... with a `trio.Event` relay ... i'm totes fine
> with rejigging the iface to NOT return a Portal as well.
## Response summary
Completed the entire #477 `run_in_actor` removal on
`drop_ria_nursery` (16 commits this session), gated per-commit:
- ported the remaining `test_cancellation` groups
(`some_cancels_all`, `nested_multierrors`+`spawn_and_error`,
SIGINT + sync-sleep) — one-shots become concurrent
`to_actor.run()` tasks in local task-nurseries; BEG-shape
assertions loosened for cancel-on-first + the runtime's
`collapse_eg()` single-member unwrap (a fully-raced nested
tree relays a bare annotated `RemoteActorError` chain).
- fixed a pre-existing `UnboundLocalError` (`timeout` `match`
had no default arm for non-trio/MTF backends).
- ported `test_dynamic_pub_sub`, 4 non-debugging examples, all
8 `debugging/` examples (debugger suite byte-identical,
28p/6s; `multi_subactors` introduces the "collect don't
cancel" reap-all replacement pattern), 8 docs pages + the
`experimental/_pubsub` docstring.
- EXCISED the API + cluster: `run_in_actor`,
`_reap_ria_portals`, `_cancel_after_result_on_exit`,
`Portal._submit_for_result/_expect_result_ctx/
wait_for_result/result`, `exhaust_portal`,
`cancel_on_completion`, `NoResult` — net -402 lines. The
reap-hang class dissolves structurally (result-waits now only
in caller task-scope).
- found + fixed a real migration race: mutual-rendezvous peers
(`test_trynamic_trio`, `a_trynamic_first_scene.py`) flaked
because an eagerly-reaped one-shot dies while its peer still
dials the registry-resolved (dead) sockaddr — such peers now
pin lifetimes via `start_actor()` + concurrent `Portal.run()`
+ explicit `an.cancel()`.
- `an: ActorNursery` naming sweep across tests/examples (±82
lines, scoped renames, prose untouched).
- parked a `to_actor.open_one_shot()` design sketch (acm +
private task-nursery over blocking `run()`; done-Event as
memo not cancel-relay; no Portal) in the plan doc.
## Files changed
See commits `d01a2123..ad42871e` on `drop_ria_nursery`
(tests, examples, docs, `tractor/{runtime,spawn,to_actor,msg}`
+ `_exceptions/_context/experimental`).

View File

@ -0,0 +1,39 @@
---
model: claude-fable-5
service: claude
timestamp: 2026-07-06T17:28:18Z
git_ref: ad42871e
diff_cmd: git diff ba4af0ad..ad42871e
---
# Raw AI output (diff-ref mode)
This session's output spans the 16 migration/excision commits
`d01a2123..ad42871e` on `drop_ria_nursery`; per diff-ref mode
the verbatim content is reachable via the pointer below.
## Generated files
> `git diff ba4af0ad..ad42871e`
Commit-wise (each `Gate:`-footed msg documents its own module
gate):
- `d01a2123` port `test_some_cancels_all`
- `697c6152` fix unbound `timeout` (non-trio/MTF `match` arm)
- `fa8799d5` port `test_nested_multierrors`
- `f11754ce` port SIGINT + sync-sleep cancel tests
- `cb6202e3` port `test_dynamic_pub_sub`
- `d8af5f12` port non-debugging examples
- `a3057cb2` port debugging examples (+ `test_debugger`
nested-nurseries final-shape expectations)
- `d6bed7c4` port docs (8 rst pages)
- `07e1669e` fix stale `@pub` docstring example
- `2a59cefb` REMOVE `run_in_actor()` + the ria reap cluster
(net -402 lines)
- `a297a32a` fix mutual-rendezvous premature-reap race
- `ad42871e` `an: ActorNursery` naming sweep
Plan/design record updated in
`ai/conc-anal/ria_nursery_removal_plan.md` (RESOLVED section +
the `to_actor.open_one_shot()` follow-up sketch).

View File

@ -17,36 +17,37 @@ async def say_hello(other_actor):
return await portal.run(hi) return await portal.run(hi)
async def run_and_print(
an: tractor.ActorNursery,
name: str,
other_actor: str,
):
print(
await tractor.to_actor.run(
say_hello,
an=an,
name=name,
# arguments are always named
other_actor=other_actor,
)
)
async def main(): async def main():
"""Main tractor entry point, the "master" process (for now """Main tractor entry point, the "master" process (for now
acts as the "director"). acts as the "director").
""" """
async with ( async with tractor.open_nursery() as an:
tractor.open_nursery() as an,
trio.open_nursery() as tn,
):
print("Alright... Action!") print("Alright... Action!")
# both actors wait on the *other* to register so their # both actors wait on (then dial!) the *other*, so each
# one-shots must run concurrently. # must outlive both hellos: spawn as daemons, run the
tn.start_soon(run_and_print, an, 'donny', 'gretchen') # hellos concurrently, reap only once both complete.
tn.start_soon(run_and_print, an, 'gretchen', 'donny') portals: dict[str, tractor.Portal] = {
name: await an.start_actor(
name,
enable_modules=[__name__],
)
for name in ('donny', 'gretchen')
}
async def run_and_print(name: str, other_actor: str):
print(
await portals[name].run(
say_hello,
other_actor=other_actor,
)
)
async with trio.open_nursery() as tn:
tn.start_soon(run_and_print, 'donny', 'gretchen')
tn.start_soon(run_and_print, 'gretchen', 'donny')
await an.cancel()
print("CUTTTT CUUTT CUT!!! Donny!! You're supposed to say...") print("CUTTTT CUUTT CUT!!! Donny!! You're supposed to say...")

View File

@ -12,9 +12,9 @@ async def movie_theatre_question():
async def main(): async def main():
"""The main ``tractor`` routine. """The main ``tractor`` routine.
""" """
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
portal = await n.start_actor( portal = await an.start_actor(
'frank', 'frank',
# enable the actor to run funcs from this current module # enable the actor to run funcs from this current module
enable_modules=[__name__], enable_modules=[__name__],

View File

@ -15,9 +15,9 @@ async def stream_forever() -> AsyncIterator[int]:
async def main(): async def main():
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
portal = await n.start_actor( portal = await an.start_actor(
'donny', 'donny',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -15,10 +15,10 @@ async def name_error():
async def spawn_error(): async def spawn_error():
""""A nested nursery that triggers another ``NameError``. """"A nested nursery that triggers another ``NameError``.
""" """
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
return await tractor.to_actor.run( return await tractor.to_actor.run(
name_error, name_error,
an=n, an=an,
name='name_error_1', name='name_error_1',
) )

View File

@ -17,10 +17,10 @@ async def name_error():
async def spawn_error(): async def spawn_error():
""""A nested nursery that triggers another ``NameError``. """"A nested nursery that triggers another ``NameError``.
""" """
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
return await tractor.to_actor.run( return await tractor.to_actor.run(
name_error, name_error,
an=n, an=an,
name='name_error_1', name='name_error_1',
) )

View File

@ -21,8 +21,8 @@ async def main() -> None:
async with tractor.open_nursery( async with tractor.open_nursery(
debug_mode=True, debug_mode=True,
) as n: ) as an:
portal = await n.start_actor( portal = await an.start_actor(
'ctx_child', 'ctx_child',
# XXX: we don't enable the current module in order # XXX: we don't enable the current module in order

View File

@ -6,14 +6,14 @@ async def die():
async def main(): async def main():
async with tractor.open_nursery() as tn: async with tractor.open_nursery() as an:
debug_actor = await tn.start_actor( debug_actor = await an.start_actor(
'debugged_boi', 'debugged_boi',
enable_modules=[__name__], enable_modules=[__name__],
debug_mode=True, debug_mode=True,
) )
crash_boi = await tn.start_actor( crash_boi = await an.start_actor(
'crash_boi', 'crash_boi',
enable_modules=[__name__], enable_modules=[__name__],
# debug_mode=True, # debug_mode=True,

View File

@ -17,11 +17,11 @@ async def main():
tractor.open_nursery( tractor.open_nursery(
debug_mode=True, debug_mode=True,
# loglevel='debug' # ?XXX required? # loglevel='debug' # ?XXX required?
) as n, ) as an,
trio.open_nursery() as tn, trio.open_nursery() as tn,
): ):
# spawn the actor.. # spawn the actor..
portal = await n.start_actor( portal = await an.start_actor(
'key_error', 'key_error',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -74,10 +74,10 @@ async def cancelled_before_pause(
async def main(): async def main():
async with tractor.open_nursery( async with tractor.open_nursery(
debug_mode=True, debug_mode=True,
) as n: ) as an:
await tractor.to_actor.run( await tractor.to_actor.run(
cancelled_before_pause, cancelled_before_pause,
an=n, an=an,
) )
# ensure the same works in the root actor! # ensure the same works in the root actor!

View File

@ -58,8 +58,8 @@ async def main():
debug_mode=True, debug_mode=True,
enable_transports=[tpt], enable_transports=[tpt],
loglevel='devx', loglevel='devx',
) as n: ) as an:
p = await n.start_actor( p = await an.start_actor(
'bp_boi', 'bp_boi',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -17,13 +17,13 @@ async def main():
async with tractor.open_nursery( async with tractor.open_nursery(
debug_mode=True, debug_mode=True,
loglevel='cancel', loglevel='cancel',
) as n: ) as an:
# parks awaiting a result which only arrives once the # parks awaiting a result which only arrives once the
# user quits (`BdbQuit`s) the child's REPL loop. # user quits (`BdbQuit`s) the child's REPL loop.
await tractor.to_actor.run( await tractor.to_actor.run(
breakpoint_forever, breakpoint_forever,
an=n, an=an,
) )

View File

@ -50,8 +50,8 @@ async def trio_to_aio_echo_server(
async def main(): async def main():
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
p = await n.start_actor( p = await an.start_actor(
'aio_server', 'aio_server',
enable_modules=[__name__], enable_modules=[__name__],
infect_asyncio=True, infect_asyncio=True,

View File

@ -29,9 +29,9 @@ async def main() -> None:
)) ))
await proc.wait() await proc.wait()
# await trio.sleep_forever() # await trio.sleep_forever()
# async with tractor.open_nursery() as n: # async with tractor.open_nursery() as an:
# portal = await n.start_actor( # portal = await an.start_actor(
# 'rpc_server', # 'rpc_server',
# enable_modules=[__name__], # enable_modules=[__name__],
# ) # )

View File

@ -55,7 +55,7 @@ async def worker_pool(workers=4):
Yes, the workers stay alive (and ready for work) until you close Yes, the workers stay alive (and ready for work) until you close
the context. the context.
""" """
async with tractor.open_nursery() as tn: async with tractor.open_nursery() as an:
portals = [] portals = []
snd_chan, recv_chan = trio.open_memory_channel(len(PRIMES)) snd_chan, recv_chan = trio.open_memory_channel(len(PRIMES))
@ -65,7 +65,7 @@ async def worker_pool(workers=4):
# this starts a new sub-actor (process + trio runtime) and # this starts a new sub-actor (process + trio runtime) and
# stores it's "portal" for later use to "submit jobs" (ugh). # stores it's "portal" for later use to "submit jobs" (ugh).
portals.append( portals.append(
await tn.start_actor( await an.start_actor(
f'worker_{i}', f'worker_{i}',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -80,10 +80,10 @@ async def worker_pool(workers=4):
async def send_result(func, value, portal): async def send_result(func, value, portal):
await snd_chan.send((value, await portal.run(func, n=value))) await snd_chan.send((value, await portal.run(func, n=value)))
async with trio.open_nursery() as n: async with trio.open_nursery() as tn:
for value, portal in zip(sequence, itertools.cycle(portals)): for value, portal in zip(sequence, itertools.cycle(portals)):
n.start_soon( tn.start_soon(
send_result, send_result,
worker_func, worker_func,
value, value,
@ -98,7 +98,7 @@ async def worker_pool(workers=4):
yield _map yield _map
# tear down all "workers" on pool close # tear down all "workers" on pool close
await tn.cancel() await an.cancel()
async def main(): async def main():

View File

@ -7,17 +7,17 @@ async def assert_err():
async def main(): async def main():
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
real_actors = [] real_actors = []
for i in range(3): for i in range(3):
real_actors.append(await n.start_actor( real_actors.append(await an.start_actor(
f'actor_{i}', f'actor_{i}',
enable_modules=[__name__], enable_modules=[__name__],
)) ))
# run one one-shot task actor that will fail immediately; # run one one-shot task actor that will fail immediately;
# its error raises right here in the caller's task.. # its error raises right here in the caller's task..
await tractor.to_actor.run(assert_err, an=n) await tractor.to_actor.run(assert_err, an=an)
# ..as a ``RemoteActorError`` containing an ``AssertionError`` # ..as a ``RemoteActorError`` containing an ``AssertionError``
# and all the other actors have been cancelled # and all the other actors have been cancelled

View File

@ -31,9 +31,9 @@ async def simple_rpc(
async def main() -> None: async def main() -> None:
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
portal = await n.start_actor( portal = await an.start_actor(
'rpc_server', 'rpc_server',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -46,9 +46,9 @@ async def test_reg_then_unreg(
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as n: ) as an:
portal = await n.start_actor('actor', enable_modules=[__name__]) portal = await an.start_actor('actor', enable_modules=[__name__])
uid = portal.channel.aid.uid uid = portal.channel.aid.uid
async with tractor.get_registry(reg_addr) as aportal: async with tractor.get_registry(reg_addr) as aportal:
@ -62,7 +62,7 @@ async def test_reg_then_unreg(
# XXX: can we figure out what the listen addr will be? # XXX: can we figure out what the listen addr will be?
assert sockaddrs assert sockaddrs
await n.cancel() # tear down nursery await an.cancel() # tear down nursery
await trio.sleep(0.1) await trio.sleep(0.1)
assert uid not in aportal.actor._registry assert uid not in aportal.actor._registry
@ -89,9 +89,9 @@ async def test_reg_then_unreg_maddr(
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[maddr_str], registry_addrs=[maddr_str],
) as n: ) as an:
portal = await n.start_actor( portal = await an.start_actor(
'actor_maddr', 'actor_maddr',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -105,7 +105,7 @@ async def test_reg_then_unreg_maddr(
sockaddrs = actor._registry[uid] sockaddrs = actor._registry[uid]
assert sockaddrs assert sockaddrs
await n.cancel() await an.cancel()
await trio.sleep(0.1) await trio.sleep(0.1)
assert uid not in aportal.actor._registry assert uid not in aportal.actor._registry
@ -152,27 +152,37 @@ async def test_trynamic_trio(
for the directed subs. for the directed subs.
''' '''
async with ( async with tractor.open_nursery() as an:
tractor.open_nursery() as n,
trio.open_nursery() as tn,
):
print("Alright... Action!") print("Alright... Action!")
# donny + gretchen each wait on the *other* to register, so # donny + gretchen each wait on (then dial!) the *other*, so
# they must run CONCURRENTLY — schedule both one-shots into a # both actors must OUTLIVE both hellos: spawn as daemons and
# local task-nursery (was two non-blocking `run_in_actor()`s). # only reap after both tasks complete. NB a pair of eagerly
# reaped `to_actor.run()` one-shots races: the first to
# finish dies while the other may still be dialing its
# registry-resolved (now dead) sockaddr -> conn-refused.
portals: dict[str, tractor.Portal] = {
name: await an.start_actor(
name,
enable_modules=[__name__],
)
for name in ('donny', 'gretchen')
}
async def _direct(this_name: str, other_actor: str): async def _direct(this_name: str, other_actor: str):
res = await tractor.to_actor.run( res = await portals[this_name].run(
ria_fn, ria_fn,
an=n,
other_actor=other_actor, other_actor=other_actor,
reg_addr=reg_addr, reg_addr=reg_addr,
name=this_name,
) )
print(res) print(res)
tn.start_soon(_direct, 'donny', 'gretchen') async with trio.open_nursery() as tn:
tn.start_soon(_direct, 'gretchen', 'donny') tn.start_soon(_direct, 'donny', 'gretchen')
tn.start_soon(_direct, 'gretchen', 'donny')
# both hellos have completed; reap the thespians.
await an.cancel()
print("CUTTTT CUUTT CUT!!?! Donny!! You're supposed to say...") print("CUTTTT CUUTT CUT!!?! Donny!! You're supposed to say...")

View File

@ -219,7 +219,7 @@ def test_dynamic_pub_sub(
tractor.open_nursery( tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
debug_mode=debug_mode, debug_mode=debug_mode,
) as n, ) as an,
# bg-schedules the forever-streaming # bg-schedules the forever-streaming
# one-shots below; the user-cancel raise # one-shots below; the user-cancel raise
# cancels them all, each reaping its # cancels them all, each reaping its
@ -237,7 +237,7 @@ def test_dynamic_pub_sub(
partial( partial(
tractor.to_actor.run, tractor.to_actor.run,
publisher, publisher,
an=n, an=an,
) )
) )
@ -249,7 +249,7 @@ def test_dynamic_pub_sub(
partial( partial(
tractor.to_actor.run, tractor.to_actor.run,
consumer, consumer,
an=n, an=an,
name=f'consumer_{sub}', name=f'consumer_{sub}',
subs=[sub], subs=[sub],
) )
@ -260,7 +260,7 @@ def test_dynamic_pub_sub(
partial( partial(
tractor.to_actor.run, tractor.to_actor.run,
consumer, consumer,
an=n, an=an,
name='consumer_dynamic', name='consumer_dynamic',
subs=list(_registry.keys()), subs=list(_registry.keys()),
) )
@ -370,10 +370,10 @@ def test_reqresp_ontopof_streaming():
timeout = 4 timeout = 4
with trio.move_on_after(timeout): with trio.move_on_after(timeout):
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
# name of this actor will be same as target func # name of this actor will be same as target func
portal = await n.start_actor( portal = await an.start_actor(
'dual_tasks', 'dual_tasks',
enable_modules=[__name__] enable_modules=[__name__]
) )
@ -436,9 +436,9 @@ def test_sigint_both_stream_types():
async def main(): async def main():
with trio.fail_after(timeout): with trio.fail_after(timeout):
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
# name of this actor will be same as target func # name of this actor will be same as target func
portal = await n.start_actor( portal = await an.start_actor(
'2_way', '2_way',
enable_modules=[__name__] enable_modules=[__name__]
) )
@ -551,8 +551,8 @@ def test_local_task_fanout_from_stream(
async with tractor.open_nursery( async with tractor.open_nursery(
debug_mode=debug_mode, debug_mode=debug_mode,
) as tn: ) as an:
p: tractor.Portal = await tn.start_actor( p: tractor.Portal = await an.start_actor(
'inf_streamer', 'inf_streamer',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -126,7 +126,7 @@ def test_remote_error(
async def main(): async def main():
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as nursery: ) as an:
# `to_actor.run()` blocks on the one-shot's result and # `to_actor.run()` blocks on the one-shot's result and
# raises the remote error directly here in the caller's # raises the remote error directly here in the caller's
@ -135,7 +135,7 @@ def test_remote_error(
try: try:
await tractor.to_actor.run( await tractor.to_actor.run(
assert_err, assert_err,
an=nursery, an=an,
name='errorer', name='errorer',
**args **args
) )
@ -248,16 +248,16 @@ def test_cancel_single_subactor(
''' '''
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as nursery: ) as an:
portal = await nursery.start_actor( portal = await an.start_actor(
'nothin', enable_modules=[__name__], 'nothin', enable_modules=[__name__],
) )
assert (await portal.run(do_nothing)) is None assert (await portal.run(do_nothing)) is None
if mechanism == 'nursery_cancel': if mechanism == 'nursery_cancel':
# would hang otherwise # would hang otherwise
await nursery.cancel() await an.cancel()
else: else:
raise mechanism raise mechanism
@ -289,8 +289,8 @@ async def test_cancel_infinite_streamer(
trio.fail_after(4), trio.fail_after(4),
trio.move_on_after(1) as cancel_scope trio.move_on_after(1) as cancel_scope
): ):
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
portal = await n.start_actor( portal = await an.start_actor(
'donny', 'donny',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -303,7 +303,7 @@ async def test_cancel_infinite_streamer(
# we support trio's cancellation system # we support trio's cancellation system
assert cancel_scope.cancelled_caught assert cancel_scope.cancelled_caught
assert n.cancel_called assert an.cancel_called
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -698,7 +698,7 @@ async def test_nested_multierrors(
async with fail_after_w_trace(timeout): async with fail_after_w_trace(timeout):
try: try:
async with ( async with (
tractor.open_nursery() as nursery, tractor.open_nursery() as an,
trio.open_nursery() as tn, trio.open_nursery() as tn,
): ):
for i in range(subactor_breadth): for i in range(subactor_breadth):
@ -706,7 +706,7 @@ async def test_nested_multierrors(
partial( partial(
tractor.to_actor.run, tractor.to_actor.run,
spawn_and_error, spawn_and_error,
an=nursery, an=an,
name=f'spawner_{i}', name=f'spawner_{i}',
breadth=subactor_breadth, breadth=subactor_breadth,
depth=depth, depth=depth,
@ -792,8 +792,8 @@ def test_cancel_via_SIGINT(
with trio.fail_after(2): with trio.fail_after(2):
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as tn: ) as an:
await tn.start_actor('sucka') await an.start_actor('sucka')
if 'mp' in start_method: if 'mp' in start_method:
time.sleep(0.1) time.sleep(0.1)
os.kill(pid, signal.SIGINT) os.kill(pid, signal.SIGINT)
@ -1054,8 +1054,8 @@ def test_fast_graceful_cancel_when_spawn_task_in_soft_proc_wait_for_daemon(
start = time.time() start = time.time()
try: try:
async with trio.open_nursery() as nurse: async with trio.open_nursery() as nurse:
async with tractor.open_nursery() as tn: async with tractor.open_nursery() as an:
p = await tn.start_actor( p = await an.start_actor(
'fast_boi', 'fast_boi',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -156,8 +156,8 @@ def test_actor_managed_trio_nursery_task_error_cancels_aio(
async def main(): async def main():
# cancel the nursery shortly after boot # cancel the nursery shortly after boot
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
p = await n.start_actor( p = await an.start_actor(
'nursery_mngr', 'nursery_mngr',
infect_asyncio=asyncio_mode, # TODO, is this enabling debug mode? infect_asyncio=asyncio_mode, # TODO, is this enabling debug mode?
enable_modules=[__name__], enable_modules=[__name__],

View File

@ -163,12 +163,12 @@ def test_do_not_swallow_error_before_started_by_remote_contextcancelled(
async def main(): async def main():
async with tractor.open_nursery( async with tractor.open_nursery(
debug_mode=debug_mode, debug_mode=debug_mode,
) as n: ) as an:
portal = await n.start_actor( portal = await an.start_actor(
'errorer', 'errorer',
enable_modules=[__name__], enable_modules=[__name__],
) )
await n.start_actor( await an.start_actor(
'sleeper', 'sleeper',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -139,9 +139,9 @@ async def test_required_args(callwith_expecterror):
with pytest.raises(err): with pytest.raises(err):
await func(**kwargs) await func(**kwargs)
else: else:
async with tractor.open_nursery() as n: async with tractor.open_nursery() as an:
portal = await n.start_actor( portal = await an.start_actor(
name='pubber', name='pubber',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -180,7 +180,7 @@ def test_multi_actor_subs_arbiter_pub(
tractor.open_nursery( tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
enable_modules=[__name__], enable_modules=[__name__],
) as n, ) as an,
trio.open_nursery() as tn, trio.open_nursery() as tn,
): ):
@ -188,7 +188,7 @@ def test_multi_actor_subs_arbiter_pub(
if pub_actor == 'streamer': if pub_actor == 'streamer':
# start the publisher as a daemon # start the publisher as a daemon
master_portal = await n.start_actor( master_portal = await an.start_actor(
'streamer', 'streamer',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -215,11 +215,11 @@ def test_multi_actor_subs_arbiter_pub(
): ):
pass # expected once we `cancel_actor()` below pass # expected once we `cancel_actor()` below
even_portal = await n.start_actor( even_portal = await an.start_actor(
'evens', 'evens',
enable_modules=[__name__], enable_modules=[__name__],
) )
odd_portal = await n.start_actor( odd_portal = await an.start_actor(
'odds', 'odds',
enable_modules=[__name__], enable_modules=[__name__],
) )
@ -294,9 +294,9 @@ def test_single_subactor_pub_multitask_subs(
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
enable_modules=[__name__], enable_modules=[__name__],
) as n: ) as an:
portal = await n.start_actor( portal = await an.start_actor(
'streamer', 'streamer',
enable_modules=[__name__], enable_modules=[__name__],
) )

View File

@ -107,13 +107,13 @@ def test_rpc_errors(
# do that if actually debugging subactor but keep it # do that if actually debugging subactor but keep it
# disabled for the test. # disabled for the test.
# debug_mode=True, # debug_mode=True,
) as n: ) as an:
actor = tractor.current_actor() actor = tractor.current_actor()
assert actor.is_registrar assert actor.is_registrar
await tractor.to_actor.run( await tractor.to_actor.run(
sleep_back_actor, sleep_back_actor,
an=n, an=an,
actor_name=subactor_requests_to, actor_name=subactor_requests_to,
name='subactor', name='subactor',

View File

@ -202,10 +202,10 @@ def test_loglevel_propagated_to_subactor(
start_method=start_method, start_method=start_method,
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as tn: ) as an:
await tractor.to_actor.run( await tractor.to_actor.run(
check_loglevel, check_loglevel,
an=tn, an=an,
loglevel=level, loglevel=level,
level=level, level=level,
) )