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
|
pass
|
||||||
except BaseExceptionGroup as beg:
|
except BaseExceptionGroup as beg:
|
||||||
# XXX: on windows it seems we may have to expect the group error
|
# 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)
|
assert is_multi_cancelled(beg)
|
||||||
else:
|
else:
|
||||||
trio.run(main)
|
trio.run(main)
|
||||||
|
|
|
@ -1246,55 +1246,6 @@ def unpack_error(
|
||||||
return exc
|
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(
|
def _raise_from_unexpected_msg(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
msg: MsgType,
|
msg: MsgType,
|
||||||
|
|
|
@ -61,9 +61,11 @@ from ._addr import (
|
||||||
mk_uuid,
|
mk_uuid,
|
||||||
wrap_address,
|
wrap_address,
|
||||||
)
|
)
|
||||||
|
from .trionics import (
|
||||||
|
is_multi_cancelled,
|
||||||
|
)
|
||||||
from ._exceptions import (
|
from ._exceptions import (
|
||||||
RuntimeFailure,
|
RuntimeFailure,
|
||||||
is_multi_cancelled,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,10 @@ from ._state import current_actor, is_main_process
|
||||||
from .log import get_logger, get_loglevel
|
from .log import get_logger, get_loglevel
|
||||||
from ._runtime import Actor
|
from ._runtime import Actor
|
||||||
from ._portal import Portal
|
from ._portal import Portal
|
||||||
from ._exceptions import (
|
from .trionics import (
|
||||||
is_multi_cancelled,
|
is_multi_cancelled,
|
||||||
|
)
|
||||||
|
from ._exceptions import (
|
||||||
ContextCancelled,
|
ContextCancelled,
|
||||||
)
|
)
|
||||||
from ._root import (
|
from ._root import (
|
||||||
|
|
|
@ -59,7 +59,7 @@ from tractor._state import (
|
||||||
debug_mode,
|
debug_mode,
|
||||||
)
|
)
|
||||||
from tractor.log import get_logger
|
from tractor.log import get_logger
|
||||||
from tractor._exceptions import (
|
from tractor.trionics import (
|
||||||
is_multi_cancelled,
|
is_multi_cancelled,
|
||||||
)
|
)
|
||||||
from ._trace import (
|
from ._trace import (
|
||||||
|
|
|
@ -38,7 +38,6 @@ from typing import (
|
||||||
import tractor
|
import tractor
|
||||||
from tractor._exceptions import (
|
from tractor._exceptions import (
|
||||||
InternalError,
|
InternalError,
|
||||||
is_multi_cancelled,
|
|
||||||
TrioTaskExited,
|
TrioTaskExited,
|
||||||
TrioCancelled,
|
TrioCancelled,
|
||||||
AsyncioTaskExited,
|
AsyncioTaskExited,
|
||||||
|
@ -59,6 +58,9 @@ from tractor.log import (
|
||||||
# from tractor.msg import (
|
# from tractor.msg import (
|
||||||
# pretty_struct,
|
# pretty_struct,
|
||||||
# )
|
# )
|
||||||
|
from tractor.trionics import (
|
||||||
|
is_multi_cancelled,
|
||||||
|
)
|
||||||
from tractor.trionics._broadcast import (
|
from tractor.trionics._broadcast import (
|
||||||
broadcast_receiver,
|
broadcast_receiver,
|
||||||
BroadcastReceiver,
|
BroadcastReceiver,
|
||||||
|
|
|
@ -32,4 +32,5 @@ from ._broadcast import (
|
||||||
from ._beg import (
|
from ._beg import (
|
||||||
collapse_eg as collapse_eg,
|
collapse_eg as collapse_eg,
|
||||||
maybe_collapse_eg as maybe_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 (
|
from contextlib import (
|
||||||
asynccontextmanager as acm,
|
asynccontextmanager as acm,
|
||||||
)
|
)
|
||||||
|
from typing import (
|
||||||
|
Literal,
|
||||||
|
)
|
||||||
|
|
||||||
|
import trio
|
||||||
|
|
||||||
|
|
||||||
def maybe_collapse_eg(
|
def maybe_collapse_eg(
|
||||||
|
@ -56,3 +61,62 @@ async def collapse_eg():
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
raise beg
|
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
|
import trio
|
||||||
from tractor._state import current_actor
|
from tractor._state import current_actor
|
||||||
from tractor.log import get_logger
|
from tractor.log import get_logger
|
||||||
|
# from ._beg import collapse_eg
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from tractor import ActorNursery
|
from tractor import ActorNursery
|
||||||
|
@ -111,17 +113,19 @@ async def gather_contexts(
|
||||||
None,
|
None,
|
||||||
]:
|
]:
|
||||||
'''
|
'''
|
||||||
Concurrently enter a sequence of async context managers (acms),
|
Concurrently enter a sequence of async context managers (`acm`s),
|
||||||
each from a separate `trio` task and deliver the unwrapped
|
each scheduled in a separate `trio.Task` and deliver their
|
||||||
`yield`-ed values in the same order once all managers have entered.
|
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
|
This function is somewhat similar to a batch of non-blocking
|
||||||
calls to `contextlib.AsyncExitStack.enter_async_context()`
|
calls to `contextlib.AsyncExitStack.enter_async_context()`
|
||||||
(inside a loop) *in combo with* a `asyncio.gather()` to get the
|
(inside a loop) *in combo with* a `asyncio.gather()` to get the
|
||||||
`.__aenter__()`-ed values, except the managers are both
|
`.__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)
|
seed: int = id(mngrs)
|
||||||
|
@ -141,16 +145,20 @@ async def gather_contexts(
|
||||||
if not mngrs:
|
if not mngrs:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'`.trionics.gather_contexts()` input mngrs is empty?\n'
|
'`.trionics.gather_contexts()` input mngrs is empty?\n'
|
||||||
|
'\n'
|
||||||
'Did try to use inline generator syntax?\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(
|
async with (
|
||||||
strict_exception_groups=False,
|
# collapse_eg(),
|
||||||
|
trio.open_nursery(
|
||||||
|
# strict_exception_groups=False,
|
||||||
# ^XXX^ TODO? soo roll our own then ??
|
# ^XXX^ TODO? soo roll our own then ??
|
||||||
# -> since we kinda want the "if only one `.exception` then
|
# -> since we kinda want the "if only one `.exception` then
|
||||||
# just raise that" interface?
|
# just raise that" interface?
|
||||||
) as tn:
|
) as tn,
|
||||||
|
):
|
||||||
for mngr in mngrs:
|
for mngr in mngrs:
|
||||||
tn.start_soon(
|
tn.start_soon(
|
||||||
_enter_and_wait,
|
_enter_and_wait,
|
||||||
|
@ -167,7 +175,7 @@ async def gather_contexts(
|
||||||
try:
|
try:
|
||||||
yield tuple(unwrapped.values())
|
yield tuple(unwrapped.values())
|
||||||
finally:
|
finally:
|
||||||
# NOTE: this is ABSOLUTELY REQUIRED to avoid
|
# XXX NOTE: this is ABSOLUTELY REQUIRED to avoid
|
||||||
# the following wacky bug:
|
# the following wacky bug:
|
||||||
# <tractorbugurlhere>
|
# <tractorbugurlhere>
|
||||||
parent_exit.set()
|
parent_exit.set()
|
||||||
|
|
Loading…
Reference in New Issue