From 0e310971a3cf0e1f6abbf1e7fe65aac40f4c97f8 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 6 Mar 2024 13:32:04 -0500 Subject: [PATCH] Only `ctx.canccel()` when non-callee ctxc received Since presumably if the ctxc *is* received from the callee side, there's no point in also sending it a cancel request ;) Also, try out shielding the `ctx.cancel()` call inside `Portal.open_context()` since that seems to resolve testing assertions where you'd expect `.cancel_called` to have been set in the case where an inter-peer cancellation is raised by some local ctx-handle and that local ctxc causes other surrounding `.open_context()` blocks to raise and then implicitly call `ctx.cancel()`. However, there's a possible race where the callee side might deliver a ctxc while all that is happening, `._scope.cancel()` gets called and then by the time `ctx.cancel()` is called it raises `trio.Cancelled` instead continuing to set `._cancel_called`? This needs more testing and docs to get the minute details correct .. yet again. --- tractor/_portal.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tractor/_portal.py b/tractor/_portal.py index 8148a5d9..928a8b9c 100644 --- a/tractor/_portal.py +++ b/tractor/_portal.py @@ -42,7 +42,7 @@ from async_generator import asynccontextmanager from .trionics import maybe_open_nursery from .devx import ( # acquire_debug_lock, - # pause, + pause, maybe_wait_for_debugger, ) from ._state import ( @@ -556,7 +556,7 @@ class Portal: # placeholder for any exception raised in the runtime # or by user tasks which cause this context's closure. scope_err: BaseException|None = None - ctxc_from_callee: ContextCancelled|None = None + ctxc_from_callee: ContextCancelled|bool = False try: async with trio.open_nursery() as nurse: @@ -673,8 +673,6 @@ class Portal: # `Nursery.cancel_scope.cancel()`) except ContextCancelled as ctxc: scope_err = ctxc - ctxc_from_callee = ctxc - # XXX TODO XXX: FIX THIS debug_mode BUGGGG!!! # using this code and then resuming the REPL will # cause a SIGINT-ignoring HANG! @@ -686,6 +684,10 @@ class Portal: # # await pause() + # ctxc_from_callee: bool = ( + # ctxc.src_actor_uid == self.chan.uid + # ) + # CASE 2: context was cancelled by local task calling # `.cancel()`, we don't raise and the exit block should # exit silently. @@ -696,6 +698,7 @@ class Portal: and ctxc.canceller == self.actor.uid ): + ctxc_from_callee = True log.cancel( f'Context (cid=[{ctx.cid[-6:]}..] cancelled gracefully with:\n' f'{ctxc}' @@ -755,6 +758,7 @@ class Portal: f'{caller_err}\n' ) + # TODO: does this need to be shielded? if debug_mode(): # async with acquire_debug_lock(self.actor.uid): # pass @@ -771,12 +775,14 @@ class Portal: 'Calling `ctx.cancel()`!\n' ) - # we don't need to cancel the callee if it already # told us it's cancelled ;p - if ctxc_from_callee is None: + if not ctxc_from_callee: + log.critical('SENDING CANCEL') try: - await ctx.cancel() + # await pause(shield=True) + with trio.CancelScope(shield=True): + await ctx.cancel() except ( trio.BrokenResourceError, trio.ClosedResourceError,