Compare commits

..

No commits in common. "efcac594c1012470f477785593e7bbfb7bccd334" and "e617b498ecc98e713551f5bf9d73fec7d981b3db" have entirely different histories.

6 changed files with 123 additions and 190 deletions

View File

@ -152,27 +152,23 @@ async def test_trynamic_trio(
for the directed subs. for the directed subs.
''' '''
async with ( async with tractor.open_nursery() as n:
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 = await n.run_in_actor(
# they must run CONCURRENTLY — schedule both one-shots into a
# local task-nursery (was two non-blocking `run_in_actor()`s).
async def _direct(this_name: str, other_actor: str):
res = await tractor.to_actor.run(
ria_fn, ria_fn,
an=n, other_actor='gretchen',
other_actor=other_actor,
reg_addr=reg_addr, reg_addr=reg_addr,
name=this_name, name='donny',
) )
print(res) gretchen = await n.run_in_actor(
ria_fn,
tn.start_soon(_direct, 'donny', 'gretchen') other_actor='donny',
tn.start_soon(_direct, 'gretchen', 'donny') reg_addr=reg_addr,
name='gretchen',
)
print(await gretchen.result())
print(await donny.result())
print("CUTTTT CUUTT CUT!!?! Donny!! You're supposed to say...") print("CUTTTT CUUTT CUT!!?! Donny!! You're supposed to say...")
@ -274,15 +270,13 @@ async def spawn_and_check_registry(
portals = {} portals = {}
for i in range(3): for i in range(3):
name = f'a{i}' name = f'a{i}'
# a daemon subactor is alive + registered if with_streaming:
# without a "main" task; the streaming
# branch below uses the module funcs, the
# non-streaming case just needs it up (was
# `run_in_actor(trio.sleep_forever)`).
portals[name] = await an.start_actor( portals[name] = await an.start_actor(
name=name, name=name, enable_modules=[__name__])
enable_modules=[__name__],
) else: # no streaming
portals[name] = await an.run_in_actor(
trio.sleep_forever, name=name)
# wait on last actor to come up # wait on last actor to come up
async with tractor.wait_for_actor(name): async with tractor.wait_for_actor(name):

View File

@ -24,7 +24,6 @@ from tractor import (
current_actor, current_actor,
Actor, Actor,
to_asyncio, to_asyncio,
to_actor,
RemoteActorError, RemoteActorError,
ContextCancelled, ContextCancelled,
) )
@ -111,9 +110,8 @@ def test_trio_cancels_aio_on_actor_side(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
debug_mode=debug_mode, debug_mode=debug_mode,
) as an: ) as an:
await to_actor.run( await an.run_in_actor(
trio_cancels_single_aio_task, trio_cancels_single_aio_task,
an=an,
infect_asyncio=True, infect_asyncio=True,
) )
@ -159,28 +157,6 @@ async def asyncio_actor(
raise raise
@tractor.context
async def sleep_forever_aio_ctx(
ctx: tractor.Context,
expect_err: str = 'trio.Cancelled',
) -> None:
'''
`@context` shim so a parent can spawn a forever-sleeping
infected-`asyncio` task via `Portal.open_context()` and cancel it
(via `Portal.cancel_actor()` or an enclosing `trio` cancel scope),
asserting the graceful `trio.Cancelled` teardown.
Replaces the legacy `ActorNursery.run_in_actor()` spawn the
aio-cancel tests below used to rely on (removed with #477).
'''
await ctx.started()
await asyncio_actor(
target='aio_sleep_forever',
expect_err=expect_err,
)
def test_aio_simple_error( def test_aio_simple_error(
reg_addr: tuple[str, int], reg_addr: tuple[str, int],
debug_mode: bool, debug_mode: bool,
@ -196,9 +172,8 @@ def test_aio_simple_error(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
debug_mode=debug_mode, debug_mode=debug_mode,
) as an: ) as an:
await to_actor.run( await an.run_in_actor(
asyncio_actor, asyncio_actor,
an=an,
target='sleep_and_err', target='sleep_and_err',
expect_err='AssertionError', expect_err='AssertionError',
infect_asyncio=True, infect_asyncio=True,
@ -232,34 +207,18 @@ def test_tractor_cancels_aio(
''' '''
async def main(): async def main():
# anti-hang wall-clock cap: a per-test `trio.fail_after`
# is the blessed guard here since `pytest-timeout`'s
# global cap is intentionally off (see the `pyproject`
# NOTE — it breaks trio under fork backends). Generous +
# CPU-headroom-scaled bc this is an anti-hang guard, not
# a perf assertion; a wedged ria-reaper once hung this
# test forever (the `._ria_nursery`-removal regression).
from .conftest import cpu_perf_headroom
with trio.fail_after(9 * cpu_perf_headroom()):
async with tractor.open_nursery( async with tractor.open_nursery(
debug_mode=debug_mode, debug_mode=debug_mode,
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as an: ) as an:
p: tractor.Portal = await an.start_actor( portal = await an.run_in_actor(
'aio_daemon', asyncio_actor,
enable_modules=[__name__], target='aio_sleep_forever',
expect_err='trio.Cancelled',
infect_asyncio=True, infect_asyncio=True,
) )
async with ( # cancel the entire remote runtime
# `.cancel_actor()` below tears the ctx down await portal.cancel_actor()
expect_ctxc(yay=True),
p.open_context(
sleep_forever_aio_ctx,
) as (ctx, first),
):
# cancel the entire remote runtime while its
# infected-`asyncio` task sleeps forever
await p.cancel_actor()
trio.run(main) trio.run(main)
@ -277,19 +236,13 @@ def test_trio_cancels_aio(
with trio.move_on_after(1): with trio.move_on_after(1):
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as an: ) as tn:
p: tractor.Portal = await an.start_actor( await tn.run_in_actor(
'aio_daemon', asyncio_actor,
enable_modules=[__name__], target='aio_sleep_forever',
expect_err='trio.Cancelled',
infect_asyncio=True, infect_asyncio=True,
) )
async with p.open_context(
sleep_forever_aio_ctx,
) as (ctx, first):
# block until the enclosing `move_on_after`
# cancels this `trio` scope, tearing down the
# infected-aio task via ctx cancellation
await trio.sleep_forever()
trio.run(main) trio.run(main)
@ -439,16 +392,17 @@ def test_aio_cancelled_from_aio_causes_trio_cancelled(
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as an: ) as an:
# `to_actor.run()` blocks on the one-shot's result and p: tractor.Portal = await an.run_in_actor(
# relays the remote error here in the caller's task.
with trio.fail_after(1 + delay):
await to_actor.run(
asyncio_actor, asyncio_actor,
an=an,
target='aio_cancel', target='aio_cancel',
expect_err='tractor.to_asyncio.AsyncioCancelled', expect_err='tractor.to_asyncio.AsyncioCancelled',
infect_asyncio=True, infect_asyncio=True,
) )
# NOTE: normally the `an.__aexit__()` waits on the
# portal's result but we do it explicitly here
# to avoid indent levels.
with trio.fail_after(1 + delay):
await p.wait_for_result()
with pytest.raises( with pytest.raises(
expected_exception=(RemoteActorError, ExceptionGroup), expected_exception=(RemoteActorError, ExceptionGroup),
@ -649,13 +603,13 @@ def test_basic_interloop_channel_stream(
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as an: ) as an:
# should raise RAE diectly portal = await an.run_in_actor(
await to_actor.run(
stream_from_aio, stream_from_aio,
an=an,
infect_asyncio=True, infect_asyncio=True,
fan_out=fan_out, fan_out=fan_out,
) )
# should raise RAE diectly
await portal.result()
trio.run(main) trio.run(main)
@ -668,13 +622,13 @@ def test_trio_error_cancels_intertask_chan(
async with tractor.open_nursery( async with tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as an: ) as an:
# should trigger remote actor error portal = await an.run_in_actor(
await to_actor.run(
stream_from_aio, stream_from_aio,
an=an,
trio_raise_err=True, trio_raise_err=True,
infect_asyncio=True, infect_asyncio=True,
) )
# should trigger remote actor error
await portal.result()
with pytest.raises(RemoteActorError) as excinfo: with pytest.raises(RemoteActorError) as excinfo:
trio.run(main) trio.run(main)
@ -704,14 +658,14 @@ def test_trio_closes_early_causes_aio_checkpoint_raise(
# enable_stack_on_sig=True, # enable_stack_on_sig=True,
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as an: ) as an:
# should raise RAE diectly portal = await an.run_in_actor(
print('waiting on final infected subactor result..')
res: None = await to_actor.run(
stream_from_aio, stream_from_aio,
an=an,
trio_exit_early=True, trio_exit_early=True,
infect_asyncio=True, infect_asyncio=True,
) )
# should raise RAE diectly
print('waiting on final infected subactor result..')
res: None = await portal.wait_for_result()
assert res is None assert res is None
print(f'infected subactor returned result: {res!r}\n') print(f'infected subactor returned result: {res!r}\n')
@ -755,15 +709,15 @@ def test_aio_exits_early_relays_AsyncioTaskExited(
debug_mode=debug_mode, debug_mode=debug_mode,
# enable_stack_on_sig=True, # enable_stack_on_sig=True,
) as an: ) as an:
# should raise RAE diectly portal = await an.run_in_actor(
print('waiting on final infected subactor result..')
res: None = await to_actor.run(
stream_from_aio, stream_from_aio,
an=an,
infect_asyncio=True, infect_asyncio=True,
trio_exit_early=False, trio_exit_early=False,
aio_exit_early=True, aio_exit_early=True,
) )
# should raise RAE diectly
print('waiting on final infected subactor result..')
res: None = await portal.wait_for_result()
assert res is None assert res is None
print(f'infected subactor returned result: {res!r}\n') print(f'infected subactor returned result: {res!r}\n')
@ -795,19 +749,17 @@ def test_aio_errors_and_channel_propagates_and_closes(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
debug_mode=debug_mode, debug_mode=debug_mode,
) as an: ) as an:
# should trigger RAE directly, not an eg. portal = await an.run_in_actor(
await to_actor.run(
stream_from_aio, stream_from_aio,
an=an,
aio_raise_err=True, aio_raise_err=True,
infect_asyncio=True, infect_asyncio=True,
) )
# should trigger RAE directly, not an eg.
await portal.result()
with pytest.raises( with pytest.raises(
# NOTE: bc `to_actor.run()` blocks on + relays the result # NOTE: bc we directly wait on `Portal.result()` instead
# in the caller's task (not captured inside the # of capturing it inside the `ActorNursery` machinery.
# `ActorNursery` teardown machinery) we get a direct RAE,
# not an eg.
expected_exception=RemoteActorError, expected_exception=RemoteActorError,
) as excinfo: ) as excinfo:
trio.run(main) trio.run(main)

View File

@ -176,13 +176,10 @@ def test_multi_actor_subs_arbiter_pub(
async def main(): async def main():
async with ( async with tractor.open_nursery(
tractor.open_nursery(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
enable_modules=[__name__], enable_modules=[__name__],
) as n, ) as n:
trio.open_nursery() as tn,
):
name = 'root' name = 'root'
@ -194,37 +191,18 @@ def test_multi_actor_subs_arbiter_pub(
) )
name = 'streamer' name = 'streamer'
# spawn the two subscriber actors as daemons and run even_portal = await n.run_in_actor(
# `subs()` on each as a background task (was the legacy
# `run_in_actor()`); keep the portals for the explicit
# `cancel_actor()` teardown below. Each runner swallows
# the teardown error that `cancel_actor()` relays.
async def _run_subs(
portal: tractor.Portal,
which: list[str],
) -> None:
try:
await portal.run(
subs, subs,
which=which, which=['even'],
pub_actor_name=name, name='evens',
pub_actor_name=name
) )
except ( odd_portal = await n.run_in_actor(
tractor.RemoteActorError, subs,
tractor.ContextCancelled, which=['odd'],
): name='odds',
pass # expected once we `cancel_actor()` below pub_actor_name=name
even_portal = await n.start_actor(
'evens',
enable_modules=[__name__],
) )
odd_portal = await n.start_actor(
'odds',
enable_modules=[__name__],
)
tn.start_soon(_run_subs, even_portal, ['even'])
tn.start_soon(_run_subs, odd_portal, ['odd'])
async with tractor.wait_for_actor('evens'): async with tractor.wait_for_actor('evens'):
# block until 2nd actor is initialized # block until 2nd actor is initialized
@ -279,9 +257,6 @@ def test_multi_actor_subs_arbiter_pub(
else: else:
await master_portal.cancel_actor() await master_portal.cancel_actor()
# drop the bg `subs()` runners now the subs are cancelled
tn.cancel_scope.cancel()
trio.run(main) trio.run(main)

View File

@ -111,9 +111,8 @@ def test_rpc_errors(
actor = tractor.current_actor() actor = tractor.current_actor()
assert actor.is_registrar assert actor.is_registrar
await tractor.to_actor.run( await n.run_in_actor(
sleep_back_actor, sleep_back_actor,
an=n,
actor_name=subactor_requests_to, actor_name=subactor_requests_to,
name='subactor', name='subactor',

View File

@ -78,12 +78,13 @@ async def test_lifetime_stack_wipes_tmpfile(
async with tractor.open_nursery( async with tractor.open_nursery(
loglevel=loglevel, loglevel=loglevel,
) as an: ) as an:
await tractor.to_actor.run( await ( # inlined `tractor.Portal`
await an.run_in_actor(
crash_and_clean_tmpdir, crash_and_clean_tmpdir,
an=an,
tmp_file_path=path, tmp_file_path=path,
error=error_in_child, error=error_in_child,
) )
).result()
except ( except (
tractor.RemoteActorError, tractor.RemoteActorError,
BaseExceptionGroup, BaseExceptionGroup,

View File

@ -48,11 +48,9 @@ async def spawn(
actor: tractor.Actor = tractor.current_actor() actor: tractor.Actor = tractor.current_actor()
assert actor.is_registrar == should_be_root assert actor.is_registrar == should_be_root
# recursively spawn this same `spawn()` fn as the lone # spawns subproc here
# task of a one-shot child subactor and get its result. portal: tractor.Portal = await an.run_in_actor(
result = await tractor.to_actor.run( fn=spawn,
spawn,
an=an,
# spawning args # spawning args
name='sub-actor', name='sub-actor',
@ -64,6 +62,16 @@ async def spawn(
data=data_to_pass_down, data=data_to_pass_down,
reg_addr=reg_addr, reg_addr=reg_addr,
) )
assert len(an._children) == 1
assert (
portal.channel.aid.uid
in
tractor.current_actor().ipc_server._peers
)
# get result from child subactor
result = await portal.result()
assert result == 10 assert result == 10
return result return result
else: else:
@ -71,7 +79,7 @@ async def spawn(
return 10 return 10
def test_to_actor_run_same_func_in_child( def test_run_in_actor_same_func_in_child(
reg_addr: tuple, reg_addr: tuple,
debug_mode: bool, debug_mode: bool,
): ):
@ -151,16 +159,21 @@ async def test_most_beautiful_word(
async with tractor.open_nursery( async with tractor.open_nursery(
debug_mode=debug_mode, debug_mode=debug_mode,
) as an: ) as an:
res: Any = await tractor.to_actor.run( portal = await an.run_in_actor(
cellar_door, cellar_door,
an=an,
return_value=return_value, return_value=return_value,
name='some_linguist', name='some_linguist',
) )
res: Any = await portal.wait_for_result()
assert res == return_value
# The ``async with`` will unblock here since the 'some_linguist'
# actor has completed its main task ``cellar_door``.
# this should pull the cached final result already captured during
# the nursery block exit.
res: Any = await portal.wait_for_result()
assert res == return_value assert res == return_value
# The ``async with`` unblocks here — the 'some_linguist'
# one-shot actor completed its lone task ``cellar_door`` and
# was reaped by `to_actor.run()`.
print(res) print(res)
@ -203,9 +216,8 @@ def test_loglevel_propagated_to_subactor(
registry_addrs=[reg_addr], registry_addrs=[reg_addr],
) as tn: ) as tn:
await tractor.to_actor.run( await tn.run_in_actor(
check_loglevel, check_loglevel,
an=tn,
loglevel=level, loglevel=level,
level=level, level=level,
) )
@ -255,11 +267,11 @@ async def check_parent_main_inheritance(
return has_data return has_data
def test_to_actor_run_can_skip_parent_main_inheritance( def test_run_in_actor_can_skip_parent_main_inheritance(
start_method: str, # <- only support on `trio` backend rn. start_method: str, # <- only support on `trio` backend rn.
): ):
''' '''
Verify ``inherit_parent_main=False`` on ``to_actor.run()`` Verify ``inherit_parent_main=False`` on ``run_in_actor()``
prevents parent ``__main__`` data from reaching the child. prevents parent ``__main__`` data from reaching the child.
''' '''
@ -272,21 +284,21 @@ def test_to_actor_run_can_skip_parent_main_inheritance(
async with tractor.open_nursery(start_method='trio') as an: async with tractor.open_nursery(start_method='trio') as an:
# Default: child receives parent __main__ bootstrap data # Default: child receives parent __main__ bootstrap data
await tractor.to_actor.run( replaying = await an.run_in_actor(
check_parent_main_inheritance, check_parent_main_inheritance,
an=an,
name='replaying-parent-main', name='replaying-parent-main',
expect_inherited=True, expect_inherited=True,
) )
await replaying.result()
# Opt-out: child gets no parent __main__ data # Opt-out: child gets no parent __main__ data
await tractor.to_actor.run( isolated = await an.run_in_actor(
check_parent_main_inheritance, check_parent_main_inheritance,
an=an,
name='isolated-parent-main', name='isolated-parent-main',
inherit_parent_main=False, inherit_parent_main=False,
expect_inherited=False, expect_inherited=False,
) )
await isolated.result()
trio.run(main) trio.run(main)