Move `.is_multi_cancelled()` to `.trioniics._beg`
Since it's for beg filtering, the current impl should be renamed anyway; it's not just for filtering cancelled excs. Deats, - added a real doc string, links to official eg docs and fixed the return typing. - adjust all internal imports to match.moar_eg_smoothing
parent
7b05547fcc
commit
502c7a1dc6
|
@ -252,7 +252,7 @@ def test_simple_context(
|
|||
pass
|
||||
except BaseExceptionGroup as beg:
|
||||
# XXX: on windows it seems we may have to expect the group error
|
||||
from tractor._exceptions import is_multi_cancelled
|
||||
from tractor.trionics import is_multi_cancelled
|
||||
assert is_multi_cancelled(beg)
|
||||
else:
|
||||
trio.run(main)
|
||||
|
|
|
@ -1246,55 +1246,6 @@ def unpack_error(
|
|||
return exc
|
||||
|
||||
|
||||
def is_multi_cancelled(
|
||||
exc: BaseException|BaseExceptionGroup,
|
||||
|
||||
ignore_nested: set[BaseException] = set(),
|
||||
|
||||
) -> bool|BaseExceptionGroup:
|
||||
'''
|
||||
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.update({trio.Cancelled})
|
||||
|
||||
if isinstance(exc, BaseExceptionGroup):
|
||||
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
|
||||
|
||||
|
||||
def _raise_from_unexpected_msg(
|
||||
ctx: Context,
|
||||
msg: MsgType,
|
||||
|
|
|
@ -61,9 +61,11 @@ from ._addr import (
|
|||
mk_uuid,
|
||||
wrap_address,
|
||||
)
|
||||
from .trionics import (
|
||||
is_multi_cancelled,
|
||||
)
|
||||
from ._exceptions import (
|
||||
RuntimeFailure,
|
||||
is_multi_cancelled,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -40,8 +40,10 @@ from ._state import current_actor, is_main_process
|
|||
from .log import get_logger, get_loglevel
|
||||
from ._runtime import Actor
|
||||
from ._portal import Portal
|
||||
from ._exceptions import (
|
||||
from .trionics import (
|
||||
is_multi_cancelled,
|
||||
)
|
||||
from ._exceptions import (
|
||||
ContextCancelled,
|
||||
)
|
||||
from ._root import (
|
||||
|
|
|
@ -59,7 +59,7 @@ from tractor._state import (
|
|||
debug_mode,
|
||||
)
|
||||
from tractor.log import get_logger
|
||||
from tractor._exceptions import (
|
||||
from tractor.trionics import (
|
||||
is_multi_cancelled,
|
||||
)
|
||||
from ._trace import (
|
||||
|
|
|
@ -38,7 +38,6 @@ from typing import (
|
|||
import tractor
|
||||
from tractor._exceptions import (
|
||||
InternalError,
|
||||
is_multi_cancelled,
|
||||
TrioTaskExited,
|
||||
TrioCancelled,
|
||||
AsyncioTaskExited,
|
||||
|
@ -59,6 +58,9 @@ from tractor.log import (
|
|||
# from tractor.msg import (
|
||||
# pretty_struct,
|
||||
# )
|
||||
from tractor.trionics import (
|
||||
is_multi_cancelled,
|
||||
)
|
||||
from tractor.trionics._broadcast import (
|
||||
broadcast_receiver,
|
||||
BroadcastReceiver,
|
||||
|
|
|
@ -32,4 +32,5 @@ from ._broadcast import (
|
|||
from ._beg import (
|
||||
collapse_eg as collapse_eg,
|
||||
maybe_collapse_eg as maybe_collapse_eg,
|
||||
is_multi_cancelled as is_multi_cancelled,
|
||||
)
|
||||
|
|
|
@ -22,6 +22,11 @@ first-class-`trio` from a historical perspective B)
|
|||
from contextlib import (
|
||||
asynccontextmanager as acm,
|
||||
)
|
||||
from typing import (
|
||||
Literal,
|
||||
)
|
||||
|
||||
import trio
|
||||
|
||||
|
||||
def maybe_collapse_eg(
|
||||
|
@ -56,3 +61,62 @@ async def collapse_eg():
|
|||
raise exc
|
||||
|
||||
raise beg
|
||||
|
||||
|
||||
def is_multi_cancelled(
|
||||
beg: BaseException|BaseExceptionGroup,
|
||||
|
||||
ignore_nested: set[BaseException] = set(),
|
||||
|
||||
) -> Literal[False]|BaseExceptionGroup:
|
||||
'''
|
||||
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 not in ignore_nested
|
||||
# XXX always count-in `trio`'s native signal
|
||||
):
|
||||
ignore_nested.update({trio.Cancelled})
|
||||
|
||||
if isinstance(beg, BaseExceptionGroup):
|
||||
# https://docs.python.org/3/library/exceptions.html#BaseExceptionGroup.subgroup
|
||||
# |_ "The condition can be an exception type or tuple of
|
||||
# exception types, in which case each exception is checked
|
||||
# for a match using the same check that is used in an
|
||||
# except clause. The condition can also be a callable
|
||||
# (other than a type object) that accepts an exception as
|
||||
# its single argument and returns true for the exceptions
|
||||
# that should be in the subgroup."
|
||||
matched_exc: BaseExceptionGroup|None = beg.subgroup(
|
||||
tuple(ignore_nested),
|
||||
|
||||
# ??TODO, complain about why not allowed to use
|
||||
# named arg style calling???
|
||||
# XD .. wtf?
|
||||
# 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
|
||||
|
|
|
@ -40,6 +40,8 @@ from typing import (
|
|||
import trio
|
||||
from tractor._state import current_actor
|
||||
from tractor.log import get_logger
|
||||
# from ._beg import collapse_eg
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tractor import ActorNursery
|
||||
|
@ -111,17 +113,19 @@ async def gather_contexts(
|
|||
None,
|
||||
]:
|
||||
'''
|
||||
Concurrently enter a sequence of async context managers (acms),
|
||||
each from a separate `trio` task and deliver the unwrapped
|
||||
`yield`-ed values in the same order once all managers have entered.
|
||||
Concurrently enter a sequence of async context managers (`acm`s),
|
||||
each scheduled in a separate `trio.Task` and deliver their
|
||||
unwrapped `yield`-ed values in the same order once all `@acm`s
|
||||
in every task have entered.
|
||||
|
||||
On exit, all acms are subsequently and concurrently exited.
|
||||
On exit, all `acm`s are subsequently and concurrently exited with
|
||||
**no order guarantees**.
|
||||
|
||||
This function is somewhat similar to a batch of non-blocking
|
||||
calls to `contextlib.AsyncExitStack.enter_async_context()`
|
||||
(inside a loop) *in combo with* a `asyncio.gather()` to get the
|
||||
`.__aenter__()`-ed values, except the managers are both
|
||||
concurrently entered and exited and *cancellation just works*(R).
|
||||
concurrently entered and exited and *cancellation-just-works™*.
|
||||
|
||||
'''
|
||||
seed: int = id(mngrs)
|
||||
|
@ -141,16 +145,20 @@ async def gather_contexts(
|
|||
if not mngrs:
|
||||
raise ValueError(
|
||||
'`.trionics.gather_contexts()` input mngrs is empty?\n'
|
||||
'\n'
|
||||
'Did try to use inline generator syntax?\n'
|
||||
'Use a non-lazy iterator or sequence type intead!'
|
||||
'Use a non-lazy iterator or sequence-type intead!\n'
|
||||
)
|
||||
|
||||
async with trio.open_nursery(
|
||||
strict_exception_groups=False,
|
||||
# ^XXX^ TODO? soo roll our own then ??
|
||||
# -> since we kinda want the "if only one `.exception` then
|
||||
# just raise that" interface?
|
||||
) as tn:
|
||||
async with (
|
||||
# collapse_eg(),
|
||||
trio.open_nursery(
|
||||
# strict_exception_groups=False,
|
||||
# ^XXX^ TODO? soo roll our own then ??
|
||||
# -> since we kinda want the "if only one `.exception` then
|
||||
# just raise that" interface?
|
||||
) as tn,
|
||||
):
|
||||
for mngr in mngrs:
|
||||
tn.start_soon(
|
||||
_enter_and_wait,
|
||||
|
@ -167,7 +175,7 @@ async def gather_contexts(
|
|||
try:
|
||||
yield tuple(unwrapped.values())
|
||||
finally:
|
||||
# NOTE: this is ABSOLUTELY REQUIRED to avoid
|
||||
# XXX NOTE: this is ABSOLUTELY REQUIRED to avoid
|
||||
# the following wacky bug:
|
||||
# <tractorbugurlhere>
|
||||
parent_exit.set()
|
||||
|
|
Loading…
Reference in New Issue