forked from goodboy/tractor
				
			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.egs_with_ctx_res_consumption
							parent
							
								
									f6ac0c2eb7
								
							
						
					
					
						commit
						c5091afa38
					
				| 
						 | 
					@ -107,16 +107,16 @@ class Lock:
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def shield_sigint(cls):
 | 
					    def shield_sigint(cls):
 | 
				
			||||||
        cls._orig_sigint_handler = signal.signal(
 | 
					        cls._orig_sigint_handler = signal.signal(
 | 
				
			||||||
                signal.SIGINT,
 | 
					            signal.SIGINT,
 | 
				
			||||||
                shield_sigint,
 | 
					            shield_sigint,
 | 
				
			||||||
            )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def unshield_sigint(cls):
 | 
					    def unshield_sigint(cls):
 | 
				
			||||||
        # always restore (some) sigint handler, either
 | 
					        # always restore ``trio``'s sigint handler. see notes below in
 | 
				
			||||||
        # the prior or at least ``trio``'s.
 | 
					        # the pdb factory about the nightmare that is that code swapping
 | 
				
			||||||
        orig = cls._orig_sigint_handler or cls._trio_handler
 | 
					        # out the handler when the repl activates...
 | 
				
			||||||
        signal.signal(signal.SIGINT, orig)
 | 
					        signal.signal(signal.SIGINT, cls._trio_handler)
 | 
				
			||||||
        cls._orig_sigint_handler = None
 | 
					        cls._orig_sigint_handler = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,11 @@ import warnings
 | 
				
			||||||
from exceptiongroup import BaseExceptionGroup
 | 
					from exceptiongroup import BaseExceptionGroup
 | 
				
			||||||
import trio
 | 
					import trio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ._runtime import Actor, Arbiter, async_main
 | 
					from ._runtime import (
 | 
				
			||||||
 | 
					    Actor,
 | 
				
			||||||
 | 
					    Arbiter,
 | 
				
			||||||
 | 
					    async_main,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from . import _debug
 | 
					from . import _debug
 | 
				
			||||||
from . import _spawn
 | 
					from . import _spawn
 | 
				
			||||||
from . import _state
 | 
					from . import _state
 | 
				
			||||||
| 
						 | 
					@ -88,7 +92,7 @@ async def open_root_actor(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # attempt to retreive ``trio``'s sigint handler and stash it
 | 
					    # attempt to retreive ``trio``'s sigint handler and stash it
 | 
				
			||||||
    # on our debugger lock state.
 | 
					    # 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
 | 
					    # mark top most level process as root actor
 | 
				
			||||||
    _state._runtime_vars['_is_root'] = True
 | 
					    _state._runtime_vars['_is_root'] = True
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,14 +25,15 @@ from itertools import chain
 | 
				
			||||||
import importlib
 | 
					import importlib
 | 
				
			||||||
import importlib.util
 | 
					import importlib.util
 | 
				
			||||||
import inspect
 | 
					import inspect
 | 
				
			||||||
import uuid
 | 
					import signal
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
from typing import (
 | 
					from typing import (
 | 
				
			||||||
    Any, Optional,
 | 
					    Any, Optional,
 | 
				
			||||||
    Union, TYPE_CHECKING,
 | 
					    Union, TYPE_CHECKING,
 | 
				
			||||||
    Callable,
 | 
					    Callable,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
from types import ModuleType
 | 
					from types import ModuleType
 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
from contextlib import ExitStack
 | 
					from contextlib import ExitStack
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
| 
						 | 
					@ -709,6 +710,14 @@ class Actor:
 | 
				
			||||||
                log.runtime(f"No more channels for {chan.uid}")
 | 
					                log.runtime(f"No more channels for {chan.uid}")
 | 
				
			||||||
                self._peers.pop(uid, None)
 | 
					                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
 | 
					                # NOTE: block this actor from acquiring the
 | 
				
			||||||
                # debugger-TTY-lock since we have no way to know if we
 | 
					                # debugger-TTY-lock since we have no way to know if we
 | 
				
			||||||
                # cancelled it and further there is no way to ensure the
 | 
					                # 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
 | 
					                    # if a now stale local task has the TTY lock still
 | 
				
			||||||
                    # we cancel it to allow servicing other requests for
 | 
					                    # we cancel it to allow servicing other requests for
 | 
				
			||||||
                    # the lock.
 | 
					                    # the lock.
 | 
				
			||||||
 | 
					                    db_cs = pdb_lock._root_local_task_cs_in_debug
 | 
				
			||||||
                    if (
 | 
					                    if (
 | 
				
			||||||
                        pdb_lock._root_local_task_cs_in_debug
 | 
					                        db_cs
 | 
				
			||||||
                        and not pdb_lock._root_local_task_cs_in_debug.cancel_called
 | 
					                        and not db_cs.cancel_called
 | 
				
			||||||
                    ):
 | 
					                    ):
 | 
				
			||||||
                        log.warning(
 | 
					                        log.warning(
 | 
				
			||||||
                            f'STALE DEBUG LOCK DETECTED FOR {uid}'
 | 
					                            f'STALE DEBUG LOCK DETECTED FOR {uid}'
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        # TODO: figure out why this breaks tests..
 | 
					                        # TODO: figure out why this breaks tests..
 | 
				
			||||||
                        # pdb_lock._root_local_task_cs_in_debug.cancel()
 | 
					                        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()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # XXX: is this necessary (GC should do it)?
 | 
					            # XXX: is this necessary (GC should do it)?
 | 
				
			||||||
            if chan.connected():
 | 
					            if chan.connected():
 | 
				
			||||||
| 
						 | 
					@ -1229,6 +1231,10 @@ async def async_main(
 | 
				
			||||||
    and when cancelled effectively cancels the actor.
 | 
					    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
 | 
					    registered_with_arbiter = False
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue