commit
368e9f3f7c
|
@ -396,7 +396,7 @@ tasks spawned via multiple RPC calls to an actor can modify
|
||||||
|
|
||||||
|
|
||||||
# a per process cache
|
# a per process cache
|
||||||
_actor_cache: Dict[str, bool] = {}
|
_actor_cache: dict[str, bool] = {}
|
||||||
|
|
||||||
|
|
||||||
def ping_endpoints(endpoints: List[str]):
|
def ping_endpoints(endpoints: List[str]):
|
||||||
|
|
|
@ -9,7 +9,7 @@ is ``tractor``'s channels.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import List, Callable
|
from typing import Callable
|
||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
|
@ -71,8 +71,8 @@ async def worker_pool(workers=4):
|
||||||
|
|
||||||
async def _map(
|
async def _map(
|
||||||
worker_func: Callable[[int], bool],
|
worker_func: Callable[[int], bool],
|
||||||
sequence: List[int]
|
sequence: list[int]
|
||||||
) -> List[bool]:
|
) -> list[bool]:
|
||||||
|
|
||||||
# define an async (local) task to collect results from workers
|
# define an async (local) task to collect results from workers
|
||||||
async def send_result(func, value, portal):
|
async def send_result(func, value, portal):
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
Strictly support Python 3.10+, start runtime machinery reorg
|
||||||
|
|
||||||
|
Since we want to push forward using the new `match:` syntax for our
|
||||||
|
internal RPC-msg loops, we officially drop 3.9 support for the next
|
||||||
|
release which should coincide well with the first release of 3.11.
|
||||||
|
|
||||||
|
This patch set also officially removes the ``tractor.run()`` API (which
|
||||||
|
has been deprecated for some time) as well as starts an initial re-org
|
||||||
|
of the internal runtime core by:
|
||||||
|
- renaming ``tractor._actor`` -> ``._runtime``
|
||||||
|
- moving the ``._runtime.ActorActor._process_messages()`` and
|
||||||
|
``._async_main()`` to be module level singleton-task-functions since
|
||||||
|
they are only started once for each connection and actor spawn
|
||||||
|
respectively; this internal API thus looks more similar to (at the
|
||||||
|
time of writing) the ``trio``-internals in ``trio._core._run``.
|
||||||
|
- officially remove ``tractor.run()``, now deprecated for some time.
|
10
setup.py
10
setup.py
|
@ -25,7 +25,7 @@ with open('docs/README.rst', encoding='utf-8') as f:
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="tractor",
|
name="tractor",
|
||||||
version='0.1.0a5', # alpha zone
|
version='0.1.0a6dev0', # alpha zone
|
||||||
description='structured concurrrent "actors"',
|
description='structured concurrrent "actors"',
|
||||||
long_description=readme,
|
long_description=readme,
|
||||||
license='AGPLv3',
|
license='AGPLv3',
|
||||||
|
@ -55,11 +55,13 @@ setup(
|
||||||
'colorlog',
|
'colorlog',
|
||||||
'wrapt',
|
'wrapt',
|
||||||
|
|
||||||
|
# serialization
|
||||||
|
'msgspec',
|
||||||
|
|
||||||
# pip ref docs on these specs:
|
# pip ref docs on these specs:
|
||||||
# https://pip.pypa.io/en/stable/reference/requirement-specifiers/#examples
|
# https://pip.pypa.io/en/stable/reference/requirement-specifiers/#examples
|
||||||
# and pep:
|
# and pep:
|
||||||
# https://peps.python.org/pep-0440/#version-specifiers
|
# https://peps.python.org/pep-0440/#version-specifiers
|
||||||
'pdbpp <= 0.10.1; python_version < "3.10"',
|
|
||||||
|
|
||||||
# windows deps workaround for ``pdbpp``
|
# windows deps workaround for ``pdbpp``
|
||||||
# https://github.com/pdbpp/pdbpp/issues/498
|
# https://github.com/pdbpp/pdbpp/issues/498
|
||||||
|
@ -71,9 +73,6 @@ setup(
|
||||||
# we need a specific patch on master atm.
|
# we need a specific patch on master atm.
|
||||||
'pdbpp @ git+https://github.com/pdbpp/pdbpp@76c4be5#egg=pdbpp ; python_version > "3.9"', # noqa: E501
|
'pdbpp @ git+https://github.com/pdbpp/pdbpp@76c4be5#egg=pdbpp ; python_version > "3.9"', # noqa: E501
|
||||||
|
|
||||||
# serialization
|
|
||||||
'msgspec >= "0.4.0"'
|
|
||||||
|
|
||||||
],
|
],
|
||||||
tests_require=['pytest'],
|
tests_require=['pytest'],
|
||||||
python_requires=">=3.9",
|
python_requires=">=3.9",
|
||||||
|
@ -94,7 +93,6 @@ setup(
|
||||||
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Intended Audience :: Science/Research",
|
"Intended Audience :: Science/Research",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
|
|
|
@ -5,7 +5,6 @@ Advanced streaming patterns using bidirectional streams and contexts.
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
import itertools
|
import itertools
|
||||||
import platform
|
import platform
|
||||||
from typing import Set, Dict, List
|
|
||||||
|
|
||||||
import trio
|
import trio
|
||||||
import tractor
|
import tractor
|
||||||
|
@ -15,7 +14,7 @@ def is_win():
|
||||||
return platform.system() == 'Windows'
|
return platform.system() == 'Windows'
|
||||||
|
|
||||||
|
|
||||||
_registry: Dict[str, Set[tractor.ReceiveMsgStream]] = {
|
_registry: dict[str, set[tractor.ReceiveMsgStream]] = {
|
||||||
'even': set(),
|
'even': set(),
|
||||||
'odd': set(),
|
'odd': set(),
|
||||||
}
|
}
|
||||||
|
@ -77,7 +76,7 @@ async def subscribe(
|
||||||
|
|
||||||
async def consumer(
|
async def consumer(
|
||||||
|
|
||||||
subs: List[str],
|
subs: list[str],
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
|
|
|
@ -571,7 +571,7 @@ def test_one_end_stream_not_opened(overrun_by):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
overrunner, buf_size_increase, entrypoint = overrun_by
|
overrunner, buf_size_increase, entrypoint = overrun_by
|
||||||
from tractor._actor import Actor
|
from tractor._runtime import Actor
|
||||||
buf_size = buf_size_increase + Actor.msg_buffer_size
|
buf_size = buf_size_increase + Actor.msg_buffer_size
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
|
|
@ -23,13 +23,6 @@ async def test_no_arbitter():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_no_main():
|
|
||||||
"""An async function **must** be passed to ``tractor.run()``.
|
|
||||||
"""
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
tractor.run(None)
|
|
||||||
|
|
||||||
|
|
||||||
@tractor_test
|
@tractor_test
|
||||||
async def test_self_is_registered(arb_addr):
|
async def test_self_is_registered(arb_addr):
|
||||||
"Verify waiting on the arbiter to register itself using the standard api."
|
"Verify waiting on the arbiter to register itself using the standard api."
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Spawning basics
|
Spawning basics
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Tuple, Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import trio
|
import trio
|
||||||
|
@ -14,8 +14,8 @@ data_to_pass_down = {'doggy': 10, 'kitty': 4}
|
||||||
|
|
||||||
async def spawn(
|
async def spawn(
|
||||||
is_arbiter: bool,
|
is_arbiter: bool,
|
||||||
data: Dict,
|
data: dict,
|
||||||
arb_addr: Tuple[str, int],
|
arb_addr: tuple[str, int],
|
||||||
):
|
):
|
||||||
namespaces = [__name__]
|
namespaces = [__name__]
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from contextlib import asynccontextmanager
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
import time
|
import time
|
||||||
from typing import Optional, List, Tuple
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import trio
|
import trio
|
||||||
|
@ -62,8 +62,8 @@ async def ensure_sequence(
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def open_sequence_streamer(
|
async def open_sequence_streamer(
|
||||||
|
|
||||||
sequence: List[int],
|
sequence: list[int],
|
||||||
arb_addr: Tuple[str, int],
|
arb_addr: tuple[str, int],
|
||||||
start_method: str,
|
start_method: str,
|
||||||
|
|
||||||
) -> tractor.MsgStream:
|
) -> tractor.MsgStream:
|
||||||
|
|
|
@ -36,7 +36,10 @@ from ._discovery import (
|
||||||
query_actor,
|
query_actor,
|
||||||
)
|
)
|
||||||
from ._supervise import open_nursery
|
from ._supervise import open_nursery
|
||||||
from ._state import current_actor, is_root_process
|
from ._state import (
|
||||||
|
current_actor,
|
||||||
|
is_root_process,
|
||||||
|
)
|
||||||
from ._exceptions import (
|
from ._exceptions import (
|
||||||
RemoteActorError,
|
RemoteActorError,
|
||||||
ModuleNotExposed,
|
ModuleNotExposed,
|
||||||
|
@ -44,11 +47,16 @@ from ._exceptions import (
|
||||||
)
|
)
|
||||||
from ._debug import breakpoint, post_mortem
|
from ._debug import breakpoint, post_mortem
|
||||||
from . import msg
|
from . import msg
|
||||||
from ._root import run, run_daemon, open_root_actor
|
from ._root import (
|
||||||
|
run_daemon,
|
||||||
|
open_root_actor,
|
||||||
|
)
|
||||||
from ._portal import Portal
|
from ._portal import Portal
|
||||||
|
from ._runtime import Actor
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
'Actor',
|
||||||
'Channel',
|
'Channel',
|
||||||
'Context',
|
'Context',
|
||||||
'ContextCancelled',
|
'ContextCancelled',
|
||||||
|
@ -70,7 +78,6 @@ __all__ = [
|
||||||
'open_root_actor',
|
'open_root_actor',
|
||||||
'post_mortem',
|
'post_mortem',
|
||||||
'query_actor',
|
'query_actor',
|
||||||
'run',
|
|
||||||
'run_daemon',
|
'run_daemon',
|
||||||
'stream',
|
'stream',
|
||||||
'to_asyncio',
|
'to_asyncio',
|
||||||
|
|
|
@ -24,7 +24,7 @@ import argparse
|
||||||
|
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
|
|
||||||
from ._actor import Actor
|
from ._runtime import Actor
|
||||||
from ._entry import _trio_main
|
from ._entry import _trio_main
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ import signal
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from contextlib import asynccontextmanager as acm
|
from contextlib import asynccontextmanager as acm
|
||||||
from typing import (
|
from typing import (
|
||||||
Tuple,
|
|
||||||
Optional,
|
Optional,
|
||||||
Callable,
|
Callable,
|
||||||
AsyncIterator,
|
AsyncIterator,
|
||||||
|
@ -74,7 +73,7 @@ class Lock:
|
||||||
local_task_in_debug: Optional[str] = None
|
local_task_in_debug: Optional[str] = None
|
||||||
|
|
||||||
# actor tree-wide actor uid that supposedly has the tty lock
|
# actor tree-wide actor uid that supposedly has the tty lock
|
||||||
global_actor_in_debug: Optional[Tuple[str, str]] = None
|
global_actor_in_debug: Optional[tuple[str, str]] = None
|
||||||
|
|
||||||
local_pdb_complete: Optional[trio.Event] = None
|
local_pdb_complete: Optional[trio.Event] = None
|
||||||
no_remote_has_tty: Optional[trio.Event] = None
|
no_remote_has_tty: Optional[trio.Event] = None
|
||||||
|
@ -172,7 +171,7 @@ class MultiActorPdb(pdbpp.Pdb):
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
async def _acquire_debug_lock_from_root_task(
|
async def _acquire_debug_lock_from_root_task(
|
||||||
uid: Tuple[str, str]
|
uid: tuple[str, str]
|
||||||
|
|
||||||
) -> AsyncIterator[trio.StrictFIFOLock]:
|
) -> AsyncIterator[trio.StrictFIFOLock]:
|
||||||
'''
|
'''
|
||||||
|
@ -252,7 +251,7 @@ async def _acquire_debug_lock_from_root_task(
|
||||||
async def lock_tty_for_child(
|
async def lock_tty_for_child(
|
||||||
|
|
||||||
ctx: tractor.Context,
|
ctx: tractor.Context,
|
||||||
subactor_uid: Tuple[str, str]
|
subactor_uid: tuple[str, str]
|
||||||
|
|
||||||
) -> str:
|
) -> str:
|
||||||
'''
|
'''
|
||||||
|
@ -302,7 +301,7 @@ async def lock_tty_for_child(
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_parent_stdin_hijack(
|
async def wait_for_parent_stdin_hijack(
|
||||||
actor_uid: Tuple[str, str],
|
actor_uid: tuple[str, str],
|
||||||
task_status: TaskStatus[trio.CancelScope] = trio.TASK_STATUS_IGNORED
|
task_status: TaskStatus[trio.CancelScope] = trio.TASK_STATUS_IGNORED
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
|
@ -643,7 +642,7 @@ def shield_sigint(
|
||||||
|
|
||||||
|
|
||||||
def _set_trace(
|
def _set_trace(
|
||||||
actor: Optional[tractor._actor.Actor] = None,
|
actor: Optional[tractor.Actor] = None,
|
||||||
pdb: Optional[MultiActorPdb] = None,
|
pdb: Optional[MultiActorPdb] = None,
|
||||||
):
|
):
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
|
@ -676,7 +675,7 @@ breakpoint = partial(
|
||||||
|
|
||||||
|
|
||||||
def _post_mortem(
|
def _post_mortem(
|
||||||
actor: tractor._actor.Actor,
|
actor: tractor.Actor,
|
||||||
pdb: MultiActorPdb,
|
pdb: MultiActorPdb,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -732,7 +731,7 @@ async def _maybe_enter_pm(err):
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
async def acquire_debug_lock(
|
async def acquire_debug_lock(
|
||||||
subactor_uid: Tuple[str, str],
|
subactor_uid: tuple[str, str],
|
||||||
) -> AsyncGenerator[None, tuple]:
|
) -> AsyncGenerator[None, tuple]:
|
||||||
'''
|
'''
|
||||||
Grab root's debug lock on entry, release on exit.
|
Grab root's debug lock on entry, release on exit.
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
Actor discovery API.
|
Actor discovery API.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import Tuple, Optional, Union, AsyncGenerator
|
from typing import (
|
||||||
|
Optional,
|
||||||
|
Union,
|
||||||
|
AsyncGenerator,
|
||||||
|
)
|
||||||
from contextlib import asynccontextmanager as acm
|
from contextlib import asynccontextmanager as acm
|
||||||
|
|
||||||
from ._ipc import _connect_chan, Channel
|
from ._ipc import _connect_chan, Channel
|
||||||
|
@ -104,7 +108,7 @@ async def query_actor(
|
||||||
@acm
|
@acm
|
||||||
async def find_actor(
|
async def find_actor(
|
||||||
name: str,
|
name: str,
|
||||||
arbiter_sockaddr: Tuple[str, int] = None
|
arbiter_sockaddr: tuple[str, int] = None
|
||||||
|
|
||||||
) -> AsyncGenerator[Optional[Portal], None]:
|
) -> AsyncGenerator[Optional[Portal], None]:
|
||||||
'''
|
'''
|
||||||
|
@ -130,7 +134,7 @@ async def find_actor(
|
||||||
@acm
|
@acm
|
||||||
async def wait_for_actor(
|
async def wait_for_actor(
|
||||||
name: str,
|
name: str,
|
||||||
arbiter_sockaddr: Tuple[str, int] = None
|
arbiter_sockaddr: tuple[str, int] = None
|
||||||
) -> AsyncGenerator[Portal, None]:
|
) -> AsyncGenerator[Portal, None]:
|
||||||
"""Wait on an actor to register with the arbiter.
|
"""Wait on an actor to register with the arbiter.
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,14 @@ Sub-process entry points.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Tuple, Any
|
from typing import Any
|
||||||
import signal
|
|
||||||
|
|
||||||
import trio # type: ignore
|
import trio # type: ignore
|
||||||
|
|
||||||
from .log import get_console_log, get_logger
|
from .log import get_console_log, get_logger
|
||||||
from . import _state
|
from . import _state
|
||||||
from .to_asyncio import run_as_asyncio_guest
|
from .to_asyncio import run_as_asyncio_guest
|
||||||
|
from ._runtime import async_main, Actor
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
@ -35,10 +35,10 @@ log = get_logger(__name__)
|
||||||
def _mp_main(
|
def _mp_main(
|
||||||
|
|
||||||
actor: 'Actor', # type: ignore
|
actor: 'Actor', # type: ignore
|
||||||
accept_addr: Tuple[str, int],
|
accept_addr: tuple[str, int],
|
||||||
forkserver_info: Tuple[Any, Any, Any, Any, Any],
|
forkserver_info: tuple[Any, Any, Any, Any, Any],
|
||||||
start_method: str,
|
start_method: str,
|
||||||
parent_addr: Tuple[str, int] = None,
|
parent_addr: tuple[str, int] = None,
|
||||||
infect_asyncio: bool = False,
|
infect_asyncio: bool = False,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -63,7 +63,8 @@ def _mp_main(
|
||||||
|
|
||||||
log.debug(f"parent_addr is {parent_addr}")
|
log.debug(f"parent_addr is {parent_addr}")
|
||||||
trio_main = partial(
|
trio_main = partial(
|
||||||
actor._async_main,
|
async_main,
|
||||||
|
actor,
|
||||||
accept_addr,
|
accept_addr,
|
||||||
parent_addr=parent_addr
|
parent_addr=parent_addr
|
||||||
)
|
)
|
||||||
|
@ -82,9 +83,9 @@ def _mp_main(
|
||||||
|
|
||||||
def _trio_main(
|
def _trio_main(
|
||||||
|
|
||||||
actor: 'Actor', # type: ignore
|
actor: Actor, # type: ignore
|
||||||
*,
|
*,
|
||||||
parent_addr: Tuple[str, int] = None,
|
parent_addr: tuple[str, int] = None,
|
||||||
infect_asyncio: bool = False,
|
infect_asyncio: bool = False,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -106,7 +107,8 @@ def _trio_main(
|
||||||
|
|
||||||
log.debug(f"parent_addr is {parent_addr}")
|
log.debug(f"parent_addr is {parent_addr}")
|
||||||
trio_main = partial(
|
trio_main = partial(
|
||||||
actor._async_main,
|
async_main,
|
||||||
|
actor,
|
||||||
parent_addr=parent_addr
|
parent_addr=parent_addr
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
Our classy exception set.
|
Our classy exception set.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Any, Optional, Type
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Optional,
|
||||||
|
Type,
|
||||||
|
)
|
||||||
import importlib
|
import importlib
|
||||||
import builtins
|
import builtins
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -95,7 +99,7 @@ def pack_error(
|
||||||
exc: BaseException,
|
exc: BaseException,
|
||||||
tb=None,
|
tb=None,
|
||||||
|
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Create an "error message" for tranmission over
|
"""Create an "error message" for tranmission over
|
||||||
a channel (aka the wire).
|
a channel (aka the wire).
|
||||||
"""
|
"""
|
||||||
|
@ -114,7 +118,7 @@ def pack_error(
|
||||||
|
|
||||||
def unpack_error(
|
def unpack_error(
|
||||||
|
|
||||||
msg: Dict[str, Any],
|
msg: dict[str, Any],
|
||||||
chan=None,
|
chan=None,
|
||||||
err_type=RemoteActorError
|
err_type=RemoteActorError
|
||||||
|
|
||||||
|
|
|
@ -611,9 +611,11 @@ async def open_portal(
|
||||||
|
|
||||||
msg_loop_cs: Optional[trio.CancelScope] = None
|
msg_loop_cs: Optional[trio.CancelScope] = None
|
||||||
if start_msg_loop:
|
if start_msg_loop:
|
||||||
|
from ._runtime import process_messages
|
||||||
msg_loop_cs = await nursery.start(
|
msg_loop_cs = await nursery.start(
|
||||||
partial(
|
partial(
|
||||||
actor._process_messages,
|
process_messages,
|
||||||
|
actor,
|
||||||
channel,
|
channel,
|
||||||
# if the local task is cancelled we want to keep
|
# if the local task is cancelled we want to keep
|
||||||
# the msg loop running until our block ends
|
# the msg loop running until our block ends
|
||||||
|
|
|
@ -23,13 +23,15 @@ from functools import partial
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Tuple, Optional, List, Any
|
from typing import (
|
||||||
|
Optional,
|
||||||
|
)
|
||||||
import typing
|
import typing
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import trio
|
import trio
|
||||||
|
|
||||||
from ._actor import Actor, Arbiter
|
from ._runtime import Actor, Arbiter, async_main
|
||||||
from . import _debug
|
from . import _debug
|
||||||
from . import _spawn
|
from . import _spawn
|
||||||
from . import _state
|
from . import _state
|
||||||
|
@ -50,7 +52,7 @@ logger = log.get_logger('tractor')
|
||||||
async def open_root_actor(
|
async def open_root_actor(
|
||||||
|
|
||||||
# defaults are above
|
# defaults are above
|
||||||
arbiter_addr: Optional[Tuple[str, int]] = (
|
arbiter_addr: Optional[tuple[str, int]] = (
|
||||||
_default_arbiter_host,
|
_default_arbiter_host,
|
||||||
_default_arbiter_port,
|
_default_arbiter_port,
|
||||||
),
|
),
|
||||||
|
@ -68,8 +70,8 @@ async def open_root_actor(
|
||||||
# internal logging
|
# internal logging
|
||||||
loglevel: Optional[str] = None,
|
loglevel: Optional[str] = None,
|
||||||
|
|
||||||
enable_modules: Optional[List] = None,
|
enable_modules: Optional[list] = None,
|
||||||
rpc_module_paths: Optional[List] = None,
|
rpc_module_paths: Optional[list] = None,
|
||||||
|
|
||||||
) -> typing.Any:
|
) -> typing.Any:
|
||||||
"""Async entry point for ``tractor``.
|
"""Async entry point for ``tractor``.
|
||||||
|
@ -188,13 +190,14 @@ async def open_root_actor(
|
||||||
# start the actor runtime in a new task
|
# start the actor runtime in a new task
|
||||||
async with trio.open_nursery() as nursery:
|
async with trio.open_nursery() as nursery:
|
||||||
|
|
||||||
# ``Actor._async_main()`` creates an internal nursery and
|
# ``_runtime.async_main()`` creates an internal nursery and
|
||||||
# thus blocks here until the entire underlying actor tree has
|
# thus blocks here until the entire underlying actor tree has
|
||||||
# terminated thereby conducting structured concurrency.
|
# terminated thereby conducting structured concurrency.
|
||||||
|
|
||||||
await nursery.start(
|
await nursery.start(
|
||||||
partial(
|
partial(
|
||||||
actor._async_main,
|
async_main,
|
||||||
|
actor,
|
||||||
accept_addr=(host, port),
|
accept_addr=(host, port),
|
||||||
parent_addr=None
|
parent_addr=None
|
||||||
)
|
)
|
||||||
|
@ -229,28 +232,35 @@ async def open_root_actor(
|
||||||
logger.runtime("Root actor terminated")
|
logger.runtime("Root actor terminated")
|
||||||
|
|
||||||
|
|
||||||
def run(
|
def run_daemon(
|
||||||
|
enable_modules: list[str],
|
||||||
# target
|
|
||||||
async_fn: typing.Callable[..., typing.Awaitable],
|
|
||||||
*args,
|
|
||||||
|
|
||||||
# runtime kwargs
|
# runtime kwargs
|
||||||
name: Optional[str] = 'root',
|
name: Optional[str] = 'root',
|
||||||
arbiter_addr: Tuple[str, int] = (
|
arbiter_addr: tuple[str, int] = (
|
||||||
_default_arbiter_host,
|
_default_arbiter_host,
|
||||||
_default_arbiter_port,
|
_default_arbiter_port,
|
||||||
),
|
),
|
||||||
|
|
||||||
start_method: Optional[str] = None,
|
start_method: Optional[str] = None,
|
||||||
debug_mode: bool = False,
|
debug_mode: bool = False,
|
||||||
**kwargs,
|
**kwargs
|
||||||
|
|
||||||
) -> Any:
|
) -> None:
|
||||||
"""Run a trio-actor async function in process.
|
'''
|
||||||
|
Spawn daemon actor which will respond to RPC; the main task simply
|
||||||
|
starts the runtime and then sleeps forever.
|
||||||
|
|
||||||
|
This is a very minimal convenience wrapper around starting
|
||||||
|
a "run-until-cancelled" root actor which can be started with a set
|
||||||
|
of enabled modules for RPC request handling.
|
||||||
|
|
||||||
|
'''
|
||||||
|
kwargs['enable_modules'] = list(enable_modules)
|
||||||
|
|
||||||
|
for path in enable_modules:
|
||||||
|
importlib.import_module(path)
|
||||||
|
|
||||||
This is tractor's main entry and the start point for any async actor.
|
|
||||||
"""
|
|
||||||
async def _main():
|
async def _main():
|
||||||
|
|
||||||
async with open_root_actor(
|
async with open_root_actor(
|
||||||
|
@ -260,35 +270,6 @@ def run(
|
||||||
debug_mode=debug_mode,
|
debug_mode=debug_mode,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
return await trio.sleep_forever()
|
||||||
|
|
||||||
return await async_fn(*args)
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"`tractor.run()` is now deprecated. `tractor` now"
|
|
||||||
" implicitly starts the root actor on first actor nursery"
|
|
||||||
" use. If you want to start the root actor manually, use"
|
|
||||||
" `tractor.open_root_actor()`.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return trio.run(_main)
|
return trio.run(_main)
|
||||||
|
|
||||||
|
|
||||||
def run_daemon(
|
|
||||||
enable_modules: list[str],
|
|
||||||
**kwargs
|
|
||||||
) -> None:
|
|
||||||
'''
|
|
||||||
Spawn daemon actor which will respond to RPC.
|
|
||||||
|
|
||||||
This is a convenience wrapper around
|
|
||||||
``tractor.run(trio.sleep(float('inf')))`` such that the first actor spawned
|
|
||||||
is meant to run forever responding to RPC requests.
|
|
||||||
|
|
||||||
'''
|
|
||||||
kwargs['enable_modules'] = list(enable_modules)
|
|
||||||
|
|
||||||
for path in enable_modules:
|
|
||||||
importlib.import_module(path)
|
|
||||||
|
|
||||||
return run(partial(trio.sleep, float('inf')), **kwargs)
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -42,7 +42,7 @@ from ._state import (
|
||||||
|
|
||||||
from .log import get_logger
|
from .log import get_logger
|
||||||
from ._portal import Portal
|
from ._portal import Portal
|
||||||
from ._actor import Actor
|
from ._runtime import Actor
|
||||||
from ._entry import _mp_main
|
from ._entry import _mp_main
|
||||||
from ._exceptions import ActorFailure
|
from ._exceptions import ActorFailure
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,10 @@
|
||||||
Per process state
|
Per process state
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import Optional, Dict, Any
|
from typing import (
|
||||||
|
Optional,
|
||||||
|
Any,
|
||||||
|
)
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
|
|
||||||
import trio
|
import trio
|
||||||
|
@ -27,7 +30,7 @@ from ._exceptions import NoRuntime
|
||||||
|
|
||||||
|
|
||||||
_current_actor: Optional['Actor'] = None # type: ignore # noqa
|
_current_actor: Optional['Actor'] = None # type: ignore # noqa
|
||||||
_runtime_vars: Dict[str, Any] = {
|
_runtime_vars: dict[str, Any] = {
|
||||||
'_debug_mode': False,
|
'_debug_mode': False,
|
||||||
'_is_root': False,
|
'_is_root': False,
|
||||||
'_root_mailbox': (None, None)
|
'_root_mailbox': (None, None)
|
||||||
|
|
|
@ -23,8 +23,10 @@ import inspect
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, Optional, Callable,
|
Any,
|
||||||
AsyncGenerator, Dict,
|
Optional,
|
||||||
|
Callable,
|
||||||
|
AsyncGenerator,
|
||||||
AsyncIterator
|
AsyncIterator
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -393,7 +395,7 @@ class Context:
|
||||||
|
|
||||||
async def _maybe_raise_from_remote_msg(
|
async def _maybe_raise_from_remote_msg(
|
||||||
self,
|
self,
|
||||||
msg: Dict[str, Any],
|
msg: dict[str, Any],
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -20,7 +20,10 @@
|
||||||
"""
|
"""
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Tuple, List, Dict, Optional, TYPE_CHECKING
|
from typing import (
|
||||||
|
Optional,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
)
|
||||||
import typing
|
import typing
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
@ -30,7 +33,7 @@ from async_generator import asynccontextmanager
|
||||||
from ._debug import maybe_wait_for_debugger
|
from ._debug import maybe_wait_for_debugger
|
||||||
from ._state import current_actor, is_main_process
|
from ._state import current_actor, is_main_process
|
||||||
from .log import get_logger, get_loglevel
|
from .log import get_logger, get_loglevel
|
||||||
from ._actor import Actor
|
from ._runtime import Actor
|
||||||
from ._portal import Portal
|
from ._portal import Portal
|
||||||
from ._exceptions import is_multi_cancelled
|
from ._exceptions import is_multi_cancelled
|
||||||
from ._root import open_root_actor
|
from ._root import open_root_actor
|
||||||
|
@ -43,7 +46,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
_default_bind_addr: Tuple[str, int] = ('127.0.0.1', 0)
|
_default_bind_addr: tuple[str, int] = ('127.0.0.1', 0)
|
||||||
|
|
||||||
|
|
||||||
class ActorNursery:
|
class ActorNursery:
|
||||||
|
@ -79,15 +82,15 @@ class ActorNursery:
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
ria_nursery: trio.Nursery,
|
ria_nursery: trio.Nursery,
|
||||||
da_nursery: trio.Nursery,
|
da_nursery: trio.Nursery,
|
||||||
errors: Dict[Tuple[str, str], Exception],
|
errors: dict[tuple[str, str], Exception],
|
||||||
) -> None:
|
) -> None:
|
||||||
# self.supervisor = supervisor # TODO
|
# self.supervisor = supervisor # TODO
|
||||||
self._actor: Actor = actor
|
self._actor: Actor = actor
|
||||||
self._ria_nursery = ria_nursery
|
self._ria_nursery = ria_nursery
|
||||||
self._da_nursery = da_nursery
|
self._da_nursery = da_nursery
|
||||||
self._children: Dict[
|
self._children: dict[
|
||||||
Tuple[str, str],
|
tuple[str, str],
|
||||||
Tuple[Actor, mp.Process, Optional[Portal]]
|
tuple[Actor, mp.Process, Optional[Portal]]
|
||||||
] = {}
|
] = {}
|
||||||
# portals spawned with ``run_in_actor()`` are
|
# portals spawned with ``run_in_actor()`` are
|
||||||
# cancelled when their "main" result arrives
|
# cancelled when their "main" result arrives
|
||||||
|
@ -102,9 +105,9 @@ class ActorNursery:
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
bind_addr: Tuple[str, int] = _default_bind_addr,
|
bind_addr: tuple[str, int] = _default_bind_addr,
|
||||||
rpc_module_paths: List[str] = None,
|
rpc_module_paths: list[str] = None,
|
||||||
enable_modules: List[str] = None,
|
enable_modules: list[str] = None,
|
||||||
loglevel: str = None, # set log level per subactor
|
loglevel: str = None, # set log level per subactor
|
||||||
nursery: trio.Nursery = None,
|
nursery: trio.Nursery = None,
|
||||||
debug_mode: Optional[bool] = None,
|
debug_mode: Optional[bool] = None,
|
||||||
|
@ -173,9 +176,9 @@ class ActorNursery:
|
||||||
*,
|
*,
|
||||||
|
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
bind_addr: Tuple[str, int] = _default_bind_addr,
|
bind_addr: tuple[str, int] = _default_bind_addr,
|
||||||
rpc_module_paths: Optional[List[str]] = None,
|
rpc_module_paths: Optional[list[str]] = None,
|
||||||
enable_modules: List[str] = None,
|
enable_modules: list[str] = None,
|
||||||
loglevel: str = None, # set log level per subactor
|
loglevel: str = None, # set log level per subactor
|
||||||
infect_asyncio: bool = False,
|
infect_asyncio: bool = False,
|
||||||
|
|
||||||
|
@ -293,7 +296,7 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
||||||
) -> typing.AsyncGenerator[ActorNursery, None]:
|
) -> typing.AsyncGenerator[ActorNursery, None]:
|
||||||
|
|
||||||
# the collection of errors retreived from spawned sub-actors
|
# the collection of errors retreived from spawned sub-actors
|
||||||
errors: Dict[Tuple[str, str], Exception] = {}
|
errors: dict[tuple[str, str], Exception] = {}
|
||||||
|
|
||||||
# This is the outermost level "deamon actor" nursery. It is awaited
|
# This is the outermost level "deamon actor" nursery. It is awaited
|
||||||
# **after** the below inner "run in actor nursery". This allows for
|
# **after** the below inner "run in actor nursery". This allows for
|
||||||
|
|
|
@ -26,7 +26,10 @@ support provided by ``tractor.Context.open_stream()`` and friends.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import inspect
|
import inspect
|
||||||
import typing
|
import typing
|
||||||
from typing import Dict, Any, Set, Callable, List, Tuple
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
)
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from async_generator import aclosing
|
from async_generator import aclosing
|
||||||
|
|
||||||
|
@ -44,7 +47,7 @@ log = get_logger('messaging')
|
||||||
|
|
||||||
async def fan_out_to_ctxs(
|
async def fan_out_to_ctxs(
|
||||||
pub_async_gen_func: typing.Callable, # it's an async gen ... gd mypy
|
pub_async_gen_func: typing.Callable, # it's an async gen ... gd mypy
|
||||||
topics2ctxs: Dict[str, list],
|
topics2ctxs: dict[str, list],
|
||||||
packetizer: typing.Callable = None,
|
packetizer: typing.Callable = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
|
@ -61,7 +64,7 @@ async def fan_out_to_ctxs(
|
||||||
|
|
||||||
async for published in pub_gen:
|
async for published in pub_gen:
|
||||||
|
|
||||||
ctx_payloads: List[Tuple[Context, Any]] = []
|
ctx_payloads: list[tuple[Context, Any]] = []
|
||||||
|
|
||||||
for topic, data in published.items():
|
for topic, data in published.items():
|
||||||
log.debug(f"publishing {topic, data}")
|
log.debug(f"publishing {topic, data}")
|
||||||
|
@ -103,8 +106,8 @@ async def fan_out_to_ctxs(
|
||||||
|
|
||||||
def modify_subs(
|
def modify_subs(
|
||||||
|
|
||||||
topics2ctxs: Dict[str, List[Context]],
|
topics2ctxs: dict[str, list[Context]],
|
||||||
topics: Set[str],
|
topics: set[str],
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -136,20 +139,20 @@ def modify_subs(
|
||||||
topics2ctxs.pop(topic)
|
topics2ctxs.pop(topic)
|
||||||
|
|
||||||
|
|
||||||
_pub_state: Dict[str, dict] = {}
|
_pub_state: dict[str, dict] = {}
|
||||||
_pubtask2lock: Dict[str, trio.StrictFIFOLock] = {}
|
_pubtask2lock: dict[str, trio.StrictFIFOLock] = {}
|
||||||
|
|
||||||
|
|
||||||
def pub(
|
def pub(
|
||||||
wrapped: typing.Callable = None,
|
wrapped: typing.Callable = None,
|
||||||
*,
|
*,
|
||||||
tasks: Set[str] = set(),
|
tasks: set[str] = set(),
|
||||||
):
|
):
|
||||||
"""Publisher async generator decorator.
|
"""Publisher async generator decorator.
|
||||||
|
|
||||||
A publisher can be called multiple times from different actors but
|
A publisher can be called multiple times from different actors but
|
||||||
will only spawn a finite set of internal tasks to stream values to
|
will only spawn a finite set of internal tasks to stream values to
|
||||||
each caller. The ``tasks: Set[str]`` argument to the decorator
|
each caller. The ``tasks: set[str]`` argument to the decorator
|
||||||
specifies the names of the mutex set of publisher tasks. When the
|
specifies the names of the mutex set of publisher tasks. When the
|
||||||
publisher function is called, an argument ``task_name`` must be
|
publisher function is called, an argument ``task_name`` must be
|
||||||
passed to specify which task (of the set named in ``tasks``) should
|
passed to specify which task (of the set named in ``tasks``) should
|
||||||
|
@ -158,9 +161,9 @@ def pub(
|
||||||
necessary.
|
necessary.
|
||||||
|
|
||||||
Values yielded from the decorated async generator must be
|
Values yielded from the decorated async generator must be
|
||||||
``Dict[str, Dict[str, Any]]`` where the fist level key is the topic
|
``dict[str, dict[str, Any]]`` where the fist level key is the topic
|
||||||
string and determines which subscription the packet will be
|
string and determines which subscription the packet will be
|
||||||
delivered to and the value is a packet ``Dict[str, Any]`` by default
|
delivered to and the value is a packet ``dict[str, Any]`` by default
|
||||||
of the form:
|
of the form:
|
||||||
|
|
||||||
.. ::python
|
.. ::python
|
||||||
|
@ -186,7 +189,7 @@ def pub(
|
||||||
|
|
||||||
|
|
||||||
The publisher must be called passing in the following arguments:
|
The publisher must be called passing in the following arguments:
|
||||||
- ``topics: Set[str]`` the topic sequence or "subscriptions"
|
- ``topics: set[str]`` the topic sequence or "subscriptions"
|
||||||
- ``task_name: str`` the task to use (if ``tasks`` was passed)
|
- ``task_name: str`` the task to use (if ``tasks`` was passed)
|
||||||
- ``ctx: Context`` the tractor context (only needed if calling the
|
- ``ctx: Context`` the tractor context (only needed if calling the
|
||||||
pub func without a nursery, otherwise this is provided implicitly)
|
pub func without a nursery, otherwise this is provided implicitly)
|
||||||
|
@ -231,7 +234,7 @@ def pub(
|
||||||
if wrapped is None:
|
if wrapped is None:
|
||||||
return partial(pub, tasks=tasks)
|
return partial(pub, tasks=tasks)
|
||||||
|
|
||||||
task2lock: Dict[str, trio.StrictFIFOLock] = {}
|
task2lock: dict[str, trio.StrictFIFOLock] = {}
|
||||||
|
|
||||||
for name in tasks:
|
for name in tasks:
|
||||||
task2lock[name] = trio.StrictFIFOLock()
|
task2lock[name] = trio.StrictFIFOLock()
|
||||||
|
@ -243,7 +246,7 @@ def pub(
|
||||||
# `wrapt` docs
|
# `wrapt` docs
|
||||||
async def _execute(
|
async def _execute(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
topics: Set[str],
|
topics: set[str],
|
||||||
*args,
|
*args,
|
||||||
# *,
|
# *,
|
||||||
task_name: str = None, # default: only one task allocated
|
task_name: str = None, # default: only one task allocated
|
||||||
|
|
|
@ -24,7 +24,7 @@ Built-in messaging patterns, types, APIs and helpers.
|
||||||
# ``pkgutil.resolve_name()`` internally uses
|
# ``pkgutil.resolve_name()`` internally uses
|
||||||
# ``importlib.import_module()`` which can be filtered by inserting
|
# ``importlib.import_module()`` which can be filtered by inserting
|
||||||
# a ``MetaPathFinder`` into ``sys.meta_path`` (which we could do before
|
# a ``MetaPathFinder`` into ``sys.meta_path`` (which we could do before
|
||||||
# entering the ``Actor._process_messages()`` loop).
|
# entering the ``_runtime.process_messages()`` loop).
|
||||||
# - https://github.com/python/cpython/blob/main/Lib/pkgutil.py#L645
|
# - https://github.com/python/cpython/blob/main/Lib/pkgutil.py#L645
|
||||||
# - https://stackoverflow.com/questions/1350466/preventing-python-code-from-importing-certain-modules
|
# - https://stackoverflow.com/questions/1350466/preventing-python-code-from-importing-certain-modules
|
||||||
# - https://stackoverflow.com/a/63320902
|
# - https://stackoverflow.com/a/63320902
|
||||||
|
|
|
@ -102,6 +102,8 @@ async def gather_contexts(
|
||||||
# deliver control once all managers have started up
|
# deliver control once all managers have started up
|
||||||
await all_entered.wait()
|
await all_entered.wait()
|
||||||
|
|
||||||
|
# NOTE: order *should* be preserved in the output values
|
||||||
|
# since ``dict``s are now implicitly ordered.
|
||||||
yield tuple(unwrapped.values())
|
yield tuple(unwrapped.values())
|
||||||
|
|
||||||
# we don't need a try/finally since cancellation will be triggered
|
# we don't need a try/finally since cancellation will be triggered
|
||||||
|
|
Loading…
Reference in New Issue