forked from goodboy/tractor
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.remote_module_errors
parent
aa479d64b0
commit
5fab61412c
|
@ -17,7 +17,7 @@ from ._actor import (
|
||||||
)
|
)
|
||||||
from ._trionics import open_nursery
|
from ._trionics import open_nursery
|
||||||
from ._state import current_actor
|
from ._state import current_actor
|
||||||
from ._exceptions import RemoteActorError
|
from ._exceptions import RemoteActorError, ModuleNotExposed
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -29,6 +29,7 @@ __all__ = [
|
||||||
'Channel',
|
'Channel',
|
||||||
'MultiError',
|
'MultiError',
|
||||||
'RemoteActorError',
|
'RemoteActorError',
|
||||||
|
'ModuleNotExposed',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ from async_generator import asynccontextmanager, aclosing
|
||||||
|
|
||||||
from ._ipc import Channel, _connect_chan
|
from ._ipc import Channel, _connect_chan
|
||||||
from .log import get_console_log, get_logger
|
from .log import get_console_log, get_logger
|
||||||
from ._exceptions import pack_error, InternalActorError
|
from ._exceptions import pack_error, InternalActorError, ModuleNotExposed
|
||||||
from ._portal import (
|
from ._portal import (
|
||||||
Portal,
|
Portal,
|
||||||
open_portal,
|
open_portal,
|
||||||
|
@ -236,6 +236,12 @@ class Actor:
|
||||||
# self._mods.pop('test_discovery')
|
# self._mods.pop('test_discovery')
|
||||||
# TODO: how to test the above?
|
# 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(
|
async def _stream_handler(
|
||||||
self,
|
self,
|
||||||
stream: trio.SocketStream,
|
stream: trio.SocketStream,
|
||||||
|
@ -398,7 +404,14 @@ class Actor:
|
||||||
if ns == 'self':
|
if ns == 'self':
|
||||||
func = getattr(self, funcname)
|
func = getattr(self, funcname)
|
||||||
else:
|
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
|
# spin up a task for the requested function
|
||||||
log.debug(f"Spawning task for {func}")
|
log.debug(f"Spawning task for {func}")
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
"""
|
"""
|
||||||
Our classy exception set.
|
Our classy exception set.
|
||||||
"""
|
"""
|
||||||
|
import importlib
|
||||||
import builtins
|
import builtins
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
_this_mod = importlib.import_module(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RemoteActorError(Exception):
|
class RemoteActorError(Exception):
|
||||||
# TODO: local recontruction of remote exception deats
|
# TODO: local recontruction of remote exception deats
|
||||||
"Remote actor exception bundled locally"
|
"Remote actor exception bundled locally"
|
||||||
def __init__(self, message, type_str, **msgdata):
|
def __init__(self, message, type_str, **msgdata):
|
||||||
super().__init__(message)
|
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
|
self.msgdata = msgdata
|
||||||
|
|
||||||
# TODO: a trio.MultiError.catch like context manager
|
# TODO: a trio.MultiError.catch like context manager
|
||||||
|
@ -27,6 +39,10 @@ class NoResult(RuntimeError):
|
||||||
"No final result is expected for this actor"
|
"No final result is expected for this actor"
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleNotExposed(RuntimeError):
|
||||||
|
"The requested module is not exposed for RPC"
|
||||||
|
|
||||||
|
|
||||||
def pack_error(exc):
|
def pack_error(exc):
|
||||||
"""Create an "error message" for tranmission over
|
"""Create an "error message" for tranmission over
|
||||||
a channel (aka the wire).
|
a channel (aka the wire).
|
||||||
|
|
Loading…
Reference in New Issue