# tractor: structured concurrent "actors". # Copyright 2018-eternity Tyler Goodlet. # 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 . """ The "bootloader" for sub-actors spawned via the native `trio` backend (the default `python -m tractor._child` CLI entry) and the in-process `subint` backend (`tractor.spawn._subint`). """ from __future__ import annotations import argparse from ast import literal_eval from typing import TYPE_CHECKING from .runtime._runtime import Actor from .spawn._entry import _trio_main if TYPE_CHECKING: from .discovery._addr import UnwrappedAddress from .spawn._spawn import SpawnMethodKey def parse_uid(arg): name, uuid = literal_eval(arg) # ensure 2 elements return str(name), str(uuid) # ensures str encoding def parse_ipaddr(arg): try: return literal_eval(arg) except (ValueError, SyntaxError): # UDS: try to interpret as a straight up str return arg def _actor_child_main( uid: tuple[str, str], loglevel: str | None, parent_addr: UnwrappedAddress | None, infect_asyncio: bool, spawn_method: SpawnMethodKey = 'trio', ) -> None: ''' Construct the child `Actor` and dispatch to `_trio_main()`. Shared entry shape used by both the `python -m tractor._child` CLI (trio/mp subproc backends) and the `subint` backend, which invokes this from inside a fresh `concurrent.interpreters` sub-interpreter via `Interpreter.call()`. ''' # Apply defensive monkey-patches for upstream `trio` # bugs we've encountered while running tractor — see # `tractor.trionics.patches` for the catalog + # per-patch upstream-fix tracking. Must run BEFORE # any trio runtime init. from .trionics.patches import apply_all apply_all() subactor = Actor( name=uid[0], uuid=uid[1], loglevel=loglevel, spawn_method=spawn_method, ) # XXX, set a stable OS-level proc-title BEFORE entering # the trio runtime so `ps`/`top`/`acli.pytree` and # orphan-reapers can identify this actor for its full # lifetime — e.g. # `tractor[doggy@1027301b]` # vs. the default uninformative # `python -m tractor._child --uid (...)` # # `setproctitle` mutates `argv[0]` (visible in # `/proc//cmdline`) AND the kernel `comm` # (visible in `/proc//comm`, kernel-truncated to # ~15 bytes, but preserved through zombie state). Both # surfaces are enough for `_testing._reap` / # `acli.reap` orphan- and zombie-detection to identify # tractor sub-actors via intrinsic signals — no cwd, # venv path, or env-var coincidence-of-implementation # matching needed. # # NB: an earlier draft also wrote `TRACTOR_AID` to # `os.environ` here for `pgrep --env`-style discovery, # but Linux snapshots `/proc//environ` at exec/fork # time, so post-fork runtime mutations don't propagate # to the kernel-visible env. The proc-title path # provides equivalent ergonomics # (`pgrep -f 'tractor\['`) without that gotcha. from .devx._proctitle import set_actor_proctitle set_actor_proctitle(subactor) _trio_main( subactor, parent_addr=parent_addr, infect_asyncio=infect_asyncio, ) if __name__ == "__main__": __tracebackhide__: bool = True parser = argparse.ArgumentParser() parser.add_argument("--uid", type=parse_uid) parser.add_argument("--loglevel", type=str) parser.add_argument("--parent_addr", type=parse_ipaddr) parser.add_argument("--asyncio", action='store_true') args = parser.parse_args() _actor_child_main( uid=args.uid, loglevel=args.loglevel, parent_addr=args.parent_addr, infect_asyncio=args.asyncio, spawn_method='trio', )