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-codedrop_ria_nursery
parent
2a59cefbe8
commit
a297a32af6
|
|
@ -17,36 +17,37 @@ async def say_hello(other_actor):
|
|||
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():
|
||||
"""Main tractor entry point, the "master" process (for now
|
||||
acts as the "director").
|
||||
"""
|
||||
async with (
|
||||
tractor.open_nursery() as an,
|
||||
trio.open_nursery() as tn,
|
||||
):
|
||||
async with tractor.open_nursery() as an:
|
||||
print("Alright... Action!")
|
||||
|
||||
# both actors wait on the *other* to register so their
|
||||
# one-shots must run concurrently.
|
||||
tn.start_soon(run_and_print, an, 'donny', 'gretchen')
|
||||
tn.start_soon(run_and_print, an, 'gretchen', 'donny')
|
||||
# both actors wait on (then dial!) the *other*, so each
|
||||
# must outlive both hellos: spawn as daemons, run the
|
||||
# hellos concurrently, reap only once both complete.
|
||||
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...")
|
||||
|
||||
|
|
|
|||
|
|
@ -152,27 +152,37 @@ async def test_trynamic_trio(
|
|||
for the directed subs.
|
||||
|
||||
'''
|
||||
async with (
|
||||
tractor.open_nursery() as n,
|
||||
trio.open_nursery() as tn,
|
||||
):
|
||||
async with tractor.open_nursery() as an:
|
||||
print("Alright... Action!")
|
||||
|
||||
# donny + gretchen each wait on the *other* to register, so
|
||||
# they must run CONCURRENTLY — schedule both one-shots into a
|
||||
# local task-nursery (was two non-blocking `run_in_actor()`s).
|
||||
# donny + gretchen each wait on (then dial!) the *other*, so
|
||||
# both actors must OUTLIVE both hellos: spawn as daemons and
|
||||
# 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):
|
||||
res = await tractor.to_actor.run(
|
||||
res = await portals[this_name].run(
|
||||
ria_fn,
|
||||
an=n,
|
||||
other_actor=other_actor,
|
||||
reg_addr=reg_addr,
|
||||
name=this_name,
|
||||
)
|
||||
print(res)
|
||||
|
||||
async with trio.open_nursery() as tn:
|
||||
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...")
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue