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.
hilevel_serman
Tyler Goodlet 2024-12-27 14:07:50 -05:00
parent 0945631629
commit 350a94f39e
1 changed files with 40 additions and 8 deletions

View File

@ -1146,19 +1146,51 @@ def unpack_error(
def is_multi_cancelled( def is_multi_cancelled(
exc: BaseException|BaseExceptionGroup exc: BaseException|BaseExceptionGroup,
) -> bool:
ignore_nested: set[BaseException] = set(),
) -> bool|BaseExceptionGroup:
''' '''
Predicate to determine if a possible ``BaseExceptionGroup`` contains Predicate to determine if an `BaseExceptionGroup` only contains
only ``trio.Cancelled`` sub-exceptions (and is likely the result of some (maybe nested) set of sub-grouped exceptions (like only
cancelling a collection of subtasks. `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): if isinstance(exc, BaseExceptionGroup):
return exc.subgroup( matched_exc: BaseExceptionGroup|None = exc.subgroup(
lambda exc: isinstance(exc, trio.Cancelled) tuple(ignore_nested),
) is not None
# 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 return False