Be explicit with `SpawnSpec` processing in subs

As per the outstanding TODO just above the redic `setattr()` loop in
`Actor._from_parent()`!!

Instead of all that risk-ay monkeying, add detailed comment-sections
around each explicit assignment of each `SpawnSpec` field, including
those that were already being explicitly set.

Those and other deats,
- ONLY enable the `.devx.debug._tty_lock` module from `Actor.__init__()`
  in the root actor.
- add a new `get_mod_nsps2fps()` to replace the loop in init and assign
  the initial `.enable_modules: dict[str, str]` from it.
- do `self.enable_modules.update(spawnspec.enable_modules)` instead of
  an overwrite and assert the table is by default empty in all
  subs.
repl_fixture
Tyler Goodlet 2025-05-13 17:39:53 -04:00
parent 1012581a0b
commit 006ee0752c
1 changed files with 65 additions and 28 deletions

View File

@ -115,6 +115,20 @@ def _get_mod_abspath(module):
return os.path.abspath(module.__file__) return os.path.abspath(module.__file__)
def get_mod_nsps2fps(mod_ns_paths: list[str]) -> dict[str, str]:
'''
Deliver a table of py module namespace-path-`str`s mapped to
their "physical" `.py` file paths in the file-sys.
'''
nsp2fp: dict[str, str] = {}
for nsp in mod_ns_paths:
mod: ModuleType = importlib.import_module(nsp)
nsp2fp[nsp] = _get_mod_abspath(mod)
return nsp2fp
class Actor: class Actor:
''' '''
The fundamental "runtime" concurrency primitive. The fundamental "runtime" concurrency primitive.
@ -219,13 +233,14 @@ class Actor:
# will be passed to children # will be passed to children
self._parent_main_data = _mp_fixup_main._mp_figure_out_main() self._parent_main_data = _mp_fixup_main._mp_figure_out_main()
# TODO? only add this when `is_debug_mode() == True` no?
# always include debugging tools module # always include debugging tools module
enable_modules.append('tractor.devx.debug') if _state.is_root_process():
enable_modules.append('tractor.devx.debug._tty_lock')
self.enable_modules: dict[str, str] = {} self.enable_modules: dict[str, str] = get_mod_nsps2fps(
for name in enable_modules: mod_ns_paths=enable_modules,
mod: ModuleType = importlib.import_module(name) )
self.enable_modules[name] = _get_mod_abspath(mod)
self._mods: dict[str, ModuleType] = {} self._mods: dict[str, ModuleType] = {}
self.loglevel: str = loglevel self.loglevel: str = loglevel
@ -729,25 +744,33 @@ class Actor:
f'Received invalid non-`SpawnSpec` payload !?\n' f'Received invalid non-`SpawnSpec` payload !?\n'
f'{spawnspec}\n' f'{spawnspec}\n'
) )
# ^^XXX TODO XXX^^^
# ^^TODO XXX!! when the `SpawnSpec` fails to decode # when the `SpawnSpec` fails to decode the above will
# the above will raise a `MsgTypeError` which if we # raise a `MsgTypeError` which if we do NOT ALSO
# do NOT ALSO RAISE it will tried to be pprinted in # RAISE it will tried to be pprinted in the
# the log.runtime() below.. # log.runtime() below..
# #
# SO we gotta look at how other `chan.recv()` calls # SO we gotta look at how other `chan.recv()` calls
# are wrapped and do the same for this spec receive! # are wrapped and do the same for this spec receive!
# -[ ] see `._rpc` likely has the answer? # -[ ] see `._rpc` likely has the answer?
# ^^^XXX NOTE XXX^^^, can't be called here!
# #
# XXX NOTE, can't be called here in subactor
# bc we haven't yet received the
# `SpawnSpec._runtime_vars: dict` which would
# declare whether `debug_mode` is set!
# breakpoint() # breakpoint()
# import pdbp; pdbp.set_trace() # import pdbp; pdbp.set_trace()
#
# => bc we haven't yet received the
# `spawnspec._runtime_vars` which contains
# `debug_mode: bool`..
# `SpawnSpec.bind_addrs`
# ---------------------
accept_addrs: list[UnwrappedAddress] = spawnspec.bind_addrs accept_addrs: list[UnwrappedAddress] = spawnspec.bind_addrs
# TODO: another `Struct` for rtvs.. # `SpawnSpec._runtime_vars`
# -------------------------
# => update process-wide globals
# TODO! -[ ] another `Struct` for rtvs..
rvs: dict[str, Any] = spawnspec._runtime_vars rvs: dict[str, Any] = spawnspec._runtime_vars
if rvs['_debug_mode']: if rvs['_debug_mode']:
from .devx import ( from .devx import (
@ -805,18 +828,20 @@ class Actor:
f'self._infected_aio = {aio_attr}\n' f'self._infected_aio = {aio_attr}\n'
) )
if aio_rtv: if aio_rtv:
assert trio_runtime.GLOBAL_RUN_CONTEXT.runner.is_guest assert (
# ^TODO^ possibly add a `sniffio` or trio_runtime.GLOBAL_RUN_CONTEXT.runner.is_guest
# `trio` pub-API for `is_guest_mode()`? # and
# ^TODO^ possibly add a `sniffio` or
# `trio` pub-API for `is_guest_mode()`?
)
rvs['_is_root'] = False # obvi XD rvs['_is_root'] = False # obvi XD
# update process-wide globals
_state._runtime_vars.update(rvs) _state._runtime_vars.update(rvs)
# XXX: ``msgspec`` doesn't support serializing tuples # `SpawnSpec.reg_addrs`
# so just cash manually here since it's what our # ---------------------
# internals expect. # => update parent provided registrar contact info
# #
self.reg_addrs = [ self.reg_addrs = [
# TODO: we don't really NEED these as tuples? # TODO: we don't really NEED these as tuples?
@ -827,12 +852,24 @@ class Actor:
for val in spawnspec.reg_addrs for val in spawnspec.reg_addrs
] ]
# TODO: better then monkey patching.. # `SpawnSpec.enable_modules`
# -[ ] maybe read the actual f#$-in `._spawn_spec` XD # ---------------------
for _, attr, value in pretty_struct.iter_fields( # => extend RPC-python-module (capabilities) with
spawnspec, # those permitted by parent.
): #
setattr(self, attr, value) # NOTE, only the root actor should have
# a pre-permitted entry for `.devx.debug._tty_lock`.
assert not self.enable_modules
self.enable_modules.update(
spawnspec.enable_modules
)
self._parent_main_data = spawnspec._parent_main_data
# XXX QUESTION(s)^^^
# -[ ] already set in `.__init__()` right, but how is
# it diff from this blatant parent copy?
# -[ ] do we need/want the .__init__() value in
# just the root case orr?
return ( return (
chan, chan,