Improve `TransportClosed.__repr__()`, add `src_exc`
By borrowing from the implementation of `RemoteActorError.pformat()`
which is now factored into a new `.devx.pformat_exc()` and re-used for
both error types while maintaining the same func-sig. Obviously delegate
`RemoteActorError.pformat()` to the new helper accordingly and keeping
the prior `body` generation from `.devx.pformat_boxed_tb()` as before.
The new helper allows for,
- passing any of a `header|message|body: str` which are all combined in
  that order in the final output.
- getting the `exc.message` as the default `message` part.
- generating an objecty-looking "type-name" header to be rendered by
  default when `header` is not overridden.
- "first-line-of `message`" processing which we split-off and then
  re-inject as a `f'<{type(exc).__name__}( {first} )>'` top line header.
- an optional `tail: str = '>'` to "close the object"-look only added
  when `with_type_header: bool = True`.
Adjustments to `TransportClosed` around this include,
- replacing the init `cause` arg for a `src_exc` which is now always
  assigned to a same named instance var.
- displaying that new `.src_exc` in the `body: str` arg to the
  `.devx.pformat.pformat_exc()` call so you can always see the
  underlying (normally `trio`) source error.
- just make it inherit from `Exception` not `trio.BrokenResourceError`
  to avoid handlers catching `TransportClosed` as the former
  particularly in testing when we want to sometimes to distinguish them.
			
			
				pull/17/head
			
			
		
							parent
							
								
									692bd0edf6
								
							
						
					
					
						commit
						74df5034c0
					
				| 
						 | 
					@ -23,7 +23,6 @@ import builtins
 | 
				
			||||||
import importlib
 | 
					import importlib
 | 
				
			||||||
from pprint import pformat
 | 
					from pprint import pformat
 | 
				
			||||||
from pdb import bdb
 | 
					from pdb import bdb
 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
from types import (
 | 
					from types import (
 | 
				
			||||||
    TracebackType,
 | 
					    TracebackType,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -543,7 +542,6 @@ class RemoteActorError(Exception):
 | 
				
			||||||
            if val:
 | 
					            if val:
 | 
				
			||||||
                _repr += f'{key}={val_str}{end_char}'
 | 
					                _repr += f'{key}={val_str}{end_char}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        return _repr
 | 
					        return _repr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def reprol(self) -> str:
 | 
					    def reprol(self) -> str:
 | 
				
			||||||
| 
						 | 
					@ -622,56 +620,9 @@ class RemoteActorError(Exception):
 | 
				
			||||||
            the type name is already implicitly shown by python).
 | 
					            the type name is already implicitly shown by python).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        header: str = ''
 | 
					 | 
				
			||||||
        body: str = ''
 | 
					 | 
				
			||||||
        message: str = ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # XXX when the currently raised exception is this instance,
 | 
					 | 
				
			||||||
        # we do not ever use the "type header" style repr.
 | 
					 | 
				
			||||||
        is_being_raised: bool = False
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
            (exc := sys.exception())
 | 
					 | 
				
			||||||
            and
 | 
					 | 
				
			||||||
            exc is self
 | 
					 | 
				
			||||||
        ):
 | 
					 | 
				
			||||||
            is_being_raised: bool = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with_type_header: bool = (
 | 
					 | 
				
			||||||
            with_type_header
 | 
					 | 
				
			||||||
            and
 | 
					 | 
				
			||||||
            not is_being_raised
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # <RemoteActorError( .. )> style
 | 
					 | 
				
			||||||
        if with_type_header:
 | 
					 | 
				
			||||||
            header: str = f'<{type(self).__name__}('
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if message := self._message:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # split off the first line so, if needed, it isn't
 | 
					 | 
				
			||||||
            # indented the same like the "boxed content" which
 | 
					 | 
				
			||||||
            # since there is no `.tb_str` is just the `.message`.
 | 
					 | 
				
			||||||
            lines: list[str] = message.splitlines()
 | 
					 | 
				
			||||||
            first: str = lines[0]
 | 
					 | 
				
			||||||
            message: str = message.removeprefix(first)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # with a type-style header we,
 | 
					 | 
				
			||||||
            # - have no special message "first line" extraction/handling
 | 
					 | 
				
			||||||
            # - place the message a space in from the header:
 | 
					 | 
				
			||||||
            #  `MsgTypeError( <message> ..`
 | 
					 | 
				
			||||||
            #                 ^-here
 | 
					 | 
				
			||||||
            # - indent the `.message` inside the type body.
 | 
					 | 
				
			||||||
            if with_type_header:
 | 
					 | 
				
			||||||
                first = f' {first} )>'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            message: str = textwrap.indent(
 | 
					 | 
				
			||||||
                message,
 | 
					 | 
				
			||||||
                prefix=' '*2,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            message: str = first + message
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # IFF there is an embedded traceback-str we always
 | 
					        # IFF there is an embedded traceback-str we always
 | 
				
			||||||
        # draw the ascii-box around it.
 | 
					        # draw the ascii-box around it.
 | 
				
			||||||
 | 
					        body: str = ''
 | 
				
			||||||
        if tb_str := self.tb_str:
 | 
					        if tb_str := self.tb_str:
 | 
				
			||||||
            fields: str = self._mk_fields_str(
 | 
					            fields: str = self._mk_fields_str(
 | 
				
			||||||
                _body_fields
 | 
					                _body_fields
 | 
				
			||||||
| 
						 | 
					@ -692,21 +643,15 @@ class RemoteActorError(Exception):
 | 
				
			||||||
                boxer_header=self.relay_uid,
 | 
					                boxer_header=self.relay_uid,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tail = ''
 | 
					        # !TODO, it'd be nice to import these top level without
 | 
				
			||||||
        if (
 | 
					        # cycles!
 | 
				
			||||||
            with_type_header
 | 
					        from tractor.devx.pformat import (
 | 
				
			||||||
            and not message
 | 
					            pformat_exc,
 | 
				
			||||||
        ):
 | 
					        )
 | 
				
			||||||
            tail: str = '>'
 | 
					        return pformat_exc(
 | 
				
			||||||
 | 
					            exc=self,
 | 
				
			||||||
        return (
 | 
					            with_type_header=with_type_header,
 | 
				
			||||||
            header
 | 
					            body=body,
 | 
				
			||||||
            +
 | 
					 | 
				
			||||||
            message
 | 
					 | 
				
			||||||
            +
 | 
					 | 
				
			||||||
            f'{body}'
 | 
					 | 
				
			||||||
            +
 | 
					 | 
				
			||||||
            tail
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __repr__ = pformat
 | 
					    __repr__ = pformat
 | 
				
			||||||
| 
						 | 
					@ -984,7 +929,7 @@ class StreamOverrun(
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TransportClosed(trio.BrokenResourceError):
 | 
					class TransportClosed(Exception):
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    IPC transport (protocol) connection was closed or broke and
 | 
					    IPC transport (protocol) connection was closed or broke and
 | 
				
			||||||
    indicates that the wrapping communication `Channel` can no longer
 | 
					    indicates that the wrapping communication `Channel` can no longer
 | 
				
			||||||
| 
						 | 
					@ -995,16 +940,21 @@ class TransportClosed(trio.BrokenResourceError):
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        message: str,
 | 
					        message: str,
 | 
				
			||||||
        loglevel: str = 'transport',
 | 
					        loglevel: str = 'transport',
 | 
				
			||||||
        cause: BaseException|None = None,
 | 
					        src_exc: Exception|None = None,
 | 
				
			||||||
        raise_on_report: bool = False,
 | 
					        raise_on_report: bool = False,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        self.message: str = message
 | 
					        self.message: str = message
 | 
				
			||||||
        self._loglevel = loglevel
 | 
					        self._loglevel: str = loglevel
 | 
				
			||||||
        super().__init__(message)
 | 
					        super().__init__(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if cause is not None:
 | 
					        self.src_exc = src_exc
 | 
				
			||||||
            self.__cause__ = cause
 | 
					        if (
 | 
				
			||||||
 | 
					            src_exc is not None
 | 
				
			||||||
 | 
					            and
 | 
				
			||||||
 | 
					            not self.__cause__
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            self.__cause__ = src_exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # flag to toggle whether the msg loop should raise
 | 
					        # flag to toggle whether the msg loop should raise
 | 
				
			||||||
        # the exc in its `TransportClosed` handler block.
 | 
					        # the exc in its `TransportClosed` handler block.
 | 
				
			||||||
| 
						 | 
					@ -1041,6 +991,26 @@ class TransportClosed(trio.BrokenResourceError):
 | 
				
			||||||
        if self._raise_on_report:
 | 
					        if self._raise_on_report:
 | 
				
			||||||
            raise self from cause
 | 
					            raise self from cause
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def pformat(self) -> str:
 | 
				
			||||||
 | 
					        from tractor.devx.pformat import (
 | 
				
			||||||
 | 
					            pformat_exc,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        src_err: Exception|None = self.src_exc or '<unknown>'
 | 
				
			||||||
 | 
					        src_msg: tuple[str] = src_err.args
 | 
				
			||||||
 | 
					        src_exc_repr: str = (
 | 
				
			||||||
 | 
					            f'{type(src_err).__name__}[ {src_msg} ]'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return pformat_exc(
 | 
				
			||||||
 | 
					            exc=self,
 | 
				
			||||||
 | 
					            # message=self.message,  # implicit!
 | 
				
			||||||
 | 
					            body=(
 | 
				
			||||||
 | 
					                f' |_src_exc: {src_exc_repr}\n'
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # delegate to `str`-ified pformat
 | 
				
			||||||
 | 
					    __repr__ = pformat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NoResult(RuntimeError):
 | 
					class NoResult(RuntimeError):
 | 
				
			||||||
    "No final result is expected for this actor"
 | 
					    "No final result is expected for this actor"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,7 @@ Pretty formatters for use throughout the code base.
 | 
				
			||||||
Mostly handy for logging and exception message content.
 | 
					Mostly handy for logging and exception message content.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
'''
 | 
					'''
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
import textwrap
 | 
					import textwrap
 | 
				
			||||||
import traceback
 | 
					import traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -115,6 +116,85 @@ def pformat_boxed_tb(
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def pformat_exc(
 | 
				
			||||||
 | 
					    exc: Exception,
 | 
				
			||||||
 | 
					    header: str = '',
 | 
				
			||||||
 | 
					    message: str = '',
 | 
				
			||||||
 | 
					    body: str = '',
 | 
				
			||||||
 | 
					    with_type_header: bool = True,
 | 
				
			||||||
 | 
					) -> str:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # XXX when the currently raised exception is this instance,
 | 
				
			||||||
 | 
					    # we do not ever use the "type header" style repr.
 | 
				
			||||||
 | 
					    is_being_raised: bool = False
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        (curr_exc := sys.exception())
 | 
				
			||||||
 | 
					        and
 | 
				
			||||||
 | 
					        curr_exc is exc
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        is_being_raised: bool = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with_type_header: bool = (
 | 
				
			||||||
 | 
					        with_type_header
 | 
				
			||||||
 | 
					        and
 | 
				
			||||||
 | 
					        not is_being_raised
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # <RemoteActorError( .. )> style
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        with_type_header
 | 
				
			||||||
 | 
					        and
 | 
				
			||||||
 | 
					        not header
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        header: str = f'<{type(exc).__name__}('
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message: str = (
 | 
				
			||||||
 | 
					        message
 | 
				
			||||||
 | 
					        or
 | 
				
			||||||
 | 
					        exc.message
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    if message:
 | 
				
			||||||
 | 
					        # split off the first line so, if needed, it isn't
 | 
				
			||||||
 | 
					        # indented the same like the "boxed content" which
 | 
				
			||||||
 | 
					        # since there is no `.tb_str` is just the `.message`.
 | 
				
			||||||
 | 
					        lines: list[str] = message.splitlines()
 | 
				
			||||||
 | 
					        first: str = lines[0]
 | 
				
			||||||
 | 
					        message: str = message.removeprefix(first)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # with a type-style header we,
 | 
				
			||||||
 | 
					        # - have no special message "first line" extraction/handling
 | 
				
			||||||
 | 
					        # - place the message a space in from the header:
 | 
				
			||||||
 | 
					        #  `MsgTypeError( <message> ..`
 | 
				
			||||||
 | 
					        #                 ^-here
 | 
				
			||||||
 | 
					        # - indent the `.message` inside the type body.
 | 
				
			||||||
 | 
					        if with_type_header:
 | 
				
			||||||
 | 
					            first = f' {first} )>'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        message: str = textwrap.indent(
 | 
				
			||||||
 | 
					            message,
 | 
				
			||||||
 | 
					            prefix=' '*2,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        message: str = first + message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tail: str = ''
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        with_type_header
 | 
				
			||||||
 | 
					        and
 | 
				
			||||||
 | 
					        not message
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        tail: str = '>'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        header
 | 
				
			||||||
 | 
					        +
 | 
				
			||||||
 | 
					        message
 | 
				
			||||||
 | 
					        +
 | 
				
			||||||
 | 
					        f'{body}'
 | 
				
			||||||
 | 
					        +
 | 
				
			||||||
 | 
					        tail
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def pformat_caller_frame(
 | 
					def pformat_caller_frame(
 | 
				
			||||||
    stack_limit: int = 1,
 | 
					    stack_limit: int = 1,
 | 
				
			||||||
    box_tb: bool = True,
 | 
					    box_tb: bool = True,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -208,6 +208,7 @@ class MsgpackTransport(MsgTransport):
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        decodes_failed: int = 0
 | 
					        decodes_failed: int = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tpt_name: str = f'{type(self).__name__!r}'
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                header: bytes = await self.recv_stream.receive_exactly(4)
 | 
					                header: bytes = await self.recv_stream.receive_exactly(4)
 | 
				
			||||||
| 
						 | 
					@ -252,10 +253,9 @@ class MsgpackTransport(MsgTransport):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                raise TransportClosed(
 | 
					                raise TransportClosed(
 | 
				
			||||||
                    message=(
 | 
					                    message=(
 | 
				
			||||||
                        f'IPC transport already closed by peer\n'
 | 
					                        f'{tpt_name} already closed by peer\n'
 | 
				
			||||||
                        f'x)> {type(trans_err)}\n'
 | 
					 | 
				
			||||||
                        f' |_{self}\n'
 | 
					 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
 | 
					                    src_exc=trans_err,
 | 
				
			||||||
                    loglevel=loglevel,
 | 
					                    loglevel=loglevel,
 | 
				
			||||||
                ) from trans_err
 | 
					                ) from trans_err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -267,18 +267,17 @@ class MsgpackTransport(MsgTransport):
 | 
				
			||||||
            #
 | 
					            #
 | 
				
			||||||
            # NOTE: as such we always re-raise this error from the
 | 
					            # NOTE: as such we always re-raise this error from the
 | 
				
			||||||
            #       RPC msg loop!
 | 
					            #       RPC msg loop!
 | 
				
			||||||
            except trio.ClosedResourceError as closure_err:
 | 
					            except trio.ClosedResourceError as cre:
 | 
				
			||||||
 | 
					                closure_err = cre
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                raise TransportClosed(
 | 
					                raise TransportClosed(
 | 
				
			||||||
                    message=(
 | 
					                    message=(
 | 
				
			||||||
                        f'IPC transport already manually closed locally?\n'
 | 
					                        f'{tpt_name} was already closed locally ?\n'
 | 
				
			||||||
                        f'x)> {type(closure_err)} \n'
 | 
					 | 
				
			||||||
                        f' |_{self}\n'
 | 
					 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
 | 
					                    src_exc=closure_err,
 | 
				
			||||||
                    loglevel='error',
 | 
					                    loglevel='error',
 | 
				
			||||||
                    raise_on_report=(
 | 
					                    raise_on_report=(
 | 
				
			||||||
                        closure_err.args[0] == 'another task closed this fd'
 | 
					                        'another task closed this fd' in closure_err.args
 | 
				
			||||||
                        or
 | 
					 | 
				
			||||||
                        closure_err.args[0] in ['another task closed this fd']
 | 
					 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                ) from closure_err
 | 
					                ) from closure_err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -286,12 +285,9 @@ class MsgpackTransport(MsgTransport):
 | 
				
			||||||
            if header == b'':
 | 
					            if header == b'':
 | 
				
			||||||
                raise TransportClosed(
 | 
					                raise TransportClosed(
 | 
				
			||||||
                    message=(
 | 
					                    message=(
 | 
				
			||||||
                        f'IPC transport already gracefully closed\n'
 | 
					                        f'{tpt_name} already gracefully closed\n'
 | 
				
			||||||
                        f')>\n'
 | 
					 | 
				
			||||||
                        f'|_{self}\n'
 | 
					 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    loglevel='transport',
 | 
					                    loglevel='transport',
 | 
				
			||||||
                    # cause=???  # handy or no?
 | 
					 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            size: int
 | 
					            size: int
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue