Fix unregistered-remote-error-type relay crash

Make `RemoteActorError` resilient to unresolved
custom error types so that errors from remote actors
always relay back to the caller - even when the user
hasn't called `reg_err_types()` to register the exc type.

Deats,
- `.src_type`: log warning + return `None` instead
  of raising `TypeError` which was crashing the
  entire `_deliver_msg()` -> `pformat()` chain
  before the error could be relayed.
- `.boxed_type_str`: fallback to `_ipc_msg.boxed_type_str`
  when the type obj can't be resolved so the type *name* is always
  available.
- `unwrap_src_err()`: fallback to `RuntimeError` preserving
  original type name + traceback.
- `unpack_error()`: log warning when `get_err_type()` returns
  `None` telling the user to call `reg_err_types()`.

(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
multicast_revertable_streams
Gud Boi 2026-03-24 15:15:28 -04:00
parent 9c37b3f956
commit a41c6d5c70
1 changed files with 72 additions and 21 deletions

View File

@ -434,24 +434,36 @@ class RemoteActorError(Exception):
return self._ipc_msg.src_type_str return self._ipc_msg.src_type_str
@property @property
def src_type(self) -> str: def src_type(self) -> Type[BaseException]|None:
''' '''
Error type raised by original remote faulting actor. Error type raised by original remote faulting
actor.
When the error has only been relayed a single actor-hop When the error has only been relayed a single
this will be the same as the `.boxed_type`. actor-hop this will be the same as
`.boxed_type`.
If the type can not be resolved locally (i.e.
it was not registered via `reg_err_types()`)
a warning is logged and `None` is returned;
all string-level error info (`.src_type_str`,
`.tb_str`, etc.) remains available.
''' '''
if self._src_type is None: if self._src_type is None:
self._src_type = get_err_type( self._src_type = get_err_type(
self._ipc_msg.src_type_str self._ipc_msg.src_type_str
) )
if not self._src_type: if not self._src_type:
raise TypeError( log.warning(
f'Failed to lookup src error type with ' f'Failed to lookup src error type via\n'
f'`tractor._exceptions.get_err_type()` :\n' f'`tractor._exceptions.get_err_type()`:\n'
f'{self.src_type_str}' f'\n'
f'`{self._ipc_msg.src_type_str}`'
f' is not registered!\n'
f'\n'
f'Call `reg_err_types()` to enable'
f' full type reconstruction.\n'
) )
return self._src_type return self._src_type
@ -459,16 +471,26 @@ class RemoteActorError(Exception):
@property @property
def boxed_type_str(self) -> str: def boxed_type_str(self) -> str:
''' '''
String-name of the (last hop's) boxed error type. String-name of the (last hop's) boxed error
type.
Falls back to the IPC-msg-encoded type-name
str when the type can not be resolved locally
(e.g. unregistered custom errors).
''' '''
# TODO, maybe support also serializing the # TODO, maybe support also serializing the
# `ExceptionGroup.exeptions: list[BaseException]` set under # `ExceptionGroup.exeptions: list[BaseException]`
# certain conditions? # set under certain conditions?
bt: Type[BaseException] = self.boxed_type bt: Type[BaseException] = self.boxed_type
if bt: if bt:
return str(bt.__name__) return str(bt.__name__)
# fallback to the str name from the IPC msg
# when the type obj can't be resolved.
if self._ipc_msg:
return self._ipc_msg.boxed_type_str
return '' return ''
@property @property
@ -701,10 +723,22 @@ class RemoteActorError(Exception):
failing actor's remote env. failing actor's remote env.
''' '''
# TODO: better tb insertion and all the fancier dunder # TODO: better tb insertion and all the fancier
# metadata stuff as per `.__context__` etc. and friends: # dunder metadata stuff as per `.__context__`
# etc. and friends:
# https://github.com/python-trio/trio/issues/611 # https://github.com/python-trio/trio/issues/611
src_type_ref: Type[BaseException] = self.src_type src_type_ref: Type[BaseException]|None = (
self.src_type
)
if src_type_ref is None:
# unresolvable type: fall back to
# a `RuntimeError` preserving original
# traceback + type name.
return RuntimeError(
f'{self.src_type_str}: '
f'{self.tb_str}'
)
return src_type_ref(self.tb_str) return src_type_ref(self.tb_str)
# TODO: local recontruction of nested inception for a given # TODO: local recontruction of nested inception for a given
@ -1233,14 +1267,31 @@ def unpack_error(
if not isinstance(msg, Error): if not isinstance(msg, Error):
return None return None
# try to lookup a suitable error type from the local runtime # try to lookup a suitable error type from the
# env then use it to construct a local instance. # local runtime env then use it to construct a
# boxed_type_str: str = error_dict['boxed_type_str'] # local instance.
boxed_type_str: str = msg.boxed_type_str boxed_type_str: str = msg.boxed_type_str
boxed_type: Type[BaseException] = get_err_type(boxed_type_str) boxed_type: Type[BaseException] = get_err_type(
boxed_type_str
)
# retrieve the error's msg-encoded remotoe-env info if boxed_type is None:
message: str = f'remote task raised a {msg.boxed_type_str!r}\n' log.warning(
f'Failed to resolve remote error type\n'
f'`{boxed_type_str}` - boxing as\n'
f'`RemoteActorError` with original\n'
f'traceback preserved.\n'
f'\n'
f'Call `reg_err_types()` to enable\n'
f'full type reconstruction.\n'
)
# retrieve the error's msg-encoded remote-env
# info
message: str = (
f'remote task raised a '
f'{msg.boxed_type_str!r}\n'
)
# TODO: do we even really need these checks for RAEs? # TODO: do we even really need these checks for RAEs?
if boxed_type_str in [ if boxed_type_str in [