2023-03-08 21:28:38 +00:00
|
|
|
# piker: trading gear for hackers
|
|
|
|
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
|
|
|
|
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
"""
|
|
|
|
``tractor`` wrapping + default config to bootstrap the `pikerd`.
|
|
|
|
|
|
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from typing import (
|
|
|
|
Any,
|
|
|
|
ClassVar,
|
|
|
|
)
|
|
|
|
from contextlib import (
|
|
|
|
asynccontextmanager as acm,
|
|
|
|
)
|
|
|
|
|
|
|
|
import tractor
|
|
|
|
|
2023-03-22 16:07:08 +00:00
|
|
|
from ._util import (
|
2023-03-08 21:28:38 +00:00
|
|
|
get_console_log,
|
|
|
|
)
|
|
|
|
from ._mngr import (
|
2024-06-21 19:34:57 +00:00
|
|
|
open_service_mngr,
|
|
|
|
ServiceMngr,
|
2023-03-08 21:28:38 +00:00
|
|
|
)
|
|
|
|
from ._registry import ( # noqa
|
|
|
|
_tractor_kwargs,
|
|
|
|
_default_reg_addr,
|
|
|
|
open_registry,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-09-28 16:13:34 +00:00
|
|
|
def get_runtime_vars() -> dict[str, Any]:
|
2023-03-08 21:28:38 +00:00
|
|
|
'''
|
|
|
|
Deliver ``tractor`` related runtime variables in a `dict`.
|
|
|
|
|
|
|
|
'''
|
|
|
|
return _tractor_kwargs
|
|
|
|
|
|
|
|
|
|
|
|
@acm
|
|
|
|
async def open_piker_runtime(
|
|
|
|
name: str,
|
2023-10-03 17:36:22 +00:00
|
|
|
registry_addrs: list[tuple[str, int]] = [],
|
2023-09-28 16:13:34 +00:00
|
|
|
|
2023-03-08 21:28:38 +00:00
|
|
|
enable_modules: list[str] = [],
|
2024-06-21 19:34:57 +00:00
|
|
|
loglevel: str|None = None,
|
2023-03-08 21:28:38 +00:00
|
|
|
|
|
|
|
# XXX NOTE XXX: you should pretty much never want debug mode
|
|
|
|
# for data daemons when running in production.
|
|
|
|
debug_mode: bool = False,
|
|
|
|
|
|
|
|
# TODO: once we have `rsyscall` support we will read a config
|
|
|
|
# and spawn the service tree distributed per that.
|
|
|
|
start_method: str = 'trio',
|
|
|
|
|
2024-06-21 19:34:57 +00:00
|
|
|
tractor_runtime_overrides: dict|None = None,
|
2023-03-08 21:28:38 +00:00
|
|
|
**tractor_kwargs,
|
|
|
|
|
|
|
|
) -> tuple[
|
|
|
|
tractor.Actor,
|
2023-09-28 16:13:34 +00:00
|
|
|
list[tuple[str, int]],
|
2023-03-08 21:28:38 +00:00
|
|
|
]:
|
|
|
|
'''
|
|
|
|
Start a piker actor who's runtime will automatically sync with
|
|
|
|
existing piker actors on the local link based on configuration.
|
|
|
|
|
|
|
|
Can be called from a subactor or any program that needs to start
|
|
|
|
a root actor.
|
|
|
|
|
|
|
|
'''
|
2023-12-04 18:00:04 +00:00
|
|
|
# check for existing runtime, boot it
|
|
|
|
# if not already running.
|
2023-03-08 21:28:38 +00:00
|
|
|
try:
|
2023-11-21 20:18:52 +00:00
|
|
|
actor = tractor.current_actor()
|
2023-03-08 21:28:38 +00:00
|
|
|
except tractor._exceptions.NoRuntime:
|
2023-03-09 19:09:12 +00:00
|
|
|
tractor._state._runtime_vars[
|
2023-09-28 16:13:34 +00:00
|
|
|
'piker_vars'
|
|
|
|
] = tractor_runtime_overrides
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2023-10-03 17:36:22 +00:00
|
|
|
# NOTE: if no registrar list passed used the default of just
|
|
|
|
# setting it as the root actor on localhost.
|
2023-09-28 16:13:34 +00:00
|
|
|
registry_addrs = (
|
|
|
|
registry_addrs
|
|
|
|
or [_default_reg_addr]
|
|
|
|
)
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2024-01-04 14:59:15 +00:00
|
|
|
if ems := tractor_kwargs.pop('enable_modules', None):
|
2023-12-22 01:40:00 +00:00
|
|
|
# import pdbp; pdbp.set_trace()
|
|
|
|
enable_modules.extend(ems)
|
|
|
|
|
2023-03-08 21:28:38 +00:00
|
|
|
async with (
|
|
|
|
tractor.open_root_actor(
|
|
|
|
|
|
|
|
# passed through to ``open_root_actor``
|
2023-09-28 16:13:34 +00:00
|
|
|
registry_addrs=registry_addrs,
|
2023-03-08 21:28:38 +00:00
|
|
|
name=name,
|
|
|
|
loglevel=loglevel,
|
|
|
|
debug_mode=debug_mode,
|
|
|
|
start_method=start_method,
|
|
|
|
|
|
|
|
# TODO: eventually we should be able to avoid
|
|
|
|
# having the root have more then permissions to
|
|
|
|
# spawn other specialized daemons I think?
|
|
|
|
enable_modules=enable_modules,
|
|
|
|
|
2024-06-21 19:34:57 +00:00
|
|
|
# TODO: how to configure this?
|
|
|
|
# keep it on by default if debug mode is set?
|
|
|
|
# maybe_enable_greenback=debug_mode,
|
|
|
|
|
2023-03-08 21:28:38 +00:00
|
|
|
**tractor_kwargs,
|
2023-11-21 20:18:52 +00:00
|
|
|
) as actor,
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2023-09-28 16:13:34 +00:00
|
|
|
open_registry(
|
|
|
|
registry_addrs,
|
|
|
|
ensure_exists=False,
|
|
|
|
) as addrs,
|
2023-03-08 21:28:38 +00:00
|
|
|
):
|
2023-11-21 20:18:52 +00:00
|
|
|
assert actor is tractor.current_actor()
|
2023-03-08 21:28:38 +00:00
|
|
|
yield (
|
2023-11-21 20:18:52 +00:00
|
|
|
actor,
|
2023-09-28 16:13:34 +00:00
|
|
|
addrs,
|
2023-03-08 21:28:38 +00:00
|
|
|
)
|
|
|
|
else:
|
2023-09-28 16:13:34 +00:00
|
|
|
async with open_registry(
|
|
|
|
registry_addrs
|
|
|
|
) as addrs:
|
2023-03-08 21:28:38 +00:00
|
|
|
yield (
|
|
|
|
actor,
|
2023-09-28 16:13:34 +00:00
|
|
|
addrs,
|
2023-03-08 21:28:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-09-28 16:13:34 +00:00
|
|
|
_root_dname: str = 'pikerd'
|
|
|
|
_root_modules: list[str] = [
|
2023-03-09 17:22:33 +00:00
|
|
|
__name__,
|
|
|
|
'piker.service._daemon',
|
2023-04-21 19:47:54 +00:00
|
|
|
'piker.brokers._daemon',
|
|
|
|
|
2023-03-09 17:22:33 +00:00
|
|
|
'piker.clearing._ems',
|
|
|
|
'piker.clearing._client',
|
2023-04-21 19:47:54 +00:00
|
|
|
|
2023-03-09 17:22:33 +00:00
|
|
|
'piker.data._sampling',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2023-03-08 21:28:38 +00:00
|
|
|
@acm
|
|
|
|
async def open_pikerd(
|
2023-09-28 16:13:34 +00:00
|
|
|
registry_addrs: list[tuple[str, int]],
|
2023-03-08 21:28:38 +00:00
|
|
|
|
|
|
|
loglevel: str | None = None,
|
|
|
|
|
|
|
|
# XXX: you should pretty much never want debug mode
|
|
|
|
# for data daemons when running in production.
|
|
|
|
debug_mode: bool = False,
|
|
|
|
|
2023-03-09 19:09:12 +00:00
|
|
|
**kwargs,
|
|
|
|
|
2024-06-21 19:34:57 +00:00
|
|
|
) -> ServiceMngr:
|
2023-03-08 21:28:38 +00:00
|
|
|
'''
|
2024-06-21 19:34:57 +00:00
|
|
|
Start a root piker daemon actor (aka `pikerd`) with an indefinite
|
|
|
|
lifetime.
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2024-06-21 19:34:57 +00:00
|
|
|
A root actor-nursery is created which can be used to spawn and
|
|
|
|
supervise underling service sub-actors (see below).
|
2023-03-08 21:28:38 +00:00
|
|
|
|
|
|
|
'''
|
2024-01-04 14:59:15 +00:00
|
|
|
# NOTE: for the root daemon we always enable the root
|
|
|
|
# mod set and we `list.extend()` it into wtv the
|
|
|
|
# caller requested.
|
|
|
|
# TODO: make this mod set more strict?
|
|
|
|
# -[ ] eventually we should be able to avoid
|
|
|
|
# having the root have more then permissions to spawn other
|
|
|
|
# specialized daemons I think?
|
|
|
|
ems: list[str] = kwargs.setdefault('enable_modules', [])
|
|
|
|
ems.extend(_root_modules)
|
|
|
|
|
2023-03-08 21:28:38 +00:00
|
|
|
async with (
|
|
|
|
open_piker_runtime(
|
|
|
|
|
|
|
|
name=_root_dname,
|
|
|
|
loglevel=loglevel,
|
|
|
|
debug_mode=debug_mode,
|
2023-09-28 16:13:34 +00:00
|
|
|
registry_addrs=registry_addrs,
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2023-03-09 19:09:12 +00:00
|
|
|
**kwargs,
|
|
|
|
|
2023-09-28 16:13:34 +00:00
|
|
|
) as (
|
|
|
|
root_actor,
|
|
|
|
reg_addrs,
|
|
|
|
),
|
2023-03-08 21:28:38 +00:00
|
|
|
):
|
2023-09-28 16:13:34 +00:00
|
|
|
for addr in reg_addrs:
|
|
|
|
if addr not in root_actor.accept_addrs:
|
|
|
|
raise RuntimeError(
|
|
|
|
f'`pikerd` failed to bind on {addr}!\n'
|
|
|
|
'Maybe you have another daemon already running?'
|
|
|
|
)
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2024-06-21 19:34:57 +00:00
|
|
|
mngr: ServiceMngr
|
|
|
|
async with open_service_mngr(
|
|
|
|
debug_mode=debug_mode,
|
|
|
|
) as mngr:
|
|
|
|
yield mngr
|
2023-03-08 21:28:38 +00:00
|
|
|
|
|
|
|
|
2023-03-09 23:34:21 +00:00
|
|
|
# TODO: do we even need this?
|
|
|
|
# @acm
|
|
|
|
# async def maybe_open_runtime(
|
2024-06-21 19:34:57 +00:00
|
|
|
# loglevel: str|None = None,
|
2023-03-09 23:34:21 +00:00
|
|
|
# **kwargs,
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2023-03-09 23:34:21 +00:00
|
|
|
# ) -> None:
|
|
|
|
# '''
|
|
|
|
# Start the ``tractor`` runtime (a root actor) if none exists.
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2023-03-09 23:34:21 +00:00
|
|
|
# '''
|
|
|
|
# name = kwargs.pop('name')
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2023-03-09 23:34:21 +00:00
|
|
|
# if not tractor.current_actor(err_on_no_runtime=False):
|
|
|
|
# async with open_piker_runtime(
|
|
|
|
# name,
|
|
|
|
# loglevel=loglevel,
|
|
|
|
# **kwargs,
|
|
|
|
# ) as (_, addr):
|
|
|
|
# yield addr,
|
|
|
|
# else:
|
|
|
|
# async with open_registry() as addr:
|
|
|
|
# yield addr
|
2023-03-08 21:28:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
@acm
|
|
|
|
async def maybe_open_pikerd(
|
2023-09-28 16:13:34 +00:00
|
|
|
registry_addrs: list[tuple[str, int]] | None = None,
|
2023-03-08 21:28:38 +00:00
|
|
|
|
2023-09-28 16:13:34 +00:00
|
|
|
loglevel: str | None = None,
|
2023-03-08 21:28:38 +00:00
|
|
|
**kwargs,
|
|
|
|
|
2024-06-21 19:34:57 +00:00
|
|
|
) -> tractor._portal.Portal | ClassVar[ServiceMngr]:
|
2023-03-08 21:28:38 +00:00
|
|
|
'''
|
|
|
|
If no ``pikerd`` daemon-root-actor can be found start it and
|
|
|
|
yield up (we should probably figure out returning a portal to self
|
|
|
|
though).
|
|
|
|
|
|
|
|
'''
|
|
|
|
if loglevel:
|
|
|
|
get_console_log(loglevel)
|
|
|
|
|
|
|
|
# subtle, we must have the runtime up here or portal lookup will fail
|
2023-03-09 23:34:21 +00:00
|
|
|
query_name = kwargs.pop(
|
|
|
|
'name',
|
|
|
|
f'piker_query_{os.getpid()}',
|
|
|
|
)
|
2023-03-08 21:28:38 +00:00
|
|
|
|
|
|
|
# TODO: if we need to make the query part faster we could not init
|
|
|
|
# an actor runtime and instead just hit the socket?
|
|
|
|
# from tractor._ipc import _connect_chan, Channel
|
|
|
|
# async with _connect_chan(host, port) as chan:
|
|
|
|
# async with open_portal(chan) as arb_portal:
|
|
|
|
# yield arb_portal
|
|
|
|
|
2023-11-21 20:18:52 +00:00
|
|
|
registry_addrs: list[tuple[str, int]] = (
|
|
|
|
registry_addrs
|
|
|
|
or [_default_reg_addr]
|
|
|
|
)
|
2023-09-28 16:13:34 +00:00
|
|
|
|
2023-11-21 20:18:52 +00:00
|
|
|
pikerd_portal: tractor.Portal | None
|
2023-03-08 21:28:38 +00:00
|
|
|
async with (
|
|
|
|
open_piker_runtime(
|
|
|
|
name=query_name,
|
2023-09-28 16:13:34 +00:00
|
|
|
registry_addrs=registry_addrs,
|
2023-03-08 21:28:38 +00:00
|
|
|
loglevel=loglevel,
|
|
|
|
**kwargs,
|
2023-11-21 20:18:52 +00:00
|
|
|
) as (actor, addrs),
|
2023-12-08 22:43:52 +00:00
|
|
|
):
|
|
|
|
if _root_dname in actor.uid:
|
|
|
|
yield None
|
|
|
|
return
|
2023-03-09 19:09:12 +00:00
|
|
|
|
2023-12-08 22:43:52 +00:00
|
|
|
# NOTE: IFF running in disti mode, try to attach to any
|
|
|
|
# existing (host-local) `pikerd`.
|
|
|
|
else:
|
|
|
|
async with tractor.find_actor(
|
|
|
|
_root_dname,
|
|
|
|
registry_addrs=registry_addrs,
|
|
|
|
only_first=True,
|
|
|
|
# raise_on_none=True,
|
|
|
|
) as pikerd_portal:
|
2023-11-21 20:18:52 +00:00
|
|
|
|
2023-12-08 22:43:52 +00:00
|
|
|
# connect to any existing remote daemon presuming its
|
|
|
|
# registry socket was selected.
|
|
|
|
if pikerd_portal is not None:
|
2023-11-21 20:18:52 +00:00
|
|
|
|
2023-12-08 22:43:52 +00:00
|
|
|
# sanity check that we are actually connecting to
|
|
|
|
# a remote process and not ourselves.
|
|
|
|
assert actor.uid != pikerd_portal.channel.uid
|
|
|
|
assert registry_addrs
|
2023-11-21 20:18:52 +00:00
|
|
|
|
2023-12-08 22:43:52 +00:00
|
|
|
yield pikerd_portal
|
|
|
|
return
|
2023-03-08 21:28:38 +00:00
|
|
|
|
|
|
|
# presume pikerd role since no daemon could be found at
|
|
|
|
# configured address
|
|
|
|
async with open_pikerd(
|
|
|
|
loglevel=loglevel,
|
2023-09-28 16:13:34 +00:00
|
|
|
registry_addrs=registry_addrs,
|
2023-03-09 23:34:21 +00:00
|
|
|
|
|
|
|
# passthrough to ``tractor`` init
|
2023-03-09 19:09:12 +00:00
|
|
|
**kwargs,
|
|
|
|
|
2023-03-08 21:28:38 +00:00
|
|
|
) as service_manager:
|
|
|
|
# in the case where we're starting up the
|
|
|
|
# tractor-piker runtime stack in **this** process
|
|
|
|
# we return no portal to self.
|
|
|
|
assert service_manager
|
|
|
|
yield service_manager
|