| 
									
										
										
										
											2021-12-13 18:08:32 +00:00
										 |  |  | # tractor: structured concurrent "actors". | 
					
						
							|  |  |  | # 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/>. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | '''
 | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | Our classy exception set. | 
					
						
							| 
									
										
										
										
											2021-12-13 18:08:32 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | '''
 | 
					
						
							|  |  |  | from __future__ import annotations | 
					
						
							| 
									
										
										
										
											2023-04-12 22:18:46 +00:00
										 |  |  | import builtins | 
					
						
							|  |  |  | import importlib | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  | from pprint import pformat | 
					
						
							| 
									
										
										
										
											2022-09-15 20:56:50 +00:00
										 |  |  | from typing import ( | 
					
						
							|  |  |  |     Any, | 
					
						
							|  |  |  |     Type, | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |     TYPE_CHECKING, | 
					
						
							| 
									
										
										
										
											2022-09-15 20:56:50 +00:00
										 |  |  | ) | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  | import textwrap | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | import traceback | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-30 04:32:10 +00:00
										 |  |  | import trio | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  | from msgspec import structs | 
					
						
							| 
									
										
										
										
											2019-10-30 04:32:10 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 22:41:24 +00:00
										 |  |  | from tractor._state import current_actor | 
					
						
							|  |  |  | from tractor.log import get_logger | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  | from tractor.msg import ( | 
					
						
							|  |  |  |     Error, | 
					
						
							|  |  |  |     Msg, | 
					
						
							|  |  |  |     Stop, | 
					
						
							|  |  |  |     Yield, | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from ._context import Context | 
					
						
							|  |  |  |     from .log import StackLevelAdapter | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     from ._stream import MsgStream | 
					
						
							|  |  |  |     from ._ipc import Channel | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-20 20:26:14 +00:00
										 |  |  | log = get_logger('tractor') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-01 20:58:38 +00:00
										 |  |  | _this_mod = importlib.import_module(__name__) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-31 17:56:26 +00:00
										 |  |  | class ActorFailure(Exception): | 
					
						
							|  |  |  |     "General actor failure" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  | class InternalError(RuntimeError): | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  |     Entirely unexpected internal machinery error indicating | 
					
						
							|  |  |  |     a completely invalid state or interface. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _body_fields: list[str] = [ | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     'boxed_type', | 
					
						
							|  |  |  |     'src_type', | 
					
						
							|  |  |  |     # TODO: format this better if we're going to include it. | 
					
						
							|  |  |  |     # 'relay_path', | 
					
						
							|  |  |  |     'src_uid', | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # only in sub-types | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     'canceller', | 
					
						
							|  |  |  |     'sender', | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _msgdata_keys: list[str] = [ | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     'boxed_type_str', | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  | ] + _body_fields | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  | def get_err_type(type_name: str) -> BaseException|None: | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  |     Look up an exception type by name from the set of locally | 
					
						
							|  |  |  |     known namespaces: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     - `builtins` | 
					
						
							|  |  |  |     - `tractor._exceptions` | 
					
						
							|  |  |  |     - `trio` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  |     for ns in [ | 
					
						
							|  |  |  |         builtins, | 
					
						
							|  |  |  |         _this_mod, | 
					
						
							|  |  |  |         trio, | 
					
						
							|  |  |  |     ]: | 
					
						
							|  |  |  |         if type_ref := getattr( | 
					
						
							|  |  |  |             ns, | 
					
						
							|  |  |  |             type_name, | 
					
						
							|  |  |  |             False, | 
					
						
							|  |  |  |         ): | 
					
						
							|  |  |  |             return type_ref | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  | # TODO: rename to just `RemoteError`? | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | class RemoteActorError(Exception): | 
					
						
							| 
									
										
										
										
											2023-10-07 22:51:03 +00:00
										 |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  |     A box(ing) type which bundles a remote actor `BaseException` for | 
					
						
							|  |  |  |     (near identical, and only if possible,) local object/instance | 
					
						
							|  |  |  |     re-construction in the local process memory domain. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Normally each instance is expected to be constructed from | 
					
						
							|  |  |  |     a special "error" IPC msg sent by some remote actor-runtime. | 
					
						
							| 
									
										
										
										
											2023-10-07 22:51:03 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     reprol_fields: list[str] = [ | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         'src_uid', | 
					
						
							|  |  |  |         'relay_path', | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-13 22:01:49 +00:00
										 |  |  |     def __init__( | 
					
						
							|  |  |  |         self, | 
					
						
							|  |  |  |         message: str, | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         boxed_type: Type[BaseException]|None = None, | 
					
						
							| 
									
										
										
										
											2021-06-13 22:01:49 +00:00
										 |  |  |         **msgdata | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ) -> None: | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  |         super().__init__(message) | 
					
						
							| 
									
										
										
										
											2019-01-01 20:58:38 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  |         # TODO: maybe a better name? | 
					
						
							|  |  |  |         # - .errtype | 
					
						
							|  |  |  |         # - .retype | 
					
						
							|  |  |  |         # - .boxed_errtype | 
					
						
							|  |  |  |         # - .boxed_type | 
					
						
							|  |  |  |         # - .remote_type | 
					
						
							|  |  |  |         # also pertains to our long long oustanding issue XD | 
					
						
							|  |  |  |         # https://github.com/goodboy/tractor/issues/5 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         # | 
					
						
							|  |  |  |         # TODO: always set ._boxed_type` as `None` by default | 
					
						
							|  |  |  |         # and instead render if from `.boxed_type_str`? | 
					
						
							|  |  |  |         self._boxed_type: BaseException = boxed_type | 
					
						
							|  |  |  |         self._src_type: BaseException|None = None | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # TODO: make this a `.errmsg: Error` throughout? | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  |         self.msgdata: dict[str, Any] = msgdata | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         # TODO: mask out eventually or place in `pack_error()` | 
					
						
							|  |  |  |         # pre-`return` lines? | 
					
						
							|  |  |  |         # sanity on inceptions | 
					
						
							|  |  |  |         if boxed_type is RemoteActorError: | 
					
						
							|  |  |  |             assert self.src_type_str != 'RemoteActorError' | 
					
						
							|  |  |  |             assert self.src_uid not in self.relay_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # ensure type-str matches and round-tripping from that | 
					
						
							|  |  |  |         # str results in same error type. | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # TODO NOTE: this is currently exclusively for the | 
					
						
							|  |  |  |         # `ContextCancelled(boxed_type=trio.Cancelled)` case as is | 
					
						
							|  |  |  |         # used inside `._rpc._invoke()` atm though probably we | 
					
						
							|  |  |  |         # should better emphasize that special (one off?) case | 
					
						
							|  |  |  |         # either by customizing `ContextCancelled.__init__()` or | 
					
						
							|  |  |  |         # through a special factor func? | 
					
						
							| 
									
										
										
										
											2024-03-19 18:20:59 +00:00
										 |  |  |         elif boxed_type: | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |             if not self.msgdata.get('boxed_type_str'): | 
					
						
							|  |  |  |                 self.msgdata['boxed_type_str'] = str( | 
					
						
							|  |  |  |                     type(boxed_type).__name__ | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             assert self.boxed_type_str == self.msgdata['boxed_type_str'] | 
					
						
							|  |  |  |             assert self.boxed_type is boxed_type | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def src_type_str(self) -> str: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         String-name of the source error's type. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         This should be the same as `.boxed_type_str` when unpacked | 
					
						
							|  |  |  |         at the first relay/hop's receiving actor. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         return self.msgdata['src_type_str'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def src_type(self) -> str: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         Error type raised by original remote faulting actor. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         if self._src_type is None: | 
					
						
							|  |  |  |             self._src_type = get_err_type( | 
					
						
							|  |  |  |                 self.msgdata['src_type_str'] | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return self._src_type | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-12 22:18:46 +00:00
										 |  |  |     @property | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     def boxed_type_str(self) -> str: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         String-name of the (last hop's) boxed error type. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         return self.msgdata['boxed_type_str'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def boxed_type(self) -> str: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         Error type boxed by last actor IPC hop. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         if self._boxed_type is None: | 
					
						
							| 
									
										
										
										
											2024-03-19 18:20:59 +00:00
										 |  |  |             self._boxed_type = get_err_type( | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |                 self.msgdata['boxed_type_str'] | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return self._boxed_type | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     def relay_path(self) -> list[tuple]: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         Return the list of actors which consecutively relayed | 
					
						
							|  |  |  |         a boxed `RemoteActorError` the src error up until THIS | 
					
						
							|  |  |  |         actor's hop. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         NOTE: a `list` field with the same name is expected to be | 
					
						
							|  |  |  |         passed/updated in `.msgdata`. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         return self.msgdata['relay_path'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def relay_uid(self) -> tuple[str, str]|None: | 
					
						
							|  |  |  |         return tuple( | 
					
						
							|  |  |  |             self.msgdata['relay_path'][-1] | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def src_uid(self) -> tuple[str, str]|None: | 
					
						
							|  |  |  |         if src_uid := ( | 
					
						
							|  |  |  |             self.msgdata.get('src_uid') | 
					
						
							|  |  |  |         ): | 
					
						
							|  |  |  |             return tuple(src_uid) | 
					
						
							|  |  |  |         # TODO: use path lookup instead? | 
					
						
							|  |  |  |         # return tuple( | 
					
						
							|  |  |  |         #     self.msgdata['relay_path'][0] | 
					
						
							|  |  |  |         # ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def tb_str( | 
					
						
							|  |  |  |         self, | 
					
						
							|  |  |  |         indent: str = ' '*3, | 
					
						
							|  |  |  |     ) -> str: | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  |         if remote_tb := self.msgdata.get('tb_str'): | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |             return textwrap.indent( | 
					
						
							|  |  |  |                 remote_tb, | 
					
						
							|  |  |  |                 prefix=indent, | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |         return '' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     def _mk_fields_str( | 
					
						
							|  |  |  |         self, | 
					
						
							|  |  |  |         fields: list[str], | 
					
						
							|  |  |  |         end_char: str = '\n', | 
					
						
							|  |  |  |     ) -> str: | 
					
						
							|  |  |  |         _repr: str = '' | 
					
						
							|  |  |  |         for key in fields: | 
					
						
							|  |  |  |             val: Any|None = ( | 
					
						
							|  |  |  |                 getattr(self, key, None) | 
					
						
							|  |  |  |                 or | 
					
						
							|  |  |  |                 self.msgdata.get(key) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             # TODO: for `.relay_path` on multiline? | 
					
						
							|  |  |  |             # if not isinstance(val, str): | 
					
						
							|  |  |  |             #     val_str = pformat(val) | 
					
						
							|  |  |  |             # else: | 
					
						
							|  |  |  |             val_str: str = repr(val) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if val: | 
					
						
							|  |  |  |                 _repr += f'{key}={val_str}{end_char}' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return _repr | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     def reprol(self) -> str: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         Represent this error for "one line" display, like in | 
					
						
							|  |  |  |         a field of our `Context.__repr__()` output. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         # TODO: use this matryoshka emjoi XD | 
					
						
							|  |  |  |         # => 🪆 | 
					
						
							|  |  |  |         reprol_str: str = f'{type(self).__name__}(' | 
					
						
							|  |  |  |         _repr: str = self._mk_fields_str( | 
					
						
							|  |  |  |             self.reprol_fields, | 
					
						
							|  |  |  |             end_char=' ', | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         return ( | 
					
						
							|  |  |  |             reprol_str | 
					
						
							|  |  |  |             + | 
					
						
							|  |  |  |             _repr | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __repr__(self) -> str: | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         '''
 | 
					
						
							|  |  |  |         Nicely formatted boxed error meta data + traceback. | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         '''
 | 
					
						
							|  |  |  |         fields: str = self._mk_fields_str( | 
					
						
							|  |  |  |             _body_fields, | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |         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' | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         if indent: | 
					
						
							|  |  |  |             body: str = textwrap.indent( | 
					
						
							|  |  |  |                 body, | 
					
						
							|  |  |  |                 prefix=indent, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         return ( | 
					
						
							|  |  |  |             f'<{type(self).__name__}(\n' | 
					
						
							|  |  |  |             f'{body}' | 
					
						
							|  |  |  |             ')>' | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     def unwrap( | 
					
						
							|  |  |  |         self, | 
					
						
							|  |  |  |     ) -> BaseException: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         Unpack the inner-most source error from it's original IPC msg data. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         We attempt to reconstruct (as best as we can) the original | 
					
						
							|  |  |  |         `Exception` from as it would have been raised in the | 
					
						
							|  |  |  |         failing actor's remote env. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         src_type_ref: Type[BaseException] = self.src_type | 
					
						
							|  |  |  |         if not src_type_ref: | 
					
						
							|  |  |  |             raise TypeError( | 
					
						
							|  |  |  |                 'Failed to lookup src error type:\n' | 
					
						
							|  |  |  |                 f'{self.src_type_str}' | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TODO: better tb insertion and all the fancier dunder | 
					
						
							|  |  |  |         # metadata stuff as per `.__context__` etc. and friends: | 
					
						
							|  |  |  |         # https://github.com/python-trio/trio/issues/611 | 
					
						
							|  |  |  |         return src_type_ref(self.tb_str) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # TODO: local recontruction of nested inception for a given | 
					
						
							|  |  |  |     # "hop" / relay-node in this error's relay_path? | 
					
						
							|  |  |  |     # => so would render a `RAE[RAE[RAE[Exception]]]` instance | 
					
						
							|  |  |  |     #   with all inner errors unpacked? | 
					
						
							|  |  |  |     # -[ ] if this is useful shouldn't be too hard to impl right? | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  |     # def unbox(self) -> BaseException: | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     #     ''' | 
					
						
							|  |  |  |     #     Unbox to the prior relays (aka last boxing actor's) | 
					
						
							|  |  |  |     #     inner error. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     #     ''' | 
					
						
							|  |  |  |     #     if not self.relay_path: | 
					
						
							|  |  |  |     #         return self.unwrap() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     #     # TODO.. | 
					
						
							|  |  |  |     #     # return self.boxed_type( | 
					
						
							|  |  |  |     #     #     boxed_type=get_type_ref(.. | 
					
						
							|  |  |  |     #     raise NotImplementedError | 
					
						
							| 
									
										
										
										
											2023-10-18 23:09:07 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | class InternalActorError(RemoteActorError): | 
					
						
							| 
									
										
										
										
											2023-04-07 20:08:07 +00:00
										 |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     (Remote) internal `tractor` error indicating failure of some | 
					
						
							|  |  |  |     primitive, machinery state or lowlevel task that should never | 
					
						
							|  |  |  |     occur. | 
					
						
							| 
									
										
										
										
											2023-04-07 20:08:07 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-12 22:18:46 +00:00
										 |  |  | class ContextCancelled(RemoteActorError): | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  |     Inter-actor task context was cancelled by either a call to | 
					
						
							|  |  |  |     ``Portal.cancel_actor()`` or ``Context.cancel()``. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     reprol_fields: list[str] = [ | 
					
						
							|  |  |  |         'canceller', | 
					
						
							|  |  |  |     ] | 
					
						
							| 
									
										
										
										
											2023-04-12 22:18:46 +00:00
										 |  |  |     @property | 
					
						
							| 
									
										
										
										
											2024-02-20 20:26:14 +00:00
										 |  |  |     def canceller(self) -> tuple[str, str]|None: | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         Return the (maybe) `Actor.uid` for the requesting-author | 
					
						
							|  |  |  |         of this ctxc. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Emit a warning msg when `.canceller` has not been set, | 
					
						
							|  |  |  |         which usually idicates that a `None` msg-loop setinel was | 
					
						
							|  |  |  |         sent before expected in the runtime. This can happen in | 
					
						
							|  |  |  |         a few situations: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         - (simulating) an IPC transport network outage | 
					
						
							|  |  |  |         - a (malicious) pkt sent specifically to cancel an actor's | 
					
						
							|  |  |  |           runtime non-gracefully without ensuring ongoing RPC tasks are  | 
					
						
							|  |  |  |           incrementally cancelled as is done with: | 
					
						
							|  |  |  |           `Actor` | 
					
						
							|  |  |  |           |_`.cancel()` | 
					
						
							|  |  |  |           |_`.cancel_soon()` | 
					
						
							|  |  |  |           |_`._cancel_task()` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							| 
									
										
										
										
											2023-04-13 19:18:00 +00:00
										 |  |  |         value = self.msgdata.get('canceller') | 
					
						
							|  |  |  |         if value: | 
					
						
							|  |  |  |             return tuple(value) | 
					
						
							| 
									
										
										
										
											2021-06-24 22:49:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-20 20:26:14 +00:00
										 |  |  |         log.warning( | 
					
						
							|  |  |  |             'IPC Context cancelled without a requesting actor?\n' | 
					
						
							|  |  |  |             'Maybe the IPC transport ended abruptly?\n\n' | 
					
						
							|  |  |  |             f'{self}' | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     # TODO: to make `.__repr__()` work uniformly? | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     # src_actor_uid = canceller | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-31 17:56:26 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-12 22:18:46 +00:00
										 |  |  | class TransportClosed(trio.ClosedResourceError): | 
					
						
							|  |  |  |     "Underlying channel transport was closed prior to use" | 
					
						
							| 
									
										
										
										
											2021-06-13 22:01:49 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-24 22:49:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | class NoResult(RuntimeError): | 
					
						
							|  |  |  |     "No final result is expected for this actor" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-12 22:55:28 +00:00
										 |  |  | class ModuleNotExposed(ModuleNotFoundError): | 
					
						
							| 
									
										
										
										
											2019-01-01 20:58:38 +00:00
										 |  |  |     "The requested module is not exposed for RPC" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-27 15:07:53 +00:00
										 |  |  | class NoRuntime(RuntimeError): | 
					
						
							|  |  |  |     "The root actor has not been initialized yet" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-16 20:23:00 +00:00
										 |  |  | class StreamOverrun( | 
					
						
							|  |  |  |     RemoteActorError, | 
					
						
							|  |  |  |     trio.TooSlowError, | 
					
						
							|  |  |  | ): | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     reprol_fields: list[str] = [ | 
					
						
							|  |  |  |         'sender', | 
					
						
							|  |  |  |     ] | 
					
						
							| 
									
										
										
										
											2024-02-16 20:23:00 +00:00
										 |  |  |     '''
 | 
					
						
							|  |  |  |     This stream was overrun by sender | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def sender(self) -> tuple[str, str] | None: | 
					
						
							|  |  |  |         value = self.msgdata.get('sender') | 
					
						
							|  |  |  |         if value: | 
					
						
							|  |  |  |             return tuple(value) | 
					
						
							| 
									
										
										
										
											2021-12-05 23:28:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-23 21:19:19 +00:00
										 |  |  | class AsyncioCancelled(Exception): | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  |     Asyncio cancelled translation (non-base) error | 
					
						
							|  |  |  |     for use with the ``to_asyncio`` module | 
					
						
							|  |  |  |     to be raised in the ``trio`` side task | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-23 18:17:36 +00:00
										 |  |  | class MessagingError(Exception): | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |     '''
 | 
					
						
							|  |  |  |     IPC related msg (typing), transaction (ordering) or dialog | 
					
						
							|  |  |  |     handling error. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class MsgTypeError(MessagingError): | 
					
						
							|  |  |  |     '''
 | 
					
						
							|  |  |  |     Equivalent of a `TypeError` for an IPC wire-message | 
					
						
							|  |  |  |     due to an invalid field value (type). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Normally this is re-raised from some `.msg._codec` | 
					
						
							|  |  |  |     decode error raised by a backend interchange lib | 
					
						
							|  |  |  |     like `msgspec` or `pycapnproto`. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2023-10-23 18:17:36 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-23 21:19:19 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-27 15:37:35 +00:00
										 |  |  | def pack_error( | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     exc: BaseException|RemoteActorError, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-18 22:17:31 +00:00
										 |  |  |     tb: str|None = None, | 
					
						
							|  |  |  |     cid: str|None = None, | 
					
						
							| 
									
										
										
										
											2021-06-27 15:37:35 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-02 18:32:16 +00:00
										 |  |  | ) -> Error: | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-02-18 22:17:31 +00:00
										 |  |  |     Create an "error message" which boxes a locally caught | 
					
						
							|  |  |  |     exception's meta-data and encodes it for wire transport via an | 
					
						
							|  |  |  |     IPC `Channel`; expected to be unpacked (and thus unboxed) on | 
					
						
							|  |  |  |     the receiver side using `unpack_error()` below. | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2021-06-27 15:37:35 +00:00
										 |  |  |     if tb: | 
					
						
							|  |  |  |         tb_str = ''.join(traceback.format_tb(tb)) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         tb_str = traceback.format_exc() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 15:36:39 +00:00
										 |  |  |     error_msg: dict[  # for IPC | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  |         str, | 
					
						
							|  |  |  |         str | tuple[str, str] | 
					
						
							| 
									
										
										
										
											2024-03-20 15:36:39 +00:00
										 |  |  |     ] = {} | 
					
						
							|  |  |  |     our_uid: tuple = current_actor().uid | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-16 20:23:00 +00:00
										 |  |  |     if ( | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         isinstance(exc, RemoteActorError) | 
					
						
							| 
									
										
										
										
											2024-02-16 20:23:00 +00:00
										 |  |  |     ): | 
					
						
							| 
									
										
										
										
											2023-04-12 22:18:46 +00:00
										 |  |  |         error_msg.update(exc.msgdata) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     # an onion/inception we need to pack | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |         type(exc) is RemoteActorError | 
					
						
							| 
									
										
										
										
											2024-03-19 18:20:59 +00:00
										 |  |  |         and (boxed := exc.boxed_type) | 
					
						
							|  |  |  |         and boxed != RemoteActorError | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     ): | 
					
						
							|  |  |  |         # sanity on source error (if needed when tweaking this) | 
					
						
							|  |  |  |         assert (src_type := exc.src_type) != RemoteActorError | 
					
						
							|  |  |  |         assert error_msg['src_type_str'] != 'RemoteActorError' | 
					
						
							|  |  |  |         assert error_msg['src_type_str'] == src_type.__name__ | 
					
						
							|  |  |  |         assert error_msg['src_uid'] != our_uid | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # set the boxed type to be another boxed type thus | 
					
						
							|  |  |  |         # creating an "inception" when unpacked by | 
					
						
							|  |  |  |         # `unpack_error()` in another actor who gets "relayed" | 
					
						
							|  |  |  |         # this error Bo | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # NOTE on WHY: since we are re-boxing and already | 
					
						
							|  |  |  |         # boxed src error, we want to overwrite the original | 
					
						
							|  |  |  |         # `boxed_type_str` and instead set it to the type of | 
					
						
							|  |  |  |         # the input `exc` type. | 
					
						
							|  |  |  |         error_msg['boxed_type_str'] = 'RemoteActorError' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         error_msg['src_uid'] = our_uid | 
					
						
							|  |  |  |         error_msg['src_type_str'] =  type(exc).__name__ | 
					
						
							|  |  |  |         error_msg['boxed_type_str'] = type(exc).__name__ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # XXX alawys append us the last relay in error propagation path | 
					
						
							|  |  |  |     error_msg.setdefault( | 
					
						
							|  |  |  |         'relay_path', | 
					
						
							|  |  |  |         [], | 
					
						
							|  |  |  |     ).append(our_uid) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-20 15:36:39 +00:00
										 |  |  |     # XXX NOTE: always ensure the traceback-str is from the | 
					
						
							|  |  |  |     # locally raised error (**not** the prior relay's boxed | 
					
						
							|  |  |  |     # content's `.msgdata`). | 
					
						
							|  |  |  |     error_msg['tb_str'] = tb_str | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-02 18:32:16 +00:00
										 |  |  |     if cid is not None: | 
					
						
							|  |  |  |         error_msg['cid'] = cid | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Error(**error_msg) | 
					
						
							| 
									
										
										
										
											2023-04-12 22:18:46 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-25 20:07:36 +00:00
										 |  |  | def unpack_error( | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |     msg: dict[str, Any]|Error, | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     chan: Channel|None = None, | 
					
						
							|  |  |  |     box_type: RemoteActorError = RemoteActorError, | 
					
						
							| 
									
										
										
										
											2024-03-19 18:20:59 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-14 21:13:32 +00:00
										 |  |  |     hide_tb: bool = True, | 
					
						
							| 
									
										
										
										
											2021-06-24 22:49:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-18 22:17:31 +00:00
										 |  |  | ) -> None|Exception: | 
					
						
							| 
									
										
										
										
											2022-10-12 21:41:01 +00:00
										 |  |  |     '''
 | 
					
						
							|  |  |  |     Unpack an 'error' message from the wire | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  |     into a local `RemoteActorError` (subtype). | 
					
						
							| 
									
										
										
										
											2021-06-24 22:49:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  |     NOTE: this routine DOES not RAISE the embedded remote error, | 
					
						
							|  |  |  |     which is the responsibilitiy of the caller. | 
					
						
							| 
									
										
										
										
											2021-06-13 22:01:49 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-02-14 21:13:32 +00:00
										 |  |  |     __tracebackhide__: bool = hide_tb | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |     error_dict: dict[str, dict]|None | 
					
						
							|  |  |  |     if not isinstance(msg, Error): | 
					
						
							|  |  |  |     # if ( | 
					
						
							|  |  |  |     #     error_dict := msg.get('error') | 
					
						
							|  |  |  |     # ) is None: | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  |         # no error field, nothing to unpack. | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # retrieve the remote error's msg encoded details | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |     # tb_str: str = error_dict.get('tb_str', '') | 
					
						
							|  |  |  |     tb_str: str = msg.tb_str | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |     message: str = ( | 
					
						
							|  |  |  |         f'{chan.uid}\n' | 
					
						
							|  |  |  |         + | 
					
						
							|  |  |  |         tb_str | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2024-03-19 18:20:59 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # try to lookup a suitable error type from the local runtime | 
					
						
							|  |  |  |     # env then use it to construct a local instance. | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |     # boxed_type_str: str = error_dict['boxed_type_str'] | 
					
						
							|  |  |  |     boxed_type_str: str = msg.boxed_type_str | 
					
						
							| 
									
										
										
										
											2024-03-19 18:20:59 +00:00
										 |  |  |     boxed_type: Type[BaseException] = get_err_type(boxed_type_str) | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if boxed_type_str == 'ContextCancelled': | 
					
						
							| 
									
										
										
										
											2024-03-19 18:20:59 +00:00
										 |  |  |         box_type = ContextCancelled | 
					
						
							|  |  |  |         assert boxed_type is box_type | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # TODO: already included by `_this_mod` in else loop right? | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # we have an inception/onion-error so ensure | 
					
						
							|  |  |  |     # we include the relay_path info and the | 
					
						
							|  |  |  |     # original source error. | 
					
						
							|  |  |  |     elif boxed_type_str == 'RemoteActorError': | 
					
						
							| 
									
										
										
										
											2024-03-19 18:20:59 +00:00
										 |  |  |         assert boxed_type is RemoteActorError | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |         # assert len(error_dict['relay_path']) >= 1 | 
					
						
							|  |  |  |         assert len(msg.relay_path) >= 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # TODO: mk RAE just take the `Error` instance directly? | 
					
						
							|  |  |  |     error_dict: dict = structs.asdict(msg) | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-29 23:56:31 +00:00
										 |  |  |     exc = box_type( | 
					
						
							| 
									
										
										
										
											2021-06-13 22:01:49 +00:00
										 |  |  |         message, | 
					
						
							| 
									
										
										
										
											2023-10-16 20:23:30 +00:00
										 |  |  |         **error_dict, | 
					
						
							| 
									
										
										
										
											2018-11-19 08:51:12 +00:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2020-12-25 20:07:36 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-13 22:01:49 +00:00
										 |  |  |     return exc | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-25 20:07:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def is_multi_cancelled(exc: BaseException) -> bool: | 
					
						
							| 
									
										
										
										
											2022-10-09 17:12:50 +00:00
										 |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-03-13 22:41:24 +00:00
										 |  |  |     Predicate to determine if a possible ``BaseExceptionGroup`` contains | 
					
						
							| 
									
										
										
										
											2022-10-09 17:12:50 +00:00
										 |  |  |     only ``trio.Cancelled`` sub-exceptions (and is likely the result of | 
					
						
							| 
									
										
										
										
											2020-12-25 20:07:36 +00:00
										 |  |  |     cancelling a collection of subtasks. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 17:12:50 +00:00
										 |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-03-13 22:41:24 +00:00
										 |  |  |     # if isinstance(exc, eg.BaseExceptionGroup): | 
					
						
							|  |  |  |     if isinstance(exc, BaseExceptionGroup): | 
					
						
							| 
									
										
										
										
											2022-10-09 17:12:50 +00:00
										 |  |  |         return exc.subgroup( | 
					
						
							|  |  |  |             lambda exc: isinstance(exc, trio.Cancelled) | 
					
						
							|  |  |  |         ) is not None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return False | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _raise_from_no_key_in_msg( | 
					
						
							|  |  |  |     ctx: Context, | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |     msg: Msg, | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |     src_err: KeyError, | 
					
						
							|  |  |  |     log: StackLevelAdapter,  # caller specific `log` obj | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |     expect_key: str = 'yield', | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |     expect_msg: str = Yield, | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |     stream: MsgStream | None = None, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |     # allow "deeper" tbs when debugging B^o | 
					
						
							|  |  |  |     hide_tb: bool = True, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | ) -> bool: | 
					
						
							|  |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |     Raise an appopriate local error when a | 
					
						
							|  |  |  |     `MsgStream` msg arrives which does not | 
					
						
							|  |  |  |     contain the expected (at least under normal | 
					
						
							|  |  |  |     operation) `'yield'` field. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     `Context` and any embedded `MsgStream` termination, | 
					
						
							|  |  |  |     as well as remote task errors are handled in order | 
					
						
							|  |  |  |     of priority as: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     - any 'error' msg is re-boxed and raised locally as | 
					
						
							|  |  |  |       -> `RemoteActorError`|`ContextCancelled` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     - a `MsgStream` 'stop' msg is constructed, assigned | 
					
						
							|  |  |  |       and raised locally as -> `trio.EndOfChannel` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     - All other mis-keyed msgss (like say a "final result" | 
					
						
							|  |  |  |       'return' msg, normally delivered from `Context.result()`) | 
					
						
							|  |  |  |       are re-boxed inside a `MessagingError` with an explicit | 
					
						
							|  |  |  |       exc content describing the missing IPC-msg-key. | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     '''
 | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |     __tracebackhide__: bool = hide_tb | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |     # an internal error should never get here | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |         cid: str = msg.cid | 
					
						
							|  |  |  |         # cid: str = msg['cid'] | 
					
						
							|  |  |  |     # except KeyError as src_err: | 
					
						
							|  |  |  |     except AttributeError as src_err: | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |         raise MessagingError( | 
					
						
							|  |  |  |             f'IPC `Context` rx-ed msg without a ctx-id (cid)!?\n' | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |             f'cid: {cid}\n\n' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |             f'{pformat(msg)}\n' | 
					
						
							|  |  |  |         ) from src_err | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # TODO: test that shows stream raising an expected error!!! | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # raise the error message in a boxed exception type! | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |     # if msg.get('error'): | 
					
						
							|  |  |  |     if isinstance(msg, Error): | 
					
						
							|  |  |  |     # match msg: | 
					
						
							|  |  |  |     #     case Error(): | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |         raise unpack_error( | 
					
						
							|  |  |  |             msg, | 
					
						
							|  |  |  |             ctx.chan, | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |             hide_tb=hide_tb, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |         ) from None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |     # `MsgStream` termination msg. | 
					
						
							| 
									
										
										
										
											2024-02-22 19:22:45 +00:00
										 |  |  |     # TODO: does it make more sense to pack  | 
					
						
							|  |  |  |     # the stream._eoc outside this in the calleer always? | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |         # case Stop(): | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |     elif ( | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |         # msg.get('stop') | 
					
						
							|  |  |  |         isinstance(msg, Stop) | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |         or ( | 
					
						
							|  |  |  |             stream | 
					
						
							|  |  |  |             and stream._eoc | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     ): | 
					
						
							|  |  |  |         log.debug( | 
					
						
							|  |  |  |             f'Context[{cid}] stream was stopped by remote side\n' | 
					
						
							|  |  |  |             f'cid: {cid}\n' | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-14 21:13:32 +00:00
										 |  |  |         # TODO: if the a local task is already blocking on | 
					
						
							|  |  |  |         # a `Context.result()` and thus a `.receive()` on the | 
					
						
							|  |  |  |         # rx-chan, we close the chan and set state ensuring that | 
					
						
							|  |  |  |         # an eoc is raised! | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |         # XXX: this causes ``ReceiveChannel.__anext__()`` to | 
					
						
							|  |  |  |         # raise a ``StopAsyncIteration`` **and** in our catch | 
					
						
							|  |  |  |         # block below it will trigger ``.aclose()``. | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |         eoc = trio.EndOfChannel( | 
					
						
							| 
									
										
										
										
											2024-02-20 20:26:14 +00:00
										 |  |  |             f'Context stream ended due to msg:\n\n' | 
					
						
							|  |  |  |             f'{pformat(msg)}\n' | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |         ) | 
					
						
							|  |  |  |         # XXX: important to set so that a new `.receive()` | 
					
						
							|  |  |  |         # call (likely by another task using a broadcast receiver) | 
					
						
							|  |  |  |         # doesn't accidentally pull the `return` message | 
					
						
							|  |  |  |         # value out of the underlying feed mem chan which is | 
					
						
							|  |  |  |         # destined for the `Context.result()` call during ctx-exit! | 
					
						
							|  |  |  |         stream._eoc: Exception = eoc | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:21:37 +00:00
										 |  |  |         # in case there already is some underlying remote error | 
					
						
							|  |  |  |         # that arrived which is probably the source of this stream | 
					
						
							|  |  |  |         # closure | 
					
						
							|  |  |  |         ctx.maybe_raise() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 18:17:37 +00:00
										 |  |  |         raise eoc from src_err | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |         stream | 
					
						
							|  |  |  |         and stream._closed | 
					
						
							|  |  |  |     ): | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |         # TODO: our own error subtype? | 
					
						
							|  |  |  |         raise trio.ClosedResourceError( | 
					
						
							|  |  |  |             'This stream was closed' | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # always re-raise the source error if no translation error case | 
					
						
							|  |  |  |     # is activated above. | 
					
						
							|  |  |  |     _type: str = 'Stream' if stream else 'Context' | 
					
						
							|  |  |  |     raise MessagingError( | 
					
						
							| 
									
										
										
										
											2024-04-02 17:41:52 +00:00
										 |  |  |         f"{_type} was expecting a '{expect_key.upper()}' message" | 
					
						
							| 
									
										
										
										
											2024-02-18 22:17:31 +00:00
										 |  |  |         " BUT received a non-error msg:\n" | 
					
						
							|  |  |  |         f'{pformat(msg)}' | 
					
						
							| 
									
										
										
										
											2024-01-02 23:34:15 +00:00
										 |  |  |     ) from src_err |