2018-06-07 04:26:49 +00:00
|
|
|
"""
|
2018-07-10 19:06:42 +00:00
|
|
|
tractor: An actor model micro-framework built on
|
|
|
|
``trio`` and ``multiprocessing``.
|
2018-06-07 04:26:49 +00:00
|
|
|
"""
|
2018-09-08 13:39:53 +00:00
|
|
|
import importlib
|
2018-06-07 04:26:49 +00:00
|
|
|
from functools import partial
|
2020-08-13 15:53:45 +00:00
|
|
|
from typing import Tuple, Any, Optional, List
|
2018-08-20 02:13:13 +00:00
|
|
|
import typing
|
2018-06-07 04:26:49 +00:00
|
|
|
|
2018-08-26 17:12:29 +00:00
|
|
|
import trio # type: ignore
|
2018-11-19 19:15:28 +00:00
|
|
|
from trio import MultiError
|
2018-06-07 04:26:49 +00:00
|
|
|
|
2019-03-19 01:32:08 +00:00
|
|
|
from . import log
|
2019-03-26 01:36:13 +00:00
|
|
|
from ._ipc import _connect_chan, Channel
|
|
|
|
from ._streaming import Context, stream
|
2019-03-24 15:37:11 +00:00
|
|
|
from ._discovery import get_arbiter, find_actor, wait_for_actor
|
|
|
|
from ._actor import Actor, _start_actor, Arbiter
|
2018-07-14 20:09:05 +00:00
|
|
|
from ._trionics import open_nursery
|
|
|
|
from ._state import current_actor
|
2020-10-13 15:03:55 +00:00
|
|
|
from . import _state
|
2019-01-01 20:58:38 +00:00
|
|
|
from ._exceptions import RemoteActorError, ModuleNotExposed
|
2020-07-23 17:32:29 +00:00
|
|
|
from ._debug import breakpoint, post_mortem
|
2019-03-06 05:29:07 +00:00
|
|
|
from . import _spawn
|
2020-10-16 02:47:11 +00:00
|
|
|
from . import msg
|
2018-07-14 20:09:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
2020-10-13 15:03:55 +00:00
|
|
|
'breakpoint',
|
|
|
|
'post_mortem',
|
2018-08-13 03:59:19 +00:00
|
|
|
'current_actor',
|
|
|
|
'find_actor',
|
|
|
|
'get_arbiter',
|
|
|
|
'open_nursery',
|
2018-11-19 19:15:28 +00:00
|
|
|
'wait_for_actor',
|
2018-08-13 03:59:19 +00:00
|
|
|
'Channel',
|
2019-03-15 23:40:34 +00:00
|
|
|
'Context',
|
2019-03-26 01:36:13 +00:00
|
|
|
'stream',
|
2018-11-19 19:15:28 +00:00
|
|
|
'MultiError',
|
|
|
|
'RemoteActorError',
|
2019-01-01 20:58:38 +00:00
|
|
|
'ModuleNotExposed',
|
2019-01-16 17:19:01 +00:00
|
|
|
'msg'
|
2018-07-14 20:09:05 +00:00
|
|
|
]
|
2018-06-07 04:26:49 +00:00
|
|
|
|
2018-06-12 19:17:48 +00:00
|
|
|
|
2018-06-19 19:30:50 +00:00
|
|
|
# set at startup and after forks
|
2018-07-04 16:51:04 +00:00
|
|
|
_default_arbiter_host = '127.0.0.1'
|
|
|
|
_default_arbiter_port = 1616
|
2018-06-12 19:17:48 +00:00
|
|
|
|
|
|
|
|
2018-08-20 02:13:13 +00:00
|
|
|
async def _main(
|
|
|
|
async_fn: typing.Callable[..., typing.Awaitable],
|
2018-09-08 13:39:53 +00:00
|
|
|
args: Tuple,
|
2019-12-10 05:55:03 +00:00
|
|
|
arbiter_addr: Tuple[str, int],
|
|
|
|
name: Optional[str] = None,
|
2020-09-12 15:48:57 +00:00
|
|
|
start_method: Optional[str] = None,
|
|
|
|
debug_mode: bool = False,
|
2020-10-13 15:03:55 +00:00
|
|
|
**kwargs,
|
2018-08-20 02:13:13 +00:00
|
|
|
) -> typing.Any:
|
2018-07-04 16:51:04 +00:00
|
|
|
"""Async entry point for ``tractor``.
|
|
|
|
"""
|
2019-03-19 01:32:08 +00:00
|
|
|
logger = log.get_logger('tractor')
|
2020-08-03 02:30:03 +00:00
|
|
|
|
2020-10-16 02:47:11 +00:00
|
|
|
# mark top most level process as root actor
|
|
|
|
_state._runtime_vars['_is_root'] = True
|
|
|
|
|
2020-09-12 15:48:57 +00:00
|
|
|
if start_method is not None:
|
|
|
|
_spawn.try_set_start_method(start_method)
|
|
|
|
|
2020-10-13 18:20:44 +00:00
|
|
|
if debug_mode and _spawn._spawn_method == 'trio':
|
2020-09-12 15:48:57 +00:00
|
|
|
_state._runtime_vars['_debug_mode'] = True
|
2020-10-16 02:47:11 +00:00
|
|
|
|
2020-09-12 15:48:57 +00:00
|
|
|
# expose internal debug module to every actor allowing
|
|
|
|
# for use of ``await tractor.breakpoint()``
|
|
|
|
kwargs.setdefault('rpc_module_paths', []).append('tractor._debug')
|
2020-10-16 02:47:11 +00:00
|
|
|
|
2020-10-13 18:20:44 +00:00
|
|
|
elif debug_mode:
|
2020-10-16 02:47:11 +00:00
|
|
|
raise RuntimeError(
|
|
|
|
"Debug mode is only supported for the `trio` backend!"
|
|
|
|
)
|
2020-09-12 15:48:57 +00:00
|
|
|
|
2018-09-05 22:13:23 +00:00
|
|
|
main = partial(async_fn, *args)
|
2020-08-03 02:30:03 +00:00
|
|
|
|
2018-07-04 16:51:04 +00:00
|
|
|
arbiter_addr = (host, port) = arbiter_addr or (
|
2020-08-03 02:30:03 +00:00
|
|
|
_default_arbiter_host,
|
|
|
|
_default_arbiter_port
|
|
|
|
)
|
|
|
|
|
2019-03-19 01:32:08 +00:00
|
|
|
loglevel = kwargs.get('loglevel', log.get_loglevel())
|
|
|
|
if loglevel is not None:
|
|
|
|
log._default_loglevel = loglevel
|
|
|
|
log.get_console_log(loglevel)
|
2018-07-07 20:50:59 +00:00
|
|
|
|
2018-07-04 16:51:04 +00:00
|
|
|
# make a temporary connection to see if an arbiter exists
|
|
|
|
arbiter_found = False
|
|
|
|
try:
|
|
|
|
async with _connect_chan(host, port):
|
|
|
|
arbiter_found = True
|
|
|
|
except OSError:
|
2019-03-19 01:32:08 +00:00
|
|
|
logger.warning(f"No actor could be found @ {host}:{port}")
|
2018-07-04 16:51:04 +00:00
|
|
|
|
2018-07-06 06:36:21 +00:00
|
|
|
# create a local actor and start up its main routine/task
|
2018-07-04 16:51:04 +00:00
|
|
|
if arbiter_found: # we were able to connect to an arbiter
|
2019-03-19 01:32:08 +00:00
|
|
|
logger.info(f"Arbiter seems to exist @ {host}:{port}")
|
2018-07-04 16:51:04 +00:00
|
|
|
actor = Actor(
|
|
|
|
name or 'anonymous',
|
2018-07-11 04:20:50 +00:00
|
|
|
arbiter_addr=arbiter_addr,
|
2018-07-04 16:51:04 +00:00
|
|
|
**kwargs
|
|
|
|
)
|
2018-07-10 19:06:42 +00:00
|
|
|
host, port = (host, 0)
|
2018-07-04 16:51:04 +00:00
|
|
|
else:
|
|
|
|
# start this local actor as the arbiter
|
2018-07-10 19:06:42 +00:00
|
|
|
actor = Arbiter(
|
2018-08-01 19:15:18 +00:00
|
|
|
name or 'arbiter', arbiter_addr=arbiter_addr, **kwargs)
|
2018-07-04 16:51:04 +00:00
|
|
|
|
2018-07-06 06:36:21 +00:00
|
|
|
# ``Actor._async_main()`` creates an internal nursery if one is not
|
|
|
|
# provided and thus blocks here until it's main task completes.
|
|
|
|
# Note that if the current actor is the arbiter it is desirable
|
|
|
|
# for it to stay up indefinitely until a re-election process has
|
|
|
|
# taken place - which is not implemented yet FYI).
|
2018-08-01 19:15:18 +00:00
|
|
|
return await _start_actor(
|
2019-12-10 05:55:03 +00:00
|
|
|
actor, main, host, port, arbiter_addr=arbiter_addr
|
|
|
|
)
|
2018-06-27 15:34:22 +00:00
|
|
|
|
2018-06-12 19:17:48 +00:00
|
|
|
|
2018-07-10 19:06:42 +00:00
|
|
|
def run(
|
2018-08-20 02:13:13 +00:00
|
|
|
async_fn: typing.Callable[..., typing.Awaitable],
|
2019-12-10 05:55:03 +00:00
|
|
|
*args,
|
|
|
|
name: Optional[str] = None,
|
2018-09-08 13:39:53 +00:00
|
|
|
arbiter_addr: Tuple[str, int] = (
|
2020-08-03 02:30:03 +00:00
|
|
|
_default_arbiter_host,
|
|
|
|
_default_arbiter_port,
|
|
|
|
),
|
2020-01-23 06:15:46 +00:00
|
|
|
# either the `multiprocessing` start method:
|
2019-03-09 00:54:27 +00:00
|
|
|
# https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
|
2020-09-12 15:48:57 +00:00
|
|
|
# OR `trio` (the new default).
|
2020-01-27 02:13:29 +00:00
|
|
|
start_method: Optional[str] = None,
|
2020-07-23 17:32:29 +00:00
|
|
|
debug_mode: bool = False,
|
2019-12-10 05:55:03 +00:00
|
|
|
**kwargs,
|
2018-09-08 13:39:53 +00:00
|
|
|
) -> Any:
|
2018-06-12 19:17:48 +00:00
|
|
|
"""Run a trio-actor async function in process.
|
|
|
|
|
|
|
|
This is tractor's main entry and the start point for any async actor.
|
|
|
|
"""
|
2020-09-12 15:48:57 +00:00
|
|
|
return trio.run(
|
|
|
|
partial(
|
|
|
|
# our entry
|
|
|
|
_main,
|
2020-07-23 17:32:29 +00:00
|
|
|
|
2020-09-12 15:48:57 +00:00
|
|
|
# user entry point
|
|
|
|
async_fn,
|
|
|
|
args,
|
2020-07-23 17:32:29 +00:00
|
|
|
|
2020-09-12 15:48:57 +00:00
|
|
|
# global kwargs
|
|
|
|
arbiter_addr=arbiter_addr,
|
|
|
|
name=name,
|
|
|
|
start_method=start_method,
|
|
|
|
debug_mode=debug_mode,
|
|
|
|
**kwargs,
|
|
|
|
)
|
|
|
|
)
|
2018-09-08 13:39:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def run_daemon(
|
2020-08-13 15:53:45 +00:00
|
|
|
rpc_module_paths: List[str],
|
2018-09-08 13:39:53 +00:00
|
|
|
**kwargs
|
|
|
|
) -> None:
|
2018-09-14 20:33:45 +00:00
|
|
|
"""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
|
2018-11-09 06:40:12 +00:00
|
|
|
is meant to run forever responding to RPC requests.
|
2018-09-11 01:56:40 +00:00
|
|
|
"""
|
2020-08-13 15:53:45 +00:00
|
|
|
kwargs['rpc_module_paths'] = list(rpc_module_paths)
|
2018-09-10 19:19:49 +00:00
|
|
|
|
2018-09-14 20:33:45 +00:00
|
|
|
for path in rpc_module_paths:
|
2018-09-11 01:56:40 +00:00
|
|
|
importlib.import_module(path)
|
|
|
|
|
2018-09-10 19:19:49 +00:00
|
|
|
return run(partial(trio.sleep, float('inf')), **kwargs)
|