Adjust ep-masking-suite for the real-use-case

Namely that the more common-and-pertinent case is when
a `@context`-ep-fn contains the `finally`-footgun but without
a surrounding embedded `tn` (which currently still requires its own
scope embedded `trionics.maybe_raise_from_masking_exc()`) which can't
be compensated-for by `._rpc._invoke()` easily. Instead the test is
composed where the `._invoke()`-internal `tn` is the machinery being
addressed in terms of masking user-code excs with `trio.Cancelled`.

Deats,
- rename the test -> `test_unmasked_remote_exc` to reflect what the
  runtime should actually be addressing/solving.
- drop the embedded `tn` from `sleep_n_chkpt_in_finally()` (for now)
  since that case can't currently easily be addressed without the user
  code using its own `trionics.maybe_raise_from_masking_exc()` inside
  the nursery scope.
- as such drop all `tn` related params/logic/usage from the ep.
- add in a `Cancelled` handler block which checks for RTE masking and
  always prints the occurrence loudly.

Follow up,
- obvi this suite will currently fail until the appropriate adjustment
  is made to `._rpc._invoke()` to do the unmasking; coming next.
- we probably still need a case with an embedded user `tn` where if
  the default strict-eg mode is used then a ctxc from the parent might
  cause a non-graceful `Context.cancel()` outcome?
 |_since the embedded user-`tn` will raise
   `ExceptionGroup[trio.Cancelled]` upward despite the parent nursery's
   scope being the canceller, or will a `collapse_eg()` inside the
   `._invoke()` scope handle this as well?
moar_eg_smoothing
Tyler Goodlet 2025-06-15 19:18:30 -04:00
parent 86346c27e8
commit bad42734db
1 changed files with 65 additions and 87 deletions

View File

@ -33,10 +33,6 @@ including,
whether a `Context` task should raise `ContextCancelled` (ctx). whether a `Context` task should raise `ContextCancelled` (ctx).
''' '''
# from contextlib import (
# asynccontextmanager as acm,
# )
import pytest import pytest
import trio import trio
import tractor import tractor
@ -46,23 +42,19 @@ from tractor import ( # typing
Context, Context,
ContextCancelled, ContextCancelled,
) )
# from tractor._testing import (
# tractor_test,
# expect_ctxc,
# )
@tractor.context @tractor.context
async def sleep_n_chkpt_in_finally( async def sleep_n_chkpt_in_finally(
ctx: Context, ctx: Context,
sleep_n_raise: bool, sleep_n_raise: bool,
chld_raise_delay: float, chld_raise_delay: float,
chld_finally_delay: float, chld_finally_delay: float,
rent_cancels: bool, rent_cancels: bool,
rent_ctxc_delay: float, rent_ctxc_delay: float,
gto_task: bool = False,
tn_cancels: bool = False,
expect_exc: str|None = None, expect_exc: str|None = None,
) -> None: ) -> None:
@ -82,10 +74,10 @@ async def sleep_n_chkpt_in_finally(
`trio.Cancelled` to signal cancellation on each side of an IPC `Context`, `trio.Cancelled` to signal cancellation on each side of an IPC `Context`,
the footgun issue can compound itself as demonstrated in this suite.. the footgun issue can compound itself as demonstrated in this suite..
Here are some edge cases codified with "sclang" syntax. Here are some edge cases codified with our WIP "sclang" syntax
Note that the parent/child relationship is just a pragmatic (note the parent(rent)/child(chld) naming here is just
choice, these cases can occurr regardless of the supervision pragmatism, generally these most of these cases can occurr
hiearchy, regardless of the distributed-task's supervision hiearchy),
- rent c)=> chld.raises-then-taskc-in-finally - rent c)=> chld.raises-then-taskc-in-finally
|_ chld's body raises an `exc: BaseException`. |_ chld's body raises an `exc: BaseException`.
@ -105,15 +97,6 @@ async def sleep_n_chkpt_in_finally(
) )
berr: BaseException|None = None berr: BaseException|None = None
async with (
tractor.trionics.collapse_eg(
# raise_from_src=True, # to show orig eg
),
trio.open_nursery() as tn
):
if gto_task:
tn.start_soon(trio.sleep_forever())
try: try:
if not sleep_n_raise: if not sleep_n_raise:
await trio.sleep_forever() await trio.sleep_forever()
@ -158,9 +141,6 @@ async def sleep_n_chkpt_in_finally(
with trio.CancelScope(shield=True): with trio.CancelScope(shield=True):
await trio.sleep(chld_finally_delay) await trio.sleep(chld_finally_delay)
if tn_cancels:
tn.cancel_scope.cancel()
# !!XXX this will raise `trio.Cancelled` which # !!XXX this will raise `trio.Cancelled` which
# will mask the RTE from above!!! # will mask the RTE from above!!!
# #
@ -190,7 +170,6 @@ async def sleep_n_chkpt_in_finally(
expect_exc='Cancelled', expect_exc='Cancelled',
rent_cancels=True, rent_cancels=True,
rent_ctxc_delay=0.1, rent_ctxc_delay=0.1,
tn_cancels=True,
), ),
dict( dict(
sleep_n_raise='RuntimeError', sleep_n_raise='RuntimeError',
@ -199,12 +178,11 @@ async def sleep_n_chkpt_in_finally(
expect_exc='RuntimeError', expect_exc='RuntimeError',
rent_cancels=False, rent_cancels=False,
rent_ctxc_delay=0.1, rent_ctxc_delay=0.1,
tn_cancels=False,
), ),
], ],
ids=lambda item: f'chld_callspec={item!r}' ids=lambda item: f'chld_callspec={item!r}'
) )
def test_masked_taskc_with_taskc_still_is_contx( def test_unmasked_remote_exc(
debug_mode: bool, debug_mode: bool,
chld_callspec: dict, chld_callspec: dict,
tpt_proto: str, tpt_proto: str,