Add "fancier" remote-error `.__repr__()`-ing

Our remote error box types `RemoteActorError`, `ContextCancelled` and
`StreamOverrun` needed a console display makeover particularly for
logging content and `repr()` in higher level primitives like `Context`.

This adds a more "dramatic" str-representation to showcase the
underlying boxed traceback content more sensationally (via ascii-art
emphasis) as well as support a more terse `.reprol()` (representation
for one-line) format that can be used for types that track remote
errors/cancels like with `Context._remote_error`.

Impl deats:
- change `RemoteActorError.__repr__()` formatting to show (sub-type
  specific) `.msgdata` fields in a multi-line format (similar to our new
  `.msg.types.Struct` style) followed by some ascii accented delimiter
  lines to emphasize any `.msgdata["tb_str"]` packed by the remote
- for rme and subtypes allow picking the specifically relevant fields
  via a type defined `.reprol_fields: list[str]` and pick for each
  subtype:
   |_ `RemoteActorError.src_actor_uid`
   |_ `ContextCancelled.canceller`
   |_ `StreamOverrun.sender`

- add `.reprol()` to show a `repr()`-on-one-line formatted string that
  can be used by other multi-line-field-`repr()` styled composite types
  as needed in (high level) logging info.
- toss in some mod level `_body_fields: list[str]` for summary of such
  fields (if needed).
- add some new rae (remote-actor-error) props:
  - `.type` around a newly named `.boxed_type`
  - `.type_str: str`
  - `.tb_str: str`
modden_spawn_from_client_req
Tyler Goodlet 2024-02-29 18:56:31 -05:00
parent 23aa97692e
commit 9bc6a61c93
1 changed files with 116 additions and 18 deletions

View File

@ -27,6 +27,7 @@ from typing import (
Type, Type,
TYPE_CHECKING, TYPE_CHECKING,
) )
import textwrap
import traceback import traceback
import exceptiongroup as eg import exceptiongroup as eg
@ -37,8 +38,9 @@ from .log import get_logger
if TYPE_CHECKING: if TYPE_CHECKING:
from ._context import Context from ._context import Context
from ._stream import MsgStream
from .log import StackLevelAdapter from .log import StackLevelAdapter
from ._stream import MsgStream
from ._ipc import Channel
log = get_logger('tractor') log = get_logger('tractor')
@ -49,6 +51,25 @@ class ActorFailure(Exception):
"General actor failure" "General actor failure"
class InternalError(RuntimeError):
'''
Entirely unexpected internal machinery error indicating
a completely invalid state or interface.
'''
_body_fields: list[str] = [
'src_actor_uid',
'canceller',
'sender',
]
_msgdata_keys: list[str] = [
'type_str',
] + _body_fields
# TODO: rename to just `RemoteError`? # TODO: rename to just `RemoteError`?
class RemoteActorError(Exception): class RemoteActorError(Exception):
''' '''
@ -60,6 +81,10 @@ class RemoteActorError(Exception):
a special "error" IPC msg sent by some remote actor-runtime. a special "error" IPC msg sent by some remote actor-runtime.
''' '''
reprol_fields: list[str] = [
'src_actor_uid',
]
def __init__( def __init__(
self, self,
message: str, message: str,
@ -77,23 +102,82 @@ class RemoteActorError(Exception):
# - .remote_type # - .remote_type
# also pertains to our long long oustanding issue XD # also pertains to our long long oustanding issue XD
# https://github.com/goodboy/tractor/issues/5 # https://github.com/goodboy/tractor/issues/5
self.type: str = suberror_type self.boxed_type: str = suberror_type
self.msgdata: dict[str, Any] = msgdata self.msgdata: dict[str, Any] = msgdata
@property
def type(self) -> str:
return self.boxed_type
@property
def type_str(self) -> str:
return str(type(self.boxed_type).__name__)
@property @property
def src_actor_uid(self) -> tuple[str, str]|None: def src_actor_uid(self) -> tuple[str, str]|None:
return self.msgdata.get('src_actor_uid') return self.msgdata.get('src_actor_uid')
def __repr__(self) -> str: @property
def tb_str(
self,
indent: str = ' '*3,
) -> str:
if remote_tb := self.msgdata.get('tb_str'): if remote_tb := self.msgdata.get('tb_str'):
pformat(remote_tb) return textwrap.indent(
return ( remote_tb,
f'{type(self).__name__}(\n' prefix=indent,
f'msgdata={pformat(self.msgdata)}\n'
')'
) )
return super().__repr__() return ''
def reprol(self) -> str:
'''
Represent this error for "one line" display, like in
a field of our `Context.__repr__()` output.
'''
_repr: str = f'{type(self).__name__}('
for key in self.reprol_fields:
val: Any|None = self.msgdata.get(key)
if val:
_repr += f'{key}={repr(val)} '
return _repr
def __repr__(self) -> str:
fields: str = ''
for key in _body_fields:
val: str|None = self.msgdata.get(key)
if val:
fields += f'{key}={val}\n'
fields: str = textwrap.indent(
fields,
# prefix=' '*2,
prefix=' |_',
)
indent: str = ''*1
body: str = (
f'{fields}'
f' |\n'
f' ------ - ------\n\n'
f'{self.tb_str}\n'
f' ------ - ------\n'
f' _|\n'
)
# f'|\n'
# f' |\n'
if indent:
body: str = textwrap.indent(
body,
prefix=indent,
)
return (
f'<{type(self).__name__}(\n'
f'{body}'
')>'
)
# TODO: local recontruction of remote exception deats # TODO: local recontruction of remote exception deats
# def unbox(self) -> BaseException: # def unbox(self) -> BaseException:
@ -102,8 +186,9 @@ class RemoteActorError(Exception):
class InternalActorError(RemoteActorError): class InternalActorError(RemoteActorError):
''' '''
Remote internal ``tractor`` error indicating (Remote) internal `tractor` error indicating failure of some
failure of some primitive or machinery. primitive, machinery state or lowlevel task that should never
occur.
''' '''
@ -114,6 +199,9 @@ class ContextCancelled(RemoteActorError):
``Portal.cancel_actor()`` or ``Context.cancel()``. ``Portal.cancel_actor()`` or ``Context.cancel()``.
''' '''
reprol_fields: list[str] = [
'canceller',
]
@property @property
def canceller(self) -> tuple[str, str]|None: def canceller(self) -> tuple[str, str]|None:
''' '''
@ -145,6 +233,9 @@ class ContextCancelled(RemoteActorError):
f'{self}' f'{self}'
) )
# to make `.__repr__()` work uniformly
# src_actor_uid = canceller
class TransportClosed(trio.ClosedResourceError): class TransportClosed(trio.ClosedResourceError):
"Underlying channel transport was closed prior to use" "Underlying channel transport was closed prior to use"
@ -166,6 +257,9 @@ class StreamOverrun(
RemoteActorError, RemoteActorError,
trio.TooSlowError, trio.TooSlowError,
): ):
reprol_fields: list[str] = [
'sender',
]
''' '''
This stream was overrun by sender This stream was overrun by sender
@ -213,6 +307,7 @@ def pack_error(
] = { ] = {
'tb_str': tb_str, 'tb_str': tb_str,
'type_str': type(exc).__name__, 'type_str': type(exc).__name__,
'boxed_type': type(exc).__name__,
'src_actor_uid': current_actor().uid, 'src_actor_uid': current_actor().uid,
} }
@ -238,8 +333,8 @@ def unpack_error(
msg: dict[str, Any], msg: dict[str, Any],
chan=None, chan: Channel|None = None,
err_type=RemoteActorError, box_type: RemoteActorError = RemoteActorError,
hide_tb: bool = True, hide_tb: bool = True,
@ -264,12 +359,15 @@ def unpack_error(
# retrieve the remote error's msg encoded details # retrieve the remote error's msg encoded details
tb_str: str = error_dict.get('tb_str', '') tb_str: str = error_dict.get('tb_str', '')
message: str = f'{chan.uid}\n' + tb_str message: str = f'{chan.uid}\n' + tb_str
type_name: str = error_dict['type_str'] type_name: str = (
error_dict.get('type_str')
or error_dict['boxed_type']
)
suberror_type: Type[BaseException] = Exception suberror_type: Type[BaseException] = Exception
if type_name == 'ContextCancelled': if type_name == 'ContextCancelled':
err_type = ContextCancelled box_type = ContextCancelled
suberror_type = err_type suberror_type = box_type
else: # try to lookup a suitable local error type else: # try to lookup a suitable local error type
for ns in [ for ns in [
@ -285,7 +383,7 @@ def unpack_error(
): ):
break break
exc = err_type( exc = box_type(
message, message,
suberror_type=suberror_type, suberror_type=suberror_type,