From 5fab61412c2294f59e950ab343af681ebf9c98c7 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 1 Jan 2019 15:58:38 -0500 Subject: [PATCH] Propagate module import and func lookup errors RPC module/function lookups should not cause the target actor to crash. This change instead ships the error back to the calling actor allowing for the remote actor to continue running depending on the caller's error handling logic. Adds a new `ModuleNotExposed` error to accommodate. --- tractor/__init__.py | 3 ++- tractor/_actor.py | 17 +++++++++++++++-- tractor/_exceptions.py | 18 +++++++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/tractor/__init__.py b/tractor/__init__.py index 0667bd5..d8d6489 100644 --- a/tractor/__init__.py +++ b/tractor/__init__.py @@ -17,7 +17,7 @@ from ._actor import ( ) from ._trionics import open_nursery from ._state import current_actor -from ._exceptions import RemoteActorError +from ._exceptions import RemoteActorError, ModuleNotExposed __all__ = [ @@ -29,6 +29,7 @@ __all__ = [ 'Channel', 'MultiError', 'RemoteActorError', + 'ModuleNotExposed', ] diff --git a/tractor/_actor.py b/tractor/_actor.py index 750487a..d9a5ee1 100644 --- a/tractor/_actor.py +++ b/tractor/_actor.py @@ -15,7 +15,7 @@ from async_generator import asynccontextmanager, aclosing from ._ipc import Channel, _connect_chan from .log import get_console_log, get_logger -from ._exceptions import pack_error, InternalActorError +from ._exceptions import pack_error, InternalActorError, ModuleNotExposed from ._portal import ( Portal, open_portal, @@ -236,6 +236,12 @@ class Actor: # self._mods.pop('test_discovery') # TODO: how to test the above? + def _get_rpc_func(self, ns, funcname): + try: + return getattr(self._mods[ns], funcname) + except KeyError as err: + raise ModuleNotExposed(*err.args) + async def _stream_handler( self, stream: trio.SocketStream, @@ -398,7 +404,14 @@ class Actor: if ns == 'self': func = getattr(self, funcname) else: - func = getattr(self._mods[ns], funcname) + # complain to client about restricted modules + try: + func = self._get_rpc_func(ns, funcname) + except (ModuleNotExposed, AttributeError) as err: + err_msg = pack_error(err) + err_msg['cid'] = cid + await chan.send(err_msg) + continue # spin up a task for the requested function log.debug(f"Spawning task for {func}") diff --git a/tractor/_exceptions.py b/tractor/_exceptions.py index 7244396..efedcf0 100644 --- a/tractor/_exceptions.py +++ b/tractor/_exceptions.py @@ -1,16 +1,28 @@ """ Our classy exception set. """ +import importlib import builtins import traceback +_this_mod = importlib.import_module(__name__) + + class RemoteActorError(Exception): # TODO: local recontruction of remote exception deats "Remote actor exception bundled locally" def __init__(self, message, type_str, **msgdata): super().__init__(message) - self.type = getattr(builtins, type_str, Exception) + for ns in [builtins, _this_mod]: + try: + self.type = getattr(ns, type_str) + break + except AttributeError: + continue + else: + self.type = Exception + self.msgdata = msgdata # TODO: a trio.MultiError.catch like context manager @@ -27,6 +39,10 @@ class NoResult(RuntimeError): "No final result is expected for this actor" +class ModuleNotExposed(RuntimeError): + "The requested module is not exposed for RPC" + + def pack_error(exc): """Create an "error message" for tranmission over a channel (aka the wire).