Attempt at better internal traceback hiding
Previously i was trying to approach this using lots of `__tracebackhide__`'s in various internal funcs but since it's not exactly straight forward to do this inside core deps like `trio` and the stdlib, it makes a bit more sense to optionally catch and re-raise certain classes of errors from their originals using `raise from` syntax as per: https://docs.python.org/3/library/exceptions.html#exception-context Deats: - litter `._context` methods with `__tracebackhide__`/`hide_tb` which were previously being shown but that don't need to be to application code now that cancel semantics testing is finished up. - i originally did the same but later commented it all out in `._ipc` since error catch and re-raise instead in higher level layers (above the transport) seems to be a much saner approach. - add catch-n-reraise-from in `MsgStream.send()`/.`receive()` to avoid seeing the depths of `trio` and/or our `._ipc` layers on comms errors. Further this patch adds some refactoring to use the same remote-error shipper routine from both the actor-core in the RPC invoker: - rename it as `try_ship_error_to_remote()` and call it from `._invoke()` as well as it's prior usage. - make it optionally accept `cid: str` a `remote_descr: str` and of course a `hide_tb: bool`. Other misc tweaks: - add some todo notes around `Actor.load_modules()` debug hooking. - tweak the zombie reaper log msg and timeout value ;)ctx_cancel_semantics_and_overruns
							parent
							
								
									389b305d3b
								
							
						
					
					
						commit
						544cb40533
					
				| 
						 | 
					@ -1198,8 +1198,12 @@ class Context:
 | 
				
			||||||
    # TODO: replace all the instances of this!! XD
 | 
					    # TODO: replace all the instances of this!! XD
 | 
				
			||||||
    def maybe_raise(
 | 
					    def maybe_raise(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hide_tb: bool = True,
 | 
				
			||||||
        **kwargs,
 | 
					        **kwargs,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> Exception|None:
 | 
					    ) -> Exception|None:
 | 
				
			||||||
 | 
					        __tracebackhide__: bool = hide_tb
 | 
				
			||||||
        if re := self._remote_error:
 | 
					        if re := self._remote_error:
 | 
				
			||||||
            return self._maybe_raise_remote_err(
 | 
					            return self._maybe_raise_remote_err(
 | 
				
			||||||
                re,
 | 
					                re,
 | 
				
			||||||
| 
						 | 
					@ -1209,8 +1213,10 @@ class Context:
 | 
				
			||||||
    def _maybe_raise_remote_err(
 | 
					    def _maybe_raise_remote_err(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        remote_error: Exception,
 | 
					        remote_error: Exception,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise_ctxc_from_self_call: bool = False,
 | 
					        raise_ctxc_from_self_call: bool = False,
 | 
				
			||||||
        raise_overrun_from_self: bool = True,
 | 
					        raise_overrun_from_self: bool = True,
 | 
				
			||||||
 | 
					        hide_tb: bool = True,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> (
 | 
					    ) -> (
 | 
				
			||||||
        ContextCancelled  # `.cancel()` request to far side
 | 
					        ContextCancelled  # `.cancel()` request to far side
 | 
				
			||||||
| 
						 | 
					@ -1222,6 +1228,7 @@ class Context:
 | 
				
			||||||
        a  cancellation (if any).
 | 
					        a  cancellation (if any).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
 | 
					        __tracebackhide__: bool = hide_tb
 | 
				
			||||||
        our_uid: tuple = self.chan.uid
 | 
					        our_uid: tuple = self.chan.uid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # XXX NOTE XXX: `ContextCancelled`/`StreamOverrun` absorption
 | 
					        # XXX NOTE XXX: `ContextCancelled`/`StreamOverrun` absorption
 | 
				
			||||||
| 
						 | 
					@ -1305,7 +1312,7 @@ class Context:
 | 
				
			||||||
    # TODO: change  to `.wait_for_result()`?
 | 
					    # TODO: change  to `.wait_for_result()`?
 | 
				
			||||||
    async def result(
 | 
					    async def result(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        hide_tb: bool = False,
 | 
					        hide_tb: bool = True,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> Any|Exception:
 | 
					    ) -> Any|Exception:
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,13 +19,14 @@ Inter-process comms abstractions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from __future__ import annotations
 | 
					from __future__ import annotations
 | 
				
			||||||
import struct
 | 
					 | 
				
			||||||
import platform
 | 
					 | 
				
			||||||
from pprint import pformat
 | 
					 | 
				
			||||||
from collections.abc import (
 | 
					from collections.abc import (
 | 
				
			||||||
    AsyncGenerator,
 | 
					    AsyncGenerator,
 | 
				
			||||||
    AsyncIterator,
 | 
					    AsyncIterator,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from contextlib import asynccontextmanager as acm
 | 
				
			||||||
 | 
					import platform
 | 
				
			||||||
 | 
					from pprint import pformat
 | 
				
			||||||
 | 
					import struct
 | 
				
			||||||
import typing
 | 
					import typing
 | 
				
			||||||
from typing import (
 | 
					from typing import (
 | 
				
			||||||
    Any,
 | 
					    Any,
 | 
				
			||||||
| 
						 | 
					@ -35,18 +36,16 @@ from typing import (
 | 
				
			||||||
    TypeVar,
 | 
					    TypeVar,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from tricycle import BufferedReceiveStream
 | 
					 | 
				
			||||||
import msgspec
 | 
					import msgspec
 | 
				
			||||||
 | 
					from tricycle import BufferedReceiveStream
 | 
				
			||||||
import trio
 | 
					import trio
 | 
				
			||||||
from async_generator import asynccontextmanager
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .log import get_logger
 | 
					from tractor.log import get_logger
 | 
				
			||||||
from ._exceptions import TransportClosed
 | 
					from tractor._exceptions import TransportClosed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log = get_logger(__name__)
 | 
					log = get_logger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
_is_windows = platform.system() == 'Windows'
 | 
					_is_windows = platform.system() == 'Windows'
 | 
				
			||||||
log = get_logger(__name__)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_stream_addrs(stream: trio.SocketStream) -> tuple:
 | 
					def get_stream_addrs(stream: trio.SocketStream) -> tuple:
 | 
				
			||||||
| 
						 | 
					@ -206,7 +205,17 @@ class MsgpackTCPStream(MsgTransport):
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    raise
 | 
					                    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def send(self, msg: Any) -> None:
 | 
					    async def send(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        msg: Any,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # hide_tb: bool = False,
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Send a msgpack coded blob-as-msg over TCP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        # __tracebackhide__: bool = hide_tb
 | 
				
			||||||
        async with self._send_lock:
 | 
					        async with self._send_lock:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            bytes_data: bytes = self.encode(msg)
 | 
					            bytes_data: bytes = self.encode(msg)
 | 
				
			||||||
| 
						 | 
					@ -388,15 +397,28 @@ class Channel:
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return transport
 | 
					        return transport
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def send(self, item: Any) -> None:
 | 
					    async def send(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        payload: Any,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # hide_tb: bool = False,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Send a coded msg-blob over the transport.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        # __tracebackhide__: bool = hide_tb
 | 
				
			||||||
        log.transport(
 | 
					        log.transport(
 | 
				
			||||||
            '=> send IPC msg:\n\n'
 | 
					            '=> send IPC msg:\n\n'
 | 
				
			||||||
            f'{pformat(item)}\n'
 | 
					            f'{pformat(payload)}\n'
 | 
				
			||||||
        )  # type: ignore
 | 
					        )  # type: ignore
 | 
				
			||||||
        assert self._transport
 | 
					        assert self._transport
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await self._transport.send(item)
 | 
					        await self._transport.send(
 | 
				
			||||||
 | 
					            payload,
 | 
				
			||||||
 | 
					            # hide_tb=hide_tb,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def recv(self) -> Any:
 | 
					    async def recv(self) -> Any:
 | 
				
			||||||
        assert self._transport
 | 
					        assert self._transport
 | 
				
			||||||
| 
						 | 
					@ -493,7 +515,7 @@ class Channel:
 | 
				
			||||||
        return self._transport.connected() if self._transport else False
 | 
					        return self._transport.connected() if self._transport else False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@asynccontextmanager
 | 
					@acm
 | 
				
			||||||
async def _connect_chan(
 | 
					async def _connect_chan(
 | 
				
			||||||
    host: str, port: int
 | 
					    host: str, port: int
 | 
				
			||||||
) -> typing.AsyncGenerator[Channel, None]:
 | 
					) -> typing.AsyncGenerator[Channel, None]:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -465,7 +465,7 @@ class Portal:
 | 
				
			||||||
        # TODO: if we set this the wrapping `@acm` body will
 | 
					        # TODO: if we set this the wrapping `@acm` body will
 | 
				
			||||||
        # still be shown (awkwardly) on pdb REPL entry. Ideally
 | 
					        # still be shown (awkwardly) on pdb REPL entry. Ideally
 | 
				
			||||||
        # we can similarly annotate that frame to NOT show?
 | 
					        # we can similarly annotate that frame to NOT show?
 | 
				
			||||||
        hide_tb: bool = False,
 | 
					        hide_tb: bool = True,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # proxied to RPC
 | 
					        # proxied to RPC
 | 
				
			||||||
        **kwargs,
 | 
					        **kwargs,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,423 +1,4 @@
 | 
				
			||||||
# tractor: structured concurrent "actors".
 | 
					tb = None
 | 
				
			||||||
# Copyright 2018-eternity Tyler Goodlet.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
# it under the terms of the GNU Affero General Public License as published by
 | 
					 | 
				
			||||||
# the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
# (at your option) any later version.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
# GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
The fundamental core machinery implementing every "actor" including
 | 
					 | 
				
			||||||
the process-local (python-interpreter global) `Actor` state-type
 | 
					 | 
				
			||||||
primitive(s), RPC-in-task scheduling, and IPC connectivity and
 | 
					 | 
				
			||||||
low-level transport msg handling.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
from __future__ import annotations
 | 
					 | 
				
			||||||
from contextlib import (
 | 
					 | 
				
			||||||
    ExitStack,
 | 
					 | 
				
			||||||
    asynccontextmanager as acm,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from collections import defaultdict
 | 
					 | 
				
			||||||
from functools import partial
 | 
					 | 
				
			||||||
from itertools import chain
 | 
					 | 
				
			||||||
import importlib
 | 
					 | 
				
			||||||
import importlib.util
 | 
					 | 
				
			||||||
import inspect
 | 
					 | 
				
			||||||
from pprint import pformat
 | 
					 | 
				
			||||||
import signal
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
from typing import (
 | 
					 | 
				
			||||||
    Any,
 | 
					 | 
				
			||||||
    Callable,
 | 
					 | 
				
			||||||
    Union,
 | 
					 | 
				
			||||||
    Coroutine,
 | 
					 | 
				
			||||||
    TYPE_CHECKING,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
import uuid
 | 
					 | 
				
			||||||
from types import ModuleType
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import warnings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from async_generator import aclosing
 | 
					 | 
				
			||||||
from exceptiongroup import BaseExceptionGroup
 | 
					 | 
				
			||||||
import trio
 | 
					 | 
				
			||||||
from trio import (
 | 
					 | 
				
			||||||
    CancelScope,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from trio_typing import (
 | 
					 | 
				
			||||||
    Nursery,
 | 
					 | 
				
			||||||
    TaskStatus,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .msg import NamespacePath
 | 
					 | 
				
			||||||
from ._ipc import Channel
 | 
					 | 
				
			||||||
from ._context import (
 | 
					 | 
				
			||||||
    mk_context,
 | 
					 | 
				
			||||||
    Context,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from .log import get_logger
 | 
					 | 
				
			||||||
from ._exceptions import (
 | 
					 | 
				
			||||||
    pack_error,
 | 
					 | 
				
			||||||
    unpack_error,
 | 
					 | 
				
			||||||
    ModuleNotExposed,
 | 
					 | 
				
			||||||
    is_multi_cancelled,
 | 
					 | 
				
			||||||
    ContextCancelled,
 | 
					 | 
				
			||||||
    TransportClosed,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from . import _debug
 | 
					 | 
				
			||||||
from ._discovery import get_arbiter
 | 
					 | 
				
			||||||
from ._portal import Portal
 | 
					 | 
				
			||||||
from . import _state
 | 
					 | 
				
			||||||
from . import _mp_fixup_main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if TYPE_CHECKING:
 | 
					 | 
				
			||||||
    from ._supervise import ActorNursery
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
log = get_logger('tractor')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_gb_mod: ModuleType|None|False = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def maybe_import_gb():
 | 
					 | 
				
			||||||
    global _gb_mod
 | 
					 | 
				
			||||||
    if _gb_mod is False:
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        import greenback
 | 
					 | 
				
			||||||
        _gb_mod = greenback
 | 
					 | 
				
			||||||
        await greenback.ensure_portal()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    except ModuleNotFoundError:
 | 
					 | 
				
			||||||
        log.debug(
 | 
					 | 
				
			||||||
            '`greenback` is not installed.\n'
 | 
					 | 
				
			||||||
            'No sync debug support!\n'
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        _gb_mod = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def _invoke_non_context(
 | 
					 | 
				
			||||||
    actor: Actor,
 | 
					 | 
				
			||||||
    cancel_scope: CancelScope,
 | 
					 | 
				
			||||||
    ctx: Context,
 | 
					 | 
				
			||||||
    cid: str,
 | 
					 | 
				
			||||||
    chan: Channel,
 | 
					 | 
				
			||||||
    func: Callable,
 | 
					 | 
				
			||||||
    coro: Coroutine,
 | 
					 | 
				
			||||||
    kwargs: dict[str, Any],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    treat_as_gen: bool,
 | 
					 | 
				
			||||||
    is_rpc: bool,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    task_status: TaskStatus[
 | 
					 | 
				
			||||||
        Context | BaseException
 | 
					 | 
				
			||||||
    ] = trio.TASK_STATUS_IGNORED,
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # TODO: can we unify this with the `context=True` impl below?
 | 
					 | 
				
			||||||
    if inspect.isasyncgen(coro):
 | 
					 | 
				
			||||||
        await chan.send({'functype': 'asyncgen', 'cid': cid})
 | 
					 | 
				
			||||||
        # XXX: massive gotcha! If the containing scope
 | 
					 | 
				
			||||||
        # is cancelled and we execute the below line,
 | 
					 | 
				
			||||||
        # any ``ActorNursery.__aexit__()`` WON'T be
 | 
					 | 
				
			||||||
        # triggered in the underlying async gen! So we
 | 
					 | 
				
			||||||
        # have to properly handle the closing (aclosing)
 | 
					 | 
				
			||||||
        # of the async gen in order to be sure the cancel
 | 
					 | 
				
			||||||
        # is propagated!
 | 
					 | 
				
			||||||
        with cancel_scope as cs:
 | 
					 | 
				
			||||||
            ctx._scope = cs
 | 
					 | 
				
			||||||
            task_status.started(ctx)
 | 
					 | 
				
			||||||
            async with aclosing(coro) as agen:
 | 
					 | 
				
			||||||
                async for item in agen:
 | 
					 | 
				
			||||||
                    # TODO: can we send values back in here?
 | 
					 | 
				
			||||||
                    # it's gonna require a `while True:` and
 | 
					 | 
				
			||||||
                    # some non-blocking way to retrieve new `asend()`
 | 
					 | 
				
			||||||
                    # values from the channel:
 | 
					 | 
				
			||||||
                    # to_send = await chan.recv_nowait()
 | 
					 | 
				
			||||||
                    # if to_send is not None:
 | 
					 | 
				
			||||||
                    #     to_yield = await coro.asend(to_send)
 | 
					 | 
				
			||||||
                    await chan.send({'yield': item, 'cid': cid})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        log.runtime(f"Finished iterating {coro}")
 | 
					 | 
				
			||||||
        # TODO: we should really support a proper
 | 
					 | 
				
			||||||
        # `StopAsyncIteration` system here for returning a final
 | 
					 | 
				
			||||||
        # value if desired
 | 
					 | 
				
			||||||
        await chan.send({'stop': True, 'cid': cid})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # one way @stream func that gets treated like an async gen
 | 
					 | 
				
			||||||
    # TODO: can we unify this with the `context=True` impl below?
 | 
					 | 
				
			||||||
    elif treat_as_gen:
 | 
					 | 
				
			||||||
        await chan.send({'functype': 'asyncgen', 'cid': cid})
 | 
					 | 
				
			||||||
        # XXX: the async-func may spawn further tasks which push
 | 
					 | 
				
			||||||
        # back values like an async-generator would but must
 | 
					 | 
				
			||||||
        # manualy construct the response dict-packet-responses as
 | 
					 | 
				
			||||||
        # above
 | 
					 | 
				
			||||||
        with cancel_scope as cs:
 | 
					 | 
				
			||||||
            ctx._scope = cs
 | 
					 | 
				
			||||||
            task_status.started(ctx)
 | 
					 | 
				
			||||||
            await coro
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not cs.cancelled_caught:
 | 
					 | 
				
			||||||
            # task was not cancelled so we can instruct the
 | 
					 | 
				
			||||||
            # far end async gen to tear down
 | 
					 | 
				
			||||||
            await chan.send({'stop': True, 'cid': cid})
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        # regular async function/method
 | 
					 | 
				
			||||||
        # XXX: possibly just a scheduled `Actor._cancel_task()`
 | 
					 | 
				
			||||||
        # from a remote request to cancel some `Context`.
 | 
					 | 
				
			||||||
        # ------ - ------
 | 
					 | 
				
			||||||
        # TODO: ideally we unify this with the above `context=True`
 | 
					 | 
				
			||||||
        # block such that for any remote invocation ftype, we
 | 
					 | 
				
			||||||
        # always invoke the far end RPC task scheduling the same
 | 
					 | 
				
			||||||
        # way: using the linked IPC context machinery.
 | 
					 | 
				
			||||||
        failed_resp: bool = False
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            await chan.send({
 | 
					 | 
				
			||||||
                'functype': 'asyncfunc',
 | 
					 | 
				
			||||||
                'cid': cid
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        except (
 | 
					 | 
				
			||||||
            trio.ClosedResourceError,
 | 
					 | 
				
			||||||
            trio.BrokenResourceError,
 | 
					 | 
				
			||||||
            BrokenPipeError,
 | 
					 | 
				
			||||||
        ) as ipc_err:
 | 
					 | 
				
			||||||
            failed_resp = True
 | 
					 | 
				
			||||||
            if is_rpc:
 | 
					 | 
				
			||||||
                raise
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                # TODO: should this be an `.exception()` call?
 | 
					 | 
				
			||||||
                log.warning(
 | 
					 | 
				
			||||||
                    f'Failed to respond to non-rpc request: {func}\n'
 | 
					 | 
				
			||||||
                    f'{ipc_err}'
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with cancel_scope as cs:
 | 
					 | 
				
			||||||
            ctx._scope: CancelScope = cs
 | 
					 | 
				
			||||||
            task_status.started(ctx)
 | 
					 | 
				
			||||||
            result = await coro
 | 
					 | 
				
			||||||
            fname: str = func.__name__
 | 
					 | 
				
			||||||
            log.runtime(
 | 
					 | 
				
			||||||
                'RPC complete:\n'
 | 
					 | 
				
			||||||
                f'task: {ctx._task}\n'
 | 
					 | 
				
			||||||
                f'|_cid={ctx.cid}\n'
 | 
					 | 
				
			||||||
                f'|_{fname}() -> {pformat(result)}\n'
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # NOTE: only send result if we know IPC isn't down
 | 
					 | 
				
			||||||
            if (
 | 
					 | 
				
			||||||
                not failed_resp
 | 
					 | 
				
			||||||
                and chan.connected()
 | 
					 | 
				
			||||||
            ):
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    await chan.send(
 | 
					 | 
				
			||||||
                        {'return': result,
 | 
					 | 
				
			||||||
                         'cid': cid}
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                except (
 | 
					 | 
				
			||||||
                    BrokenPipeError,
 | 
					 | 
				
			||||||
                    trio.BrokenResourceError,
 | 
					 | 
				
			||||||
                ):
 | 
					 | 
				
			||||||
                    log.warning(
 | 
					 | 
				
			||||||
                        'Failed to return result:\n'
 | 
					 | 
				
			||||||
                        f'{func}@{actor.uid}\n'
 | 
					 | 
				
			||||||
                        f'remote chan: {chan.uid}'
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@acm
 | 
					 | 
				
			||||||
async def _errors_relayed_via_ipc(
 | 
					 | 
				
			||||||
    actor: Actor,
 | 
					 | 
				
			||||||
    chan: Channel,
 | 
					 | 
				
			||||||
    ctx: Context,
 | 
					 | 
				
			||||||
    is_rpc: bool,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    hide_tb: bool = False,
 | 
					 | 
				
			||||||
    debug_kbis: bool = False,
 | 
					 | 
				
			||||||
    task_status: TaskStatus[
 | 
					 | 
				
			||||||
        Context | BaseException
 | 
					 | 
				
			||||||
    ] = trio.TASK_STATUS_IGNORED,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
) -> None:
 | 
					 | 
				
			||||||
    __tracebackhide__: bool = hide_tb  # TODO: use hide_tb here?
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        yield  # run RPC invoke body
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # box and ship RPC errors for wire-transit via
 | 
					 | 
				
			||||||
    # the task's requesting parent IPC-channel.
 | 
					 | 
				
			||||||
    except (
 | 
					 | 
				
			||||||
        Exception,
 | 
					 | 
				
			||||||
        BaseExceptionGroup,
 | 
					 | 
				
			||||||
        KeyboardInterrupt,
 | 
					 | 
				
			||||||
    ) as err:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # always hide this frame from debug REPL if the crash
 | 
					 | 
				
			||||||
        # originated from an rpc task and we DID NOT fail due to
 | 
					 | 
				
			||||||
        # an IPC transport error!
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
            is_rpc
 | 
					 | 
				
			||||||
            and chan.connected()
 | 
					 | 
				
			||||||
        ):
 | 
					 | 
				
			||||||
            __tracebackhide__: bool = hide_tb
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not is_multi_cancelled(err):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # TODO: maybe we'll want different "levels" of debugging
 | 
					 | 
				
			||||||
            # eventualy such as ('app', 'supervisory', 'runtime') ?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # if not isinstance(err, trio.ClosedResourceError) and (
 | 
					 | 
				
			||||||
            # if not is_multi_cancelled(err) and (
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            entered_debug: bool = False
 | 
					 | 
				
			||||||
            if (
 | 
					 | 
				
			||||||
                (
 | 
					 | 
				
			||||||
                    not isinstance(err, ContextCancelled)
 | 
					 | 
				
			||||||
                    or (
 | 
					 | 
				
			||||||
                        isinstance(err, ContextCancelled)
 | 
					 | 
				
			||||||
                        and ctx._cancel_called
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        # if the root blocks the debugger lock request from a child
 | 
					 | 
				
			||||||
                        # we will get a remote-cancelled condition.
 | 
					 | 
				
			||||||
                        and ctx._enter_debugger_on_cancel
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                and
 | 
					 | 
				
			||||||
                (
 | 
					 | 
				
			||||||
                    not isinstance(err, KeyboardInterrupt)
 | 
					 | 
				
			||||||
                    or (
 | 
					 | 
				
			||||||
                        isinstance(err, KeyboardInterrupt)
 | 
					 | 
				
			||||||
                        and debug_kbis
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            ):
 | 
					 | 
				
			||||||
                # await _debug.pause()
 | 
					 | 
				
			||||||
                # XXX QUESTION XXX: is there any case where we'll
 | 
					 | 
				
			||||||
                # want to debug IPC disconnects as a default?
 | 
					 | 
				
			||||||
                # => I can't think of a reason that inspecting this
 | 
					 | 
				
			||||||
                # type of failure will be useful for respawns or
 | 
					 | 
				
			||||||
                # recovery logic - the only case is some kind of
 | 
					 | 
				
			||||||
                # strange bug in our transport layer itself? Going
 | 
					 | 
				
			||||||
                # to keep this open ended for now.
 | 
					 | 
				
			||||||
                entered_debug = await _debug._maybe_enter_pm(err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if not entered_debug:
 | 
					 | 
				
			||||||
                    log.exception('Actor crashed:\n')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # always ship errors back to caller
 | 
					 | 
				
			||||||
        err_msg: dict[str, dict] = pack_error(
 | 
					 | 
				
			||||||
            err,
 | 
					 | 
				
			||||||
            # tb=tb, # TODO: special tb fmting?
 | 
					 | 
				
			||||||
            cid=ctx.cid,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # NOTE: the src actor should always be packed into the
 | 
					 | 
				
			||||||
        # error.. but how should we verify this?
 | 
					 | 
				
			||||||
        # assert err_msg['src_actor_uid']
 | 
					 | 
				
			||||||
        # if not err_msg['error'].get('src_actor_uid'):
 | 
					 | 
				
			||||||
        #     import pdbp; pdbp.set_trace()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if is_rpc:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                await chan.send(err_msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # TODO: tests for this scenario:
 | 
					 | 
				
			||||||
            # - RPC caller closes connection before getting a response
 | 
					 | 
				
			||||||
            # should **not** crash this actor..
 | 
					 | 
				
			||||||
            except (
 | 
					 | 
				
			||||||
                trio.ClosedResourceError,
 | 
					 | 
				
			||||||
                trio.BrokenResourceError,
 | 
					 | 
				
			||||||
                BrokenPipeError,
 | 
					 | 
				
			||||||
            ) as ipc_err:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # if we can't propagate the error that's a big boo boo
 | 
					 | 
				
			||||||
                log.exception(
 | 
					 | 
				
			||||||
                    f"Failed to ship error to caller @ {chan.uid} !?\n"
 | 
					 | 
				
			||||||
                    f'{ipc_err}'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # error is probably from above coro running code *not from
 | 
					 | 
				
			||||||
        # the target rpc invocation since a scope was never
 | 
					 | 
				
			||||||
        # allocated around the coroutine await.
 | 
					 | 
				
			||||||
        if ctx._scope is None:
 | 
					 | 
				
			||||||
            # we don't ever raise directly here to allow the
 | 
					 | 
				
			||||||
            # msg-loop-scheduler to continue running for this
 | 
					 | 
				
			||||||
            # channel.
 | 
					 | 
				
			||||||
            task_status.started(err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # always reraise KBIs so they propagate at the sys-process
 | 
					 | 
				
			||||||
        # level.
 | 
					 | 
				
			||||||
        if isinstance(err, KeyboardInterrupt):
 | 
					 | 
				
			||||||
            raise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # RPC task bookeeping
 | 
					 | 
				
			||||||
    finally:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            ctx, func, is_complete = actor._rpc_tasks.pop(
 | 
					 | 
				
			||||||
                (chan, ctx.cid)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            is_complete.set()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        except KeyError:
 | 
					 | 
				
			||||||
            if is_rpc:
 | 
					 | 
				
			||||||
                # If we're cancelled before the task returns then the
 | 
					 | 
				
			||||||
                # cancel scope will not have been inserted yet
 | 
					 | 
				
			||||||
                log.warning(
 | 
					 | 
				
			||||||
                    'RPC task likely errored or cancelled before start?'
 | 
					 | 
				
			||||||
                    f'|_{ctx._task}\n'
 | 
					 | 
				
			||||||
                    f'  >> {ctx.repr_rpc}\n'
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                log.cancel(
 | 
					 | 
				
			||||||
                    'Failed to de-alloc internal runtime cancel task?\n'
 | 
					 | 
				
			||||||
                    f'|_{ctx._task}\n'
 | 
					 | 
				
			||||||
                    f'  >> {ctx.repr_rpc}\n'
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            if not actor._rpc_tasks:
 | 
					 | 
				
			||||||
                log.runtime("All RPC tasks have completed")
 | 
					 | 
				
			||||||
                actor._ongoing_rpc_tasks.set()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def _invoke(
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    actor: 'Actor',
 | 
					 | 
				
			||||||
    cid: str,
 | 
					 | 
				
			||||||
    chan: Channel,
 | 
					 | 
				
			||||||
    func: Callable,
 | 
					 | 
				
			||||||
    kwargs: dict[str, Any],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    is_rpc: bool = True,
 | 
					 | 
				
			||||||
    hide_tb: bool = True,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    task_status: TaskStatus[
 | 
					 | 
				
			||||||
        Union[Context, BaseException]
 | 
					 | 
				
			||||||
    ] = trio.TASK_STATUS_IGNORED,
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    '''
 | 
					 | 
				
			||||||
    Schedule a `trio` task-as-func and deliver result(s) over
 | 
					 | 
				
			||||||
    connected IPC channel.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    This is the core "RPC" `trio.Task` scheduling machinery used to start every
 | 
					 | 
				
			||||||
    remotely invoked function, normally in `Actor._service_n: Nursery`.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    '''
 | 
					 | 
				
			||||||
    __tracebackhide__: bool = hide_tb
 | 
					 | 
				
			||||||
    treat_as_gen: bool = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # possibly a traceback (not sure what typing is for this..)
 | 
					 | 
				
			||||||
    tb = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cancel_scope = CancelScope()
 | 
					    cancel_scope = CancelScope()
 | 
				
			||||||
    # activated cancel scope ref
 | 
					    # activated cancel scope ref
 | 
				
			||||||
| 
						 | 
					@ -712,9 +293,13 @@ def _get_mod_abspath(module):
 | 
				
			||||||
    return os.path.abspath(module.__file__)
 | 
					    return os.path.abspath(module.__file__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def try_ship_error_to_parent(
 | 
					async def try_ship_error_to_remote(
 | 
				
			||||||
    channel: Channel,
 | 
					    channel: Channel,
 | 
				
			||||||
    err: Union[Exception, BaseExceptionGroup],
 | 
					    err: Exception|BaseExceptionGroup,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cid: str|None = None,
 | 
				
			||||||
 | 
					    remote_descr: str = 'parent',
 | 
				
			||||||
 | 
					    hide_tb: bool = True,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
| 
						 | 
					@ -723,22 +308,39 @@ async def try_ship_error_to_parent(
 | 
				
			||||||
    local cancellation ignored but logged as critical(ly bad).
 | 
					    local cancellation ignored but logged as critical(ly bad).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
 | 
					    __tracebackhide__: bool = hide_tb
 | 
				
			||||||
    with CancelScope(shield=True):
 | 
					    with CancelScope(shield=True):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            await channel.send(
 | 
					            # NOTE: normally only used for internal runtime errors
 | 
				
			||||||
                # NOTE: normally only used for internal runtime errors
 | 
					            # so ship to peer actor without a cid.
 | 
				
			||||||
                # so ship to peer actor without a cid.
 | 
					            msg: dict = pack_error(
 | 
				
			||||||
                pack_error(err)
 | 
					                err,
 | 
				
			||||||
 | 
					                cid=cid,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # TODO: special tb fmting for ctxc cases?
 | 
				
			||||||
 | 
					                # tb=tb,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            # NOTE: the src actor should always be packed into the
 | 
				
			||||||
 | 
					            # error.. but how should we verify this?
 | 
				
			||||||
 | 
					            # actor: Actor = _state.current_actor()
 | 
				
			||||||
 | 
					            # assert err_msg['src_actor_uid']
 | 
				
			||||||
 | 
					            # if not err_msg['error'].get('src_actor_uid'):
 | 
				
			||||||
 | 
					            #     import pdbp; pdbp.set_trace()
 | 
				
			||||||
 | 
					            await channel.send(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # XXX NOTE XXX in SC terms this is one of the worst things
 | 
				
			||||||
 | 
					        # that can happen and provides for a 2-general's dilemma..
 | 
				
			||||||
        except (
 | 
					        except (
 | 
				
			||||||
            trio.ClosedResourceError,
 | 
					            trio.ClosedResourceError,
 | 
				
			||||||
            trio.BrokenResourceError,
 | 
					            trio.BrokenResourceError,
 | 
				
			||||||
 | 
					            BrokenPipeError,
 | 
				
			||||||
        ):
 | 
					        ):
 | 
				
			||||||
            # in SC terms this is one of the worst things that can
 | 
					            err_msg: dict = msg['error']['tb_str']
 | 
				
			||||||
            # happen and provides for a 2-general's dilemma..
 | 
					 | 
				
			||||||
            log.critical(
 | 
					            log.critical(
 | 
				
			||||||
                f'Failed to ship error to parent '
 | 
					                'IPC transport failure -> '
 | 
				
			||||||
                f'{channel.uid}, IPC transport failure!'
 | 
					                f'failed to ship error to {remote_descr}!\n\n'
 | 
				
			||||||
 | 
					                f'X=> {channel.uid}\n\n'
 | 
				
			||||||
 | 
					                f'{err_msg}\n'
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -896,7 +498,10 @@ class Actor:
 | 
				
			||||||
        log.runtime(f"{uid} successfully connected back to us")
 | 
					        log.runtime(f"{uid} successfully connected back to us")
 | 
				
			||||||
        return event, self._peers[uid][-1]
 | 
					        return event, self._peers[uid][-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def load_modules(self) -> None:
 | 
					    def load_modules(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        debug_mode: bool = False,
 | 
				
			||||||
 | 
					    ) -> None:
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        Load allowed RPC modules locally (after fork).
 | 
					        Load allowed RPC modules locally (after fork).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -928,7 +533,9 @@ class Actor:
 | 
				
			||||||
        except ModuleNotFoundError:
 | 
					        except ModuleNotFoundError:
 | 
				
			||||||
            # it is expected the corresponding `ModuleNotExposed` error
 | 
					            # it is expected the corresponding `ModuleNotExposed` error
 | 
				
			||||||
            # will be raised later
 | 
					            # will be raised later
 | 
				
			||||||
            log.error(f"Failed to import {modpath} in {self.name}")
 | 
					            log.error(
 | 
				
			||||||
 | 
					                f"Failed to import {modpath} in {self.name}"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            raise
 | 
					            raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_rpc_func(self, ns, funcname):
 | 
					    def _get_rpc_func(self, ns, funcname):
 | 
				
			||||||
| 
						 | 
					@ -1759,7 +1366,7 @@ class Actor:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log.cancel(
 | 
					        log.cancel(
 | 
				
			||||||
            'Cancel request for RPC task\n\n'
 | 
					            'Cancel request for RPC task\n\n'
 | 
				
			||||||
            f'<= Actor.cancel_task(): {requesting_uid}\n\n'
 | 
					            f'<= Actor._cancel_task(): {requesting_uid}\n\n'
 | 
				
			||||||
            f'=> {ctx._task}\n'
 | 
					            f'=> {ctx._task}\n'
 | 
				
			||||||
            f'  |_ >> {ctx.repr_rpc}\n'
 | 
					            f'  |_ >> {ctx.repr_rpc}\n'
 | 
				
			||||||
            # f'  >> Actor._cancel_task() => {ctx._task}\n'
 | 
					            # f'  >> Actor._cancel_task() => {ctx._task}\n'
 | 
				
			||||||
| 
						 | 
					@ -2021,11 +1628,6 @@ async def async_main(
 | 
				
			||||||
            if accept_addr_rent is not None:
 | 
					            if accept_addr_rent is not None:
 | 
				
			||||||
                accept_addr = accept_addr_rent
 | 
					                accept_addr = accept_addr_rent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # load exposed/allowed RPC modules
 | 
					 | 
				
			||||||
        # XXX: do this **after** establishing a channel to the parent
 | 
					 | 
				
			||||||
        # but **before** starting the message loop for that channel
 | 
					 | 
				
			||||||
        # such that import errors are properly propagated upwards
 | 
					 | 
				
			||||||
        actor.load_modules()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The "root" nursery ensures the channel with the immediate
 | 
					        # The "root" nursery ensures the channel with the immediate
 | 
				
			||||||
        # parent is kept alive as a resilient service until
 | 
					        # parent is kept alive as a resilient service until
 | 
				
			||||||
| 
						 | 
					@ -2043,7 +1645,25 @@ async def async_main(
 | 
				
			||||||
                actor._service_n = service_nursery
 | 
					                actor._service_n = service_nursery
 | 
				
			||||||
                assert actor._service_n
 | 
					                assert actor._service_n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # Startup up the channel server with,
 | 
					                # load exposed/allowed RPC modules
 | 
				
			||||||
 | 
					                # XXX: do this **after** establishing a channel to the parent
 | 
				
			||||||
 | 
					                # but **before** starting the message loop for that channel
 | 
				
			||||||
 | 
					                # such that import errors are properly propagated upwards
 | 
				
			||||||
 | 
					                actor.load_modules()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # XXX TODO XXX: figuring out debugging of this
 | 
				
			||||||
 | 
					                # would somemwhat guarantee "self-hosted" runtime
 | 
				
			||||||
 | 
					                # debugging (since it hits all the ede cases?)
 | 
				
			||||||
 | 
					                #
 | 
				
			||||||
 | 
					                # `tractor.pause()` right?
 | 
				
			||||||
 | 
					                # try:
 | 
				
			||||||
 | 
					                #     actor.load_modules()
 | 
				
			||||||
 | 
					                # except ModuleNotFoundError as err:
 | 
				
			||||||
 | 
					                #     _debug.pause_from_sync()
 | 
				
			||||||
 | 
					                #     import pdbp; pdbp.set_trace()
 | 
				
			||||||
 | 
					                #     raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Startup up the transport(-channel) server with,
 | 
				
			||||||
                # - subactor: the bind address is sent by our parent
 | 
					                # - subactor: the bind address is sent by our parent
 | 
				
			||||||
                #   over our established channel
 | 
					                #   over our established channel
 | 
				
			||||||
                # - root actor: the ``accept_addr`` passed to this method
 | 
					                # - root actor: the ``accept_addr`` passed to this method
 | 
				
			||||||
| 
						 | 
					@ -2122,7 +1742,7 @@ async def async_main(
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if actor._parent_chan:
 | 
					        if actor._parent_chan:
 | 
				
			||||||
            await try_ship_error_to_parent(
 | 
					            await try_ship_error_to_remote(
 | 
				
			||||||
                actor._parent_chan,
 | 
					                actor._parent_chan,
 | 
				
			||||||
                err,
 | 
					                err,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
| 
						 | 
					@ -2532,7 +2152,7 @@ async def process_messages(
 | 
				
			||||||
                    log.exception("Actor errored:")
 | 
					                    log.exception("Actor errored:")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if actor._parent_chan:
 | 
					            if actor._parent_chan:
 | 
				
			||||||
                await try_ship_error_to_parent(
 | 
					                await try_ship_error_to_remote(
 | 
				
			||||||
                    actor._parent_chan,
 | 
					                    actor._parent_chan,
 | 
				
			||||||
                    err,
 | 
					                    err,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -215,7 +215,7 @@ async def cancel_on_completion(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def hard_kill(
 | 
					async def hard_kill(
 | 
				
			||||||
    proc: trio.Process,
 | 
					    proc: trio.Process,
 | 
				
			||||||
    terminate_after: int = 3,
 | 
					    terminate_after: int = 1.6,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # NOTE: for mucking with `.pause()`-ing inside the runtime
 | 
					    # NOTE: for mucking with `.pause()`-ing inside the runtime
 | 
				
			||||||
    # whilst also hacking on it XD
 | 
					    # whilst also hacking on it XD
 | 
				
			||||||
| 
						 | 
					@ -281,8 +281,11 @@ async def hard_kill(
 | 
				
			||||||
    # zombies (as a feature) we ask the OS to do send in the
 | 
					    # zombies (as a feature) we ask the OS to do send in the
 | 
				
			||||||
    # removal swad as the last resort.
 | 
					    # removal swad as the last resort.
 | 
				
			||||||
    if cs.cancelled_caught:
 | 
					    if cs.cancelled_caught:
 | 
				
			||||||
 | 
					        # TODO: toss in the skynet-logo face as ascii art?
 | 
				
			||||||
        log.critical(
 | 
					        log.critical(
 | 
				
			||||||
            'Well, the #ZOMBIE_LORD_IS_HERE# to collect\n'
 | 
					            # 'Well, the #ZOMBIE_LORD_IS_HERE# to collect\n'
 | 
				
			||||||
 | 
					            '#T-800 deployed to collect zombie B0\n'
 | 
				
			||||||
 | 
					            f'|\n'
 | 
				
			||||||
            f'|_{proc}\n'
 | 
					            f'|_{proc}\n'
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        proc.kill()
 | 
					        proc.kill()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -114,13 +114,19 @@ class MsgStream(trio.abc.Channel):
 | 
				
			||||||
                    stream=self,
 | 
					                    stream=self,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def receive(self):
 | 
					    async def receive(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hide_tb: bool = True,
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        Receive a single msg from the IPC transport, the next in
 | 
					        Receive a single msg from the IPC transport, the next in
 | 
				
			||||||
        sequence sent by the far end task (possibly in order as
 | 
					        sequence sent by the far end task (possibly in order as
 | 
				
			||||||
        determined by the underlying protocol).
 | 
					        determined by the underlying protocol).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
 | 
					        __tracebackhide__: bool = hide_tb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # NOTE: `trio.ReceiveChannel` implements
 | 
					        # NOTE: `trio.ReceiveChannel` implements
 | 
				
			||||||
        # EOC handling as follows (aka uses it
 | 
					        # EOC handling as follows (aka uses it
 | 
				
			||||||
        # to gracefully exit async for loops):
 | 
					        # to gracefully exit async for loops):
 | 
				
			||||||
| 
						 | 
					@ -139,7 +145,7 @@ class MsgStream(trio.abc.Channel):
 | 
				
			||||||
        if self._closed:
 | 
					        if self._closed:
 | 
				
			||||||
            raise self._closed
 | 
					            raise self._closed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        src_err: Exception|None = None
 | 
					        src_err: Exception|None = None  # orig tb
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                msg = await self._rx_chan.receive()
 | 
					                msg = await self._rx_chan.receive()
 | 
				
			||||||
| 
						 | 
					@ -186,7 +192,7 @@ class MsgStream(trio.abc.Channel):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # TODO: Locally, we want to close this stream gracefully, by
 | 
					            # TODO: Locally, we want to close this stream gracefully, by
 | 
				
			||||||
            # terminating any local consumers tasks deterministically.
 | 
					            # terminating any local consumers tasks deterministically.
 | 
				
			||||||
            # One we have broadcast support, we **don't** want to be
 | 
					            # Once we have broadcast support, we **don't** want to be
 | 
				
			||||||
            # closing this stream and not flushing a final value to
 | 
					            # closing this stream and not flushing a final value to
 | 
				
			||||||
            # remaining (clone) consumers who may not have been
 | 
					            # remaining (clone) consumers who may not have been
 | 
				
			||||||
            # scheduled to receive it yet.
 | 
					            # scheduled to receive it yet.
 | 
				
			||||||
| 
						 | 
					@ -237,7 +243,12 @@ class MsgStream(trio.abc.Channel):
 | 
				
			||||||
                raise_ctxc_from_self_call=True,
 | 
					                raise_ctxc_from_self_call=True,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise src_err  # propagate
 | 
					        # propagate any error but hide low-level frames from
 | 
				
			||||||
 | 
					        # caller by default.
 | 
				
			||||||
 | 
					        if hide_tb:
 | 
				
			||||||
 | 
					            raise type(src_err)(*src_err.args) from src_err
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise src_err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def aclose(self) -> list[Exception|dict]:
 | 
					    async def aclose(self) -> list[Exception|dict]:
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
| 
						 | 
					@ -475,23 +486,39 @@ class MsgStream(trio.abc.Channel):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def send(
 | 
					    async def send(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        data: Any
 | 
					        data: Any,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hide_tb: bool = True,
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        Send a message over this stream to the far end.
 | 
					        Send a message over this stream to the far end.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
        if self._ctx._remote_error:
 | 
					        __tracebackhide__: bool = hide_tb
 | 
				
			||||||
            raise self._ctx._remote_error  # from None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._ctx.maybe_raise()
 | 
				
			||||||
        if self._closed:
 | 
					        if self._closed:
 | 
				
			||||||
            raise self._closed
 | 
					            raise self._closed
 | 
				
			||||||
            # raise trio.ClosedResourceError('This stream was already closed')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await self._ctx.chan.send({
 | 
					        try:
 | 
				
			||||||
            'yield': data,
 | 
					            await self._ctx.chan.send(
 | 
				
			||||||
            'cid': self._ctx.cid,
 | 
					                payload={
 | 
				
			||||||
        })
 | 
					                    'yield': data,
 | 
				
			||||||
 | 
					                    'cid': self._ctx.cid,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                # hide_tb=hide_tb,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        except (
 | 
				
			||||||
 | 
					            trio.ClosedResourceError,
 | 
				
			||||||
 | 
					            trio.BrokenResourceError,
 | 
				
			||||||
 | 
					            BrokenPipeError,
 | 
				
			||||||
 | 
					        ) as trans_err:
 | 
				
			||||||
 | 
					            if hide_tb:
 | 
				
			||||||
 | 
					                raise type(trans_err)(
 | 
				
			||||||
 | 
					                    *trans_err.args
 | 
				
			||||||
 | 
					                ) from trans_err
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def stream(func: Callable) -> Callable:
 | 
					def stream(func: Callable) -> Callable:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue