From 350a94f39e67aa610b0e1c0a9d82bfb24b20b132 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 27 Dec 2024 14:07:50 -0500 Subject: [PATCH] Accept err-type override in `is_multi_cancelled()` Such that equivalents of `trio.Cancelled` from other runtimes such as `asyncio.CancelledError` and `subprocess.CalledProcessError` (with a `.returncode == -2`) can be gracefully ignored as needed by the caller. For example this is handy if you want to avoid debug-mode REPL entry on an exception-group full of only some subset of exception types since you expect certain tasks to raise such errors after having been cancelled by a request from some parent supervision sys (some "higher up" `trio.CancelScope`, a remote triggered `ContextCancelled` or just from and OS SIGINT). Impl deats, - offer a new `ignore_nested: set[BaseException]` param which by default we add `trio.Cancelled` to when no other types are provided. - use `ExceptionGroup.subgroup(tuple(ignore_nested)` to filter to egs of the "ignored sub-errors set" and return any such match (instead of `True`). - detail a comment on exclusion case. --- tractor/_exceptions.py | 48 +++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/tractor/_exceptions.py b/tractor/_exceptions.py index 812664a..89ea21a 100644 --- a/tractor/_exceptions.py +++ b/tractor/_exceptions.py @@ -1146,19 +1146,51 @@ def unpack_error( def is_multi_cancelled( - exc: BaseException|BaseExceptionGroup -) -> bool: + exc: BaseException|BaseExceptionGroup, + + ignore_nested: set[BaseException] = set(), + +) -> bool|BaseExceptionGroup: ''' - Predicate to determine if a possible ``BaseExceptionGroup`` contains - only ``trio.Cancelled`` sub-exceptions (and is likely the result of - cancelling a collection of subtasks. + Predicate to determine if an `BaseExceptionGroup` only contains + some (maybe nested) set of sub-grouped exceptions (like only + `trio.Cancelled`s which get swallowed silently by default) and is + thus the result of "gracefully cancelling" a collection of + sub-tasks (or other conc primitives) and receiving a "cancelled + ACK" from each after termination. + + Docs: + ---- + - https://docs.python.org/3/library/exceptions.html#exception-groups + - https://docs.python.org/3/library/exceptions.html#BaseExceptionGroup.subgroup ''' + + if ( + not ignore_nested + or + trio.Cancelled in ignore_nested + # XXX always count-in `trio`'s native signal + ): + ignore_nested |= {trio.Cancelled} + if isinstance(exc, BaseExceptionGroup): - return exc.subgroup( - lambda exc: isinstance(exc, trio.Cancelled) - ) is not None + matched_exc: BaseExceptionGroup|None = exc.subgroup( + tuple(ignore_nested), + # TODO, complain about why not allowed XD + # condition=tuple(ignore_nested), + ) + if matched_exc is not None: + return matched_exc + + # NOTE, IFF no excs types match (throughout the error-tree) + # -> return `False`, OW return the matched sub-eg. + # + # IOW, for the inverse of ^ for the purpose of + # maybe-enter-REPL--logic: "only debug when the err-tree contains + # at least one exc-type NOT in `ignore_nested`" ; i.e. the case where + # we fallthrough and return `False` here. return False