forked from goodboy/tractor
1
0
Fork 0

Fix hard kill in debug mode; only do it when debug lock is empty

ctx_debugger_from_hardening
Tyler Goodlet 2021-06-25 20:52:08 -04:00
parent 3412d344e2
commit 1ec1743c48
1 changed files with 47 additions and 23 deletions

View File

@ -28,6 +28,7 @@ from ._state import (
is_root_process, is_root_process,
_runtime_vars, _runtime_vars,
) )
from ._debug import _global_actor_in_debug
from .log import get_logger from .log import get_logger
from ._portal import Portal from ._portal import Portal
@ -154,6 +155,26 @@ async def cancel_on_completion(
# cancel the process now that we have a final result # cancel the process now that we have a final result
await portal.cancel_actor() await portal.cancel_actor()
async def do_hard_kill(
proc: trio.Process,
) -> None:
# NOTE: this timeout used to do nothing since we were shielding
# the ``.wait()`` inside ``new_proc()`` which will pretty much
# never release until the process exits, now it acts as
# a hard-kill time ultimatum.
with trio.move_on_after(3) as cs:
# NOTE: This ``__aexit__()`` shields internally.
async with proc: # calls ``trio.Process.aclose()``
log.debug(f"Terminating {proc}")
if cs.cancelled_caught:
# XXX: should pretty much never get here unless we have
# to move the bits from ``proc.__aexit__()`` out and
# into here.
log.critical(f"HARD KILLING {proc}")
proc.kill()
@asynccontextmanager @asynccontextmanager
async def spawn_subactor( async def spawn_subactor(
@ -201,33 +222,31 @@ async def spawn_subactor(
# root-processe's ``trio.Process.aclose()`` can clobber # root-processe's ``trio.Process.aclose()`` can clobber
# any existing debugger session so we avoid # any existing debugger session so we avoid
and _runtime_vars['_debug_mode'] and _runtime_vars['_debug_mode']
and _global_actor_in_debug is not None
): ):
# XXX: this is ``trio.Process.aclose()`` minus # XXX: this is ``trio.Process.aclose()`` MINUS the
# the std-streams pre-closing steps and ``Process.kill()`` # std-streams pre-closing steps inside ``proc.__aexit__()``
# calls. # (see below) which incluses a ``Process.kill()`` call
log.critical(
"Root process tty is locked in debug mode by "
f"{_global_actor_in_debug}. If the console is hanging, you "
"may need to trigger a KBI to kill any "
"not-fully-initialized" " subprocesses and allow errors "
"from `trio` to propagate"
)
try: try:
# one more graceful wait try can can be cancelled by KBI
# sent by user.
await proc.wait() await proc.wait()
finally: finally:
if proc.returncode is None: if proc.returncode is None:
# XXX: skip this when in debug and a session might # with trio.CancelScope(shield=True):
# still be live await do_hard_kill(proc)
# proc.kill()
with trio.CancelScope(shield=True):
await proc.wait()
else: else:
# NOTE: this timeout used to do nothing since we were shielding # with trio.CancelScope(shield=True):
# the ``.wait()`` inside ``new_proc()`` which will pretty much await do_hard_kill(proc)
# never release until the process exits, now it acts as
# a hard-kill time ultimatum.
with trio.move_on_after(3) as cs:
# NOTE: This ``__aexit__()`` shields internally.
async with proc: # calls ``trio.Process.aclose()``
log.debug(f"Terminating {proc}")
if cs.cancelled_caught:
log.critical(f"HARD KILLING {proc}")
proc.kill()
async def new_proc( async def new_proc(
@ -304,9 +323,14 @@ async def new_proc(
# reaping more stringently without the shield # reaping more stringently without the shield
# we used to have below... # we used to have below...
# always "hard" join sub procs:
# no actor zombies allowed
# with trio.CancelScope(shield=True): # with trio.CancelScope(shield=True):
# async with proc:
# Always "hard" join sub procs since no actor zombies
# are allowed!
# this is a "light" (cancellable) join, the hard join is
# in the enclosing scope (see above).
await proc.wait() await proc.wait()
log.debug(f"Joined {proc}") log.debug(f"Joined {proc}")