Repair weird spawn test, start `test_root_runtime`
There was a very strange legacy test `test_spawning.test_local_arbiter_subactor_global_state` which was causing unforseen hangs/errors on the UDS tpt and looking deeper this test was already doing root-actor things that should never have been valid XD So rework that test to properly demonstrate something of value (i guess..) and add a new suite which start more rigorously auditing our `open_root_actor()` permitted usage. For the old test, - since the main point of this test seemed to be the ability to invoke the same function in both the parent and child actor (using the very legacy `ActorNursery.run_in_actor()`.. due to be deprecated) rename it to `test_run_in_actor_same_func_in_child`, - don't re-enter `.open_root_actor()` since that's invalid usage (tested in new suite see below), - adjust some `spawn()` arg/var naming and ensure we only return in the child. For the new suite add tests for, - ensuring the implicit `open_root_actor()` call under `open_nursery()`. - double open of `open_root_actor()` from within the same process tree both from a root and sub. Intro some new `_exceptions` used in the new suite, - a top level `RuntimeFailure` for generically expressing faults not of our own doing that prevent successful operation; this is what we now (changed in this commit) raise on attempts to open a 2nd root. - mk `ActorFailure` derive from the former; it's already used from `._spawn` when subprocs fail to boot.leslies_extra_appendix
parent
a528d45a30
commit
7d537e60cc
|
@ -0,0 +1,85 @@
|
|||
'''
|
||||
Runtime boot/init sanity.
|
||||
|
||||
'''
|
||||
|
||||
import pytest
|
||||
import trio
|
||||
|
||||
import tractor
|
||||
from tractor._exceptions import RuntimeFailure
|
||||
|
||||
|
||||
@tractor.context
|
||||
async def open_new_root_in_sub(
|
||||
ctx: tractor.Context,
|
||||
) -> None:
|
||||
|
||||
async with tractor.open_root_actor():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'open_root_in',
|
||||
['root', 'sub'],
|
||||
ids='open_2nd_root_in={}'.format,
|
||||
)
|
||||
def test_only_one_root_actor(
|
||||
open_root_in: str,
|
||||
reg_addr: tuple,
|
||||
debug_mode: bool
|
||||
):
|
||||
'''
|
||||
Verify we specially fail whenever more then one root actor
|
||||
is attempted to be opened within an already opened tree.
|
||||
|
||||
'''
|
||||
async def main():
|
||||
async with tractor.open_nursery() as an:
|
||||
|
||||
if open_root_in == 'root':
|
||||
async with tractor.open_root_actor(
|
||||
registry_addrs=[reg_addr],
|
||||
):
|
||||
pass
|
||||
|
||||
ptl: tractor.Portal = await an.start_actor(
|
||||
name='bad_rooty_boi',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
|
||||
async with ptl.open_context(
|
||||
open_new_root_in_sub,
|
||||
) as (ctx, first):
|
||||
pass
|
||||
|
||||
if open_root_in == 'root':
|
||||
with pytest.raises(
|
||||
RuntimeFailure
|
||||
) as excinfo:
|
||||
trio.run(main)
|
||||
|
||||
else:
|
||||
with pytest.raises(
|
||||
tractor.RemoteActorError,
|
||||
) as excinfo:
|
||||
trio.run(main)
|
||||
|
||||
assert excinfo.value.boxed_type is RuntimeFailure
|
||||
|
||||
|
||||
def test_implicit_root_via_first_nursery(
|
||||
reg_addr: tuple,
|
||||
debug_mode: bool
|
||||
):
|
||||
'''
|
||||
The first `ActorNursery` open should implicitly call
|
||||
`_root.open_root_actor()`.
|
||||
|
||||
'''
|
||||
async def main():
|
||||
async with tractor.open_nursery() as an:
|
||||
assert an._implicit_runtime_started
|
||||
assert tractor.current_actor().aid.name == 'root'
|
||||
|
||||
trio.run(main)
|
|
@ -2,6 +2,7 @@
|
|||
Spawning basics
|
||||
|
||||
"""
|
||||
from functools import partial
|
||||
from typing import (
|
||||
Any,
|
||||
)
|
||||
|
@ -12,74 +13,95 @@ import tractor
|
|||
|
||||
from tractor._testing import tractor_test
|
||||
|
||||
data_to_pass_down = {'doggy': 10, 'kitty': 4}
|
||||
data_to_pass_down = {
|
||||
'doggy': 10,
|
||||
'kitty': 4,
|
||||
}
|
||||
|
||||
|
||||
async def spawn(
|
||||
is_arbiter: bool,
|
||||
should_be_root: bool,
|
||||
data: dict,
|
||||
reg_addr: tuple[str, int],
|
||||
|
||||
debug_mode: bool = False,
|
||||
):
|
||||
namespaces = [__name__]
|
||||
|
||||
await trio.sleep(0.1)
|
||||
actor = tractor.current_actor(err_on_no_runtime=False)
|
||||
|
||||
async with tractor.open_root_actor(
|
||||
arbiter_addr=reg_addr,
|
||||
):
|
||||
actor = tractor.current_actor()
|
||||
assert actor.is_arbiter == is_arbiter
|
||||
data = data_to_pass_down
|
||||
if should_be_root:
|
||||
assert actor is None # no runtime yet
|
||||
async with (
|
||||
tractor.open_root_actor(
|
||||
arbiter_addr=reg_addr,
|
||||
),
|
||||
tractor.open_nursery() as an,
|
||||
):
|
||||
# now runtime exists
|
||||
actor: tractor.Actor = tractor.current_actor()
|
||||
assert actor.is_arbiter == should_be_root
|
||||
|
||||
if actor.is_arbiter:
|
||||
async with tractor.open_nursery() as nursery:
|
||||
# spawns subproc here
|
||||
portal: tractor.Portal = await an.run_in_actor(
|
||||
fn=spawn,
|
||||
|
||||
# forks here
|
||||
portal = await nursery.run_in_actor(
|
||||
spawn,
|
||||
is_arbiter=False,
|
||||
name='sub-actor',
|
||||
data=data,
|
||||
reg_addr=reg_addr,
|
||||
enable_modules=namespaces,
|
||||
)
|
||||
# spawning args
|
||||
name='sub-actor',
|
||||
enable_modules=[__name__],
|
||||
|
||||
assert len(nursery._children) == 1
|
||||
assert portal.channel.uid in tractor.current_actor()._peers
|
||||
# be sure we can still get the result
|
||||
result = await portal.result()
|
||||
assert result == 10
|
||||
return result
|
||||
else:
|
||||
return 10
|
||||
# passed to a subactor-recursive RPC invoke
|
||||
# of this same `spawn()` fn.
|
||||
should_be_root=False,
|
||||
data=data_to_pass_down,
|
||||
reg_addr=reg_addr,
|
||||
)
|
||||
|
||||
assert len(an._children) == 1
|
||||
assert portal.channel.uid in tractor.current_actor()._peers
|
||||
|
||||
# get result from child subactor
|
||||
result = await portal.result()
|
||||
assert result == 10
|
||||
return result
|
||||
else:
|
||||
assert actor.is_arbiter == should_be_root
|
||||
return 10
|
||||
|
||||
|
||||
def test_local_arbiter_subactor_global_state(
|
||||
reg_addr,
|
||||
def test_run_in_actor_same_func_in_child(
|
||||
reg_addr: tuple,
|
||||
debug_mode: bool,
|
||||
):
|
||||
result = trio.run(
|
||||
spawn,
|
||||
True,
|
||||
data_to_pass_down,
|
||||
reg_addr,
|
||||
partial(
|
||||
spawn,
|
||||
should_be_root=True,
|
||||
data=data_to_pass_down,
|
||||
reg_addr=reg_addr,
|
||||
debug_mode=debug_mode,
|
||||
)
|
||||
)
|
||||
assert result == 10
|
||||
|
||||
|
||||
async def movie_theatre_question():
|
||||
"""A question asked in a dark theatre, in a tangent
|
||||
'''
|
||||
A question asked in a dark theatre, in a tangent
|
||||
(errr, I mean different) process.
|
||||
"""
|
||||
|
||||
'''
|
||||
return 'have you ever seen a portal?'
|
||||
|
||||
|
||||
@tractor_test
|
||||
async def test_movie_theatre_convo(start_method):
|
||||
"""The main ``tractor`` routine.
|
||||
"""
|
||||
async with tractor.open_nursery(debug_mode=True) as n:
|
||||
'''
|
||||
The main ``tractor`` routine.
|
||||
|
||||
portal = await n.start_actor(
|
||||
'''
|
||||
async with tractor.open_nursery(debug_mode=True) as an:
|
||||
|
||||
portal = await an.start_actor(
|
||||
'frank',
|
||||
# enable the actor to run funcs from this current module
|
||||
enable_modules=[__name__],
|
||||
|
@ -118,8 +140,8 @@ async def test_most_beautiful_word(
|
|||
with trio.fail_after(1):
|
||||
async with tractor.open_nursery(
|
||||
debug_mode=debug_mode,
|
||||
) as n:
|
||||
portal = await n.run_in_actor(
|
||||
) as an:
|
||||
portal = await an.run_in_actor(
|
||||
cellar_door,
|
||||
return_value=return_value,
|
||||
name='some_linguist',
|
||||
|
|
|
@ -72,8 +72,22 @@ log = get_logger('tractor')
|
|||
_this_mod = importlib.import_module(__name__)
|
||||
|
||||
|
||||
class ActorFailure(Exception):
|
||||
"General actor failure"
|
||||
class RuntimeFailure(RuntimeError):
|
||||
'''
|
||||
General `Actor`-runtime failure due to,
|
||||
|
||||
- a bad runtime-env,
|
||||
- falied spawning (bad input to process),
|
||||
- API usage.
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class ActorFailure(RuntimeFailure):
|
||||
'''
|
||||
`Actor` failed to boot before/after spawn
|
||||
|
||||
'''
|
||||
|
||||
|
||||
class InternalError(RuntimeError):
|
||||
|
|
|
@ -60,7 +60,7 @@ from ._addr import (
|
|||
wrap_address,
|
||||
)
|
||||
from ._exceptions import (
|
||||
ActorFailure,
|
||||
RuntimeFailure,
|
||||
is_multi_cancelled,
|
||||
)
|
||||
|
||||
|
@ -195,7 +195,7 @@ async def open_root_actor(
|
|||
rtvs: dict[str, Any] = _state._runtime_vars
|
||||
root_mailbox: list[str, int] = rtvs['_root_mailbox']
|
||||
registry_addrs: list[list[str, int]] = rtvs['_registry_addrs']
|
||||
raise ActorFailure(
|
||||
raise RuntimeFailure(
|
||||
f'A current actor already exists !?\n'
|
||||
f'({already_actor}\n'
|
||||
f'\n'
|
||||
|
|
Loading…
Reference in New Issue