forked from goodboy/tractor
Fix hard kill in debug mode; only do it when debug lock is empty
parent
7ecad76adf
commit
988574a456
|
@ -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}")
|
||||||
|
|
Loading…
Reference in New Issue