2018-09-08 13:44:29 +00:00
|
|
|
"""
|
2021-02-24 20:02:02 +00:00
|
|
|
Multiple python programs invoking the runtime.
|
2018-09-08 13:44:29 +00:00
|
|
|
"""
|
2026-02-11 22:43:05 +00:00
|
|
|
from __future__ import annotations
|
2019-03-31 00:59:10 +00:00
|
|
|
import platform
|
2026-02-11 22:43:05 +00:00
|
|
|
import subprocess
|
2018-09-08 13:44:29 +00:00
|
|
|
import time
|
2026-02-11 22:43:05 +00:00
|
|
|
from typing import (
|
|
|
|
|
TYPE_CHECKING,
|
|
|
|
|
)
|
2018-09-08 13:44:29 +00:00
|
|
|
|
|
|
|
|
import pytest
|
2021-02-24 20:02:02 +00:00
|
|
|
import trio
|
2018-09-08 13:44:29 +00:00
|
|
|
import tractor
|
2024-03-12 19:48:20 +00:00
|
|
|
from tractor._testing import (
|
2020-08-03 18:49:46 +00:00
|
|
|
tractor_test,
|
2024-03-12 19:48:20 +00:00
|
|
|
)
|
2026-02-11 22:43:05 +00:00
|
|
|
from tractor import (
|
2026-02-11 23:20:59 +00:00
|
|
|
current_actor,
|
|
|
|
|
_state,
|
2026-02-11 22:43:05 +00:00
|
|
|
Actor,
|
|
|
|
|
Context,
|
|
|
|
|
Portal,
|
|
|
|
|
)
|
2025-03-19 00:55:21 +00:00
|
|
|
from .conftest import (
|
2020-08-03 18:49:46 +00:00
|
|
|
sig_prog,
|
|
|
|
|
_INT_SIGNAL,
|
|
|
|
|
_INT_RETURN_CODE,
|
|
|
|
|
)
|
2018-09-08 13:44:29 +00:00
|
|
|
|
2026-02-11 22:43:05 +00:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from tractor.msg import Aid
|
|
|
|
|
from tractor._addr import (
|
|
|
|
|
UnwrappedAddress,
|
|
|
|
|
)
|
2018-09-08 13:44:29 +00:00
|
|
|
|
2026-02-11 22:43:05 +00:00
|
|
|
|
|
|
|
|
def test_abort_on_sigint(
|
|
|
|
|
daemon: subprocess.Popen,
|
|
|
|
|
):
|
2018-09-08 13:44:29 +00:00
|
|
|
assert daemon.returncode is None
|
|
|
|
|
time.sleep(0.1)
|
2019-03-10 03:48:50 +00:00
|
|
|
sig_prog(daemon, _INT_SIGNAL)
|
|
|
|
|
assert daemon.returncode == _INT_RETURN_CODE
|
2020-08-03 18:49:46 +00:00
|
|
|
|
2018-09-08 13:44:29 +00:00
|
|
|
# XXX: oddly, couldn't get capfd.readouterr() to work here?
|
2019-03-10 03:48:50 +00:00
|
|
|
if platform.system() != 'Windows':
|
|
|
|
|
# don't check stderr on windows as its empty when sending CTRL_C_EVENT
|
|
|
|
|
assert "KeyboardInterrupt" in str(daemon.stderr.read())
|
2018-09-08 13:44:29 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@tractor_test
|
2026-02-11 22:43:05 +00:00
|
|
|
async def test_cancel_remote_arbiter(
|
|
|
|
|
daemon: subprocess.Popen,
|
|
|
|
|
reg_addr: UnwrappedAddress,
|
|
|
|
|
):
|
2026-02-11 23:20:59 +00:00
|
|
|
assert not current_actor().is_arbiter
|
2025-03-23 03:14:04 +00:00
|
|
|
async with tractor.get_registry(reg_addr) as portal:
|
2018-09-08 13:44:29 +00:00
|
|
|
await portal.cancel_actor()
|
|
|
|
|
|
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
# the arbiter channel server is cancelled but not its main task
|
|
|
|
|
assert daemon.returncode is None
|
|
|
|
|
|
|
|
|
|
# no arbiter socket should exist
|
|
|
|
|
with pytest.raises(OSError):
|
2025-03-23 03:14:04 +00:00
|
|
|
async with tractor.get_registry(reg_addr) as portal:
|
2018-09-08 13:44:29 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2026-02-11 22:43:05 +00:00
|
|
|
def test_register_duplicate_name(
|
|
|
|
|
daemon: subprocess.Popen,
|
|
|
|
|
reg_addr: UnwrappedAddress,
|
|
|
|
|
):
|
2018-11-26 16:20:53 +00:00
|
|
|
async def main():
|
2021-02-24 20:02:02 +00:00
|
|
|
async with tractor.open_nursery(
|
2025-03-20 21:50:22 +00:00
|
|
|
registry_addrs=[reg_addr],
|
2026-02-11 22:43:05 +00:00
|
|
|
) as an:
|
2021-02-24 20:02:02 +00:00
|
|
|
|
2026-02-11 23:20:59 +00:00
|
|
|
assert not current_actor().is_arbiter
|
2021-02-24 20:02:02 +00:00
|
|
|
|
2026-02-11 22:43:05 +00:00
|
|
|
p1 = await an.start_actor('doggy')
|
|
|
|
|
p2 = await an.start_actor('doggy')
|
2018-11-26 16:20:53 +00:00
|
|
|
|
|
|
|
|
async with tractor.wait_for_actor('doggy') as portal:
|
|
|
|
|
assert portal.channel.uid in (p2.channel.uid, p1.channel.uid)
|
2018-09-08 13:44:29 +00:00
|
|
|
|
2026-02-11 22:43:05 +00:00
|
|
|
await an.cancel()
|
|
|
|
|
|
|
|
|
|
# XXX, run manually since we want to start this root **after**
|
|
|
|
|
# the other "daemon" program with it's own root.
|
|
|
|
|
trio.run(main)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@tractor.context
|
|
|
|
|
async def get_root_portal(
|
|
|
|
|
ctx: Context,
|
|
|
|
|
):
|
|
|
|
|
'''
|
|
|
|
|
Connect back to the root actor manually (using `._discovery` API)
|
|
|
|
|
and ensure it's contact info is the same as our immediate parent.
|
|
|
|
|
|
|
|
|
|
'''
|
2026-02-11 23:20:59 +00:00
|
|
|
sub: Actor = current_actor()
|
|
|
|
|
rtvs: dict = _state._runtime_vars
|
|
|
|
|
raddrs: list[UnwrappedAddress] = rtvs['_root_addrs']
|
|
|
|
|
|
|
|
|
|
# await tractor.pause()
|
|
|
|
|
# XXX, in case the sub->root discovery breaks you might need
|
|
|
|
|
# this (i know i did Xp)!!
|
|
|
|
|
# from tractor.devx import mk_pdb
|
|
|
|
|
# mk_pdb().set_trace()
|
|
|
|
|
|
|
|
|
|
assert (
|
|
|
|
|
len(raddrs) == 1
|
|
|
|
|
and
|
|
|
|
|
list(sub._parent_chan.raddr.unwrap()) in raddrs
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# connect back to our immediate parent which should also
|
|
|
|
|
# be the actor-tree's root.
|
2026-02-11 22:43:05 +00:00
|
|
|
from tractor._discovery import get_root
|
|
|
|
|
ptl: Portal
|
|
|
|
|
async with get_root() as ptl:
|
|
|
|
|
root_aid: Aid = ptl.chan.aid
|
2026-02-11 23:20:59 +00:00
|
|
|
parent_ptl: Portal = current_actor().get_parent()
|
2026-02-11 22:43:05 +00:00
|
|
|
assert (
|
|
|
|
|
root_aid.name == 'root'
|
|
|
|
|
and
|
|
|
|
|
parent_ptl.chan.aid == root_aid
|
|
|
|
|
)
|
2026-02-11 23:20:59 +00:00
|
|
|
await ctx.started()
|
2026-02-11 22:43:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_non_registrar_spawns_child(
|
|
|
|
|
daemon: subprocess.Popen,
|
|
|
|
|
reg_addr: UnwrappedAddress,
|
2026-02-11 23:20:59 +00:00
|
|
|
loglevel: str,
|
|
|
|
|
debug_mode: bool,
|
2026-02-11 22:43:05 +00:00
|
|
|
):
|
|
|
|
|
'''
|
|
|
|
|
Ensure a non-regristar (serving) root actor can spawn a sub and
|
|
|
|
|
that sub can connect back (manually) to it's rent that is the
|
|
|
|
|
root without issue.
|
|
|
|
|
|
|
|
|
|
More or less this audits the global contact info in
|
|
|
|
|
`._state._runtime_vars`.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
async def main():
|
|
|
|
|
async with tractor.open_nursery(
|
|
|
|
|
registry_addrs=[reg_addr],
|
2026-02-11 23:20:59 +00:00
|
|
|
loglevel=loglevel,
|
|
|
|
|
debug_mode=debug_mode,
|
2026-02-11 22:43:05 +00:00
|
|
|
) as an:
|
|
|
|
|
|
|
|
|
|
actor: Actor = tractor.current_actor()
|
|
|
|
|
assert not actor.is_registrar
|
|
|
|
|
sub_ptl: Portal = await an.start_actor(
|
|
|
|
|
name='sub',
|
|
|
|
|
enable_modules=[__name__],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async with sub_ptl.open_context(
|
|
|
|
|
get_root_portal,
|
|
|
|
|
) as (ctx, first):
|
2026-02-11 23:20:59 +00:00
|
|
|
print('Waiting for `sub` to connect back to us..')
|
2026-02-11 22:43:05 +00:00
|
|
|
|
|
|
|
|
await an.cancel()
|
2018-09-08 13:44:29 +00:00
|
|
|
|
2026-02-11 22:43:05 +00:00
|
|
|
# XXX, run manually since we want to start this root **after**
|
|
|
|
|
# the other "daemon" program with it's own root.
|
2021-02-24 20:02:02 +00:00
|
|
|
trio.run(main)
|