Skip to content
Permalink
Browse files
use CTRL-BREAK to emulate SIGTERM (clean shutdown) on windows
  • Loading branch information
Ximin Luo committed Oct 28, 2013
1 parent a77c3a6 commit 157459e630a415756c7872399ed7f36172ed638d
Showing with 51 additions and 2 deletions.
  1. +1 −1 pyptlib/test/test_util_subproc.py
  2. +6 −0 pyptlib/test/util_subproc_child.py
  3. +44 −1 pyptlib/util/subproc.py
@@ -97,7 +97,7 @@ def test_auto_killall_term(self):
proc.send_signal(signal.SIGTERM)
proc_wait(proc, 3)
self.assertFalse(proc_is_alive(pid), "TERM not handled")
self.assertFalse(proc_is_alive(cid), "TERM not handled")
self.assertFalse(proc_is_alive(cid), "parent did not kill child")

def test_auto_killall_exit(self):
"""Test that auto_killall works on normal exit."""
@@ -16,6 +16,12 @@ def child_default_io(subcmd, *argv):
def child_killall_kill(subcmd, *argv):
signal.signal(signal.SIGINT, hangForever)
signal.signal(signal.SIGTERM, hangForever)
if sys.platform == "win32":
import win32api
def HandlerRoutine(signum):
if signum != signal.CTRL_BREAK_EVENT: return False
return hangForever(signum)
win32api.SetConsoleCtrlHandler(HandlerRoutine, True)
child_default(None)

if __name__ == '__main__':
@@ -15,6 +15,10 @@
import time

mswindows = (sys.platform == "win32")
# By default on windows, python calls TerminateProcess to terminate a child
# process, but semantically this is equivalent to SIGKILL. Set this to True to
# send a Console CTRL-BREAK event instead, which acts more like SIGTERM.
_USE_CTRLBREAK_AS_SIGTERM = True

_CHILD_PROCS = []
# TODO(infinity0): add functionality to detect when any child dies, and
@@ -53,6 +57,16 @@ def __init__(self, *args, **kwargs):
subprocess.Popen.__init__(self, *args, **kwargs)
_CHILD_PROCS.append(self)

if mswindows and _USE_CTRLBREAK_AS_SIGTERM:
def send_signal(self, sig):
if sig == signal.SIGTERM:
self.terminate()
else:
subprocess.Popen.send_signal(self, sig)

def terminate(self):
os.kill(self.pid, signal.CTRL_BREAK_EVENT)

# TODO(infinity0): perhaps replace Popen.std* with wrapped file objects
# that don't buffer readlines() et. al. Currently one must avoid these and
# use while/readline(); see man page for "python -u" for more details.
@@ -162,6 +176,35 @@ def trap_sigint(handler, ignoreNum=0):
handlers.register(handler, ignoreNum)


_SIGTERM_HANDLERS = SignalHandlers()
def trap_sigterm(handler, ignoreNum=0):
"""Register a handler for a TERM signal (Unix) or CTRL-BREAK console event
(Windows).
Successive handlers are cumulative. On Unix, they override any previous
handlers registered with signal.signal(). On Windows, they *do not*
override previous handlers registered with win32api.SetConsoleCtrlHandler().
Args:
handler: a signal handler; see signal.signal() for details. For
cross-platform portability, it should accept None as a valid value
for the sframe (second) parameter.
ignoreNum: number of signals to ignore before activating the handler,
which will be run on all subsequent signals.
"""
handlers = _SIGTERM_HANDLERS
if not (mswindows and _USE_CTRLBREAK_AS_SIGTERM):
handlers.attach_override_unix(signal.SIGTERM)
handlers.register(handler, ignoreNum)

if mswindows and _USE_CTRLBREAK_AS_SIGTERM:
import win32api
def HandlerRoutine(signum):
if signum != signal.CTRL_BREAK_EVENT: return False
return _SIGTERM_HANDLERS.handle(signum)
win32api.SetConsoleCtrlHandler(HandlerRoutine, True)


_isTerminating = False
def killall(cleanup=lambda:None, wait_s=16):
"""Attempt to gracefully terminate all child processes.
@@ -212,5 +255,5 @@ def auto_killall(ignoreNumSigInts=0, *args, **kwargs):
"""
killall_handler = lambda signum, sframe: killall(*args, **kwargs)
trap_sigint(killall_handler, ignoreNumSigInts)
signal.signal(signal.SIGTERM, killall_handler)
trap_sigterm(killall_handler)
atexit.register(killall, *args, **kwargs)

0 comments on commit 157459e

Please sign in to comment.