From 2a4307975d7c577bcaff4d3c628197406f0bb163 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 29 Jan 2020 00:51:25 -0500 Subject: [PATCH] Fix that thing where the first example in your docs is supposed to work Thanks to @salotz for pointing out that the first example in the docs was broken. Though it's somewhat embarrassing this might also explain the problem in #79 and certain issues in #59... The solution here is to import the target RPC module using the its unique basename and absolute filepath in the sub-actor that requires it. Special handling for `__main__` and `__mp_main__` is needed since the spawned subprocess will have no knowledge about these parent- -state-specific module variables. Solution: map the modules name to the respective module file basename in the child process since the module variables will of course have different values in children. --- tractor/_actor.py | 45 +++++++++++++++++++++++++------------------- tractor/_spawn.py | 1 - tractor/_trionics.py | 1 - 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/tractor/_actor.py b/tractor/_actor.py index eb0570d..42656d2 100644 --- a/tractor/_actor.py +++ b/tractor/_actor.py @@ -151,6 +151,10 @@ async def _invoke( actor._ongoing_rpc_tasks.set() +def _get_mod_abspath(module): + return os.path.abspath(module.__file__) + + class Actor: """The fundamental concurrency primitive. @@ -178,9 +182,13 @@ class Actor: self.uid = (name, uid or str(uuid.uuid4())) mods = {} - for path in rpc_module_paths or (): - mod = importlib.import_module(path) - mods[path] = mod.__file__ + for name in rpc_module_paths or (): + mod = importlib.import_module(name) + suffix_index = mod.__file__.find('.py') + unique_modname = os.path.basename(mod.__file__[:suffix_index]) + mods[unique_modname] = _get_mod_abspath(mod) + if mod.__name__ == '__main__' or mod.__name__ == '__mp_main__': + self._main_mod = unique_modname self.rpc_module_paths = mods self._mods: dict = {} @@ -235,21 +243,20 @@ class Actor: code (if it exists). """ try: - for path, absfilepath in self.rpc_module_paths.items(): - log.debug(f"Attempting to import {path}") - # spec = importlib.util.spec_from_file_location( - # path, absfilepath) - # mod = importlib.util.module_from_spec(spec) + for modname, absfilepath in self.rpc_module_paths.items(): + sys.path.append(os.path.dirname(absfilepath)) + log.debug(f"Attempting to import {modname}@{absfilepath}") + spec = importlib.util.spec_from_file_location( + modname, absfilepath) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) # type: ignore + self._mods[modname] = mod # XXX append the allowed module to the python path # which should allow for relative (at least downward) # imports. Seems to be the only that will work currently # to get `trio-run-in-process` to import modules we "send # it". - sys.path.append(os.path.dirname(absfilepath)) - # spec.loader.exec_module(mod) - mod = importlib.import_module(path) - self._mods[path] = mod # if self.name != 'arbiter': # importlib.import_module('doggy') @@ -257,10 +264,14 @@ class Actor: except ModuleNotFoundError: # it is expected the corresponding `ModuleNotExposed` error # will be raised later - log.error(f"Failed to import {path} in {self.name}") + log.error(f"Failed to import {modname} in {self.name}") raise def _get_rpc_func(self, ns, funcname): + if ns == '__main__' or ns == '__mp_main__': + # lookup the specific module in the child denoted + # as `__main__`/`__mp_main__` in the parent + ns = self._main_mod try: return getattr(self._mods[ns], funcname) except KeyError as err: @@ -640,8 +651,6 @@ class Actor: # blocks here as expected until the channel server is # killed (i.e. this actor is cancelled or signalled by the parent) except Exception as err: - # if self.name == 'arbiter': - # import pdb; pdb.set_trace() if not registered_with_arbiter: log.exception( f"Actor errored and failed to register with arbiter " @@ -666,8 +675,6 @@ class Actor: raise finally: - # if self.name == 'arbiter': - # import pdb; pdb.set_trace() if registered_with_arbiter: await self._do_unreg(arbiter_addr) # terminate actor once all it's peers (actors that connected @@ -713,8 +720,8 @@ class Actor: port=accept_port, host=accept_host, ) ) - log.debug( - f"Started tcp server(s) on {[l.socket for l in listeners]}") # type: ignore + log.debug(f"Started tcp server(s) on" + " {[l.socket for l in listeners]}") # type: ignore self._listeners.extend(listeners) task_status.started() diff --git a/tractor/_spawn.py b/tractor/_spawn.py index f94fc62..3582f8b 100644 --- a/tractor/_spawn.py +++ b/tractor/_spawn.py @@ -158,7 +158,6 @@ async def new_proc( # passed through to actor main bind_addr: Tuple[str, int], parent_addr: Tuple[str, int], - begin_wait_phase: trio.Event, use_trio_run_in_process: bool = False, task_status: TaskStatus[Portal] = trio.TASK_STATUS_IGNORED ) -> None: diff --git a/tractor/_trionics.py b/tractor/_trionics.py index a20f8bb..4bb7467 100644 --- a/tractor/_trionics.py +++ b/tractor/_trionics.py @@ -78,7 +78,6 @@ class ActorNursery: self.errors, bind_addr, parent_addr, - nursery, ) async def run_in_actor(