From c5091afa380ad11d7eb082dc8f68463b3da50b3f Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Thu, 13 Oct 2022 15:17:26 -0400 Subject: [PATCH] Always restore the `trio` SIGINT handler Pretty sure this is the final touch to alleviate all our debug lock headaches! Instead of trying to revert to the "last" handler (as `pdb` does internally in the stdlib) we always just revert to the handler `trio` registers during startup. Further this seems to allow cancelling the root-side locking task if it's detected as stale IFF we only do this when the root actor is in a "no more IPC peers" state. Deatz: - always `._debug.Lock._trio_handler` as the `trio` version, not some last used handler to make sure we're getting the ctrl-c handling we want when not in debug mode. - assign the trio handler in `open_root_actor()` `._runtime._async_main()` to be sure it's applied in subactors as well as the root. - only do debug lock blocking and root-side-locking-task cancels when a "no peers" condition is detected in the root actor: i.e. no IPC channels are detected by the root meaning it's impossible any actor has a sane lock-state ongoing for debug mode. --- tractor/_debug.py | 14 +++++++------- tractor/_root.py | 8 ++++++-- tractor/_runtime.py | 32 +++++++++++++++++++------------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/tractor/_debug.py b/tractor/_debug.py index 819dd9a..8fded72 100644 --- a/tractor/_debug.py +++ b/tractor/_debug.py @@ -107,16 +107,16 @@ class Lock: @classmethod def shield_sigint(cls): cls._orig_sigint_handler = signal.signal( - signal.SIGINT, - shield_sigint, - ) + signal.SIGINT, + shield_sigint, + ) @classmethod def unshield_sigint(cls): - # always restore (some) sigint handler, either - # the prior or at least ``trio``'s. - orig = cls._orig_sigint_handler or cls._trio_handler - signal.signal(signal.SIGINT, orig) + # always restore ``trio``'s sigint handler. see notes below in + # the pdb factory about the nightmare that is that code swapping + # out the handler when the repl activates... + signal.signal(signal.SIGINT, cls._trio_handler) cls._orig_sigint_handler = None @classmethod diff --git a/tractor/_root.py b/tractor/_root.py index e43121c..cb405f5 100644 --- a/tractor/_root.py +++ b/tractor/_root.py @@ -34,7 +34,11 @@ import warnings from exceptiongroup import BaseExceptionGroup import trio -from ._runtime import Actor, Arbiter, async_main +from ._runtime import ( + Actor, + Arbiter, + async_main, +) from . import _debug from . import _spawn from . import _state @@ -88,7 +92,7 @@ async def open_root_actor( # attempt to retreive ``trio``'s sigint handler and stash it # on our debugger lock state. - _debug.Lock._trio_handler = signal.getsignal(signal.SIGINT) + _debug.Lock._trio_handler = signal.getsignal(signal.SIGINT) # mark top most level process as root actor _state._runtime_vars['_is_root'] = True diff --git a/tractor/_runtime.py b/tractor/_runtime.py index 5a3a693..30ba1f4 100644 --- a/tractor/_runtime.py +++ b/tractor/_runtime.py @@ -25,14 +25,15 @@ from itertools import chain import importlib import importlib.util import inspect -import uuid +import signal +import sys from typing import ( Any, Optional, Union, TYPE_CHECKING, Callable, ) +import uuid from types import ModuleType -import sys import os from contextlib import ExitStack import warnings @@ -709,6 +710,14 @@ class Actor: log.runtime(f"No more channels for {chan.uid}") self._peers.pop(uid, None) + log.runtime(f"Peers is {self._peers}") + + # No more channels to other actors (at all) registered + # as connected. + if not self._peers: + log.runtime("Signalling no more peer channel connections") + self._no_more_peers.set() + # NOTE: block this actor from acquiring the # debugger-TTY-lock since we have no way to know if we # cancelled it and further there is no way to ensure the @@ -722,23 +731,16 @@ class Actor: # if a now stale local task has the TTY lock still # we cancel it to allow servicing other requests for # the lock. + db_cs = pdb_lock._root_local_task_cs_in_debug if ( - pdb_lock._root_local_task_cs_in_debug - and not pdb_lock._root_local_task_cs_in_debug.cancel_called + db_cs + and not db_cs.cancel_called ): log.warning( f'STALE DEBUG LOCK DETECTED FOR {uid}' ) # TODO: figure out why this breaks tests.. - # pdb_lock._root_local_task_cs_in_debug.cancel() - - log.runtime(f"Peers is {self._peers}") - - # No more channels to other actors (at all) registered - # as connected. - if not self._peers: - log.runtime("Signalling no more peer channel connections") - self._no_more_peers.set() + pdb_lock._root_local_task_cs_in_debug.cancel() # XXX: is this necessary (GC should do it)? if chan.connected(): @@ -1229,6 +1231,10 @@ async def async_main( and when cancelled effectively cancels the actor. ''' + # attempt to retreive ``trio``'s sigint handler and stash it + # on our debugger lock state. + _debug.Lock._trio_handler = signal.getsignal(signal.SIGINT) + registered_with_arbiter = False try: