Skip to content

Commit

Permalink
Fix process termination for Windows
Browse files Browse the repository at this point in the history
SIGKILL doesn't exist on Windows, so Honcho was broken on that platform.
This refactors Manager and Env so that signal.SIGKILL is only referenced
on POSIX platforms.
  • Loading branch information
nickstenning committed Feb 7, 2015
1 parent f3e1037 commit 3f72aad
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 24 deletions.
31 changes: 22 additions & 9 deletions honcho/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import shlex
import os
import re
import signal

from . import compat

Expand All @@ -21,17 +22,29 @@ def now(self):
return datetime.datetime.now()

if compat.ON_WINDOWS:
# Shamelessly cribbed from
# https://docs.python.org/2/faq/windows.html#how-do-i-emulate-os-kill-in-windows
def killpg(self, pid, signum=None):
"""kill function for Win32"""
kernel32 = ctypes.windll.kernel32
handle = kernel32.OpenProcess(1, 0, pid)
return (0 != kernel32.TerminateProcess(handle, 0))
def terminate(self, pid):
PROCESS_TERMINATE = 1
handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE,
False,
pid)
ctypes.windll.kernel32.TerminateProcess(handle, -1)
ctypes.windll.kernel32.CloseHandle(handle)
else:
def killpg(self, pid, signum=None):
def terminate(self, pid):
try:
os.killpg(pid, signum)
os.killpg(pid, signal.SIGTERM)
except OSError as e:
if e.errno not in [errno.EPERM, errno.ESRCH]:
raise

if compat.ON_WINDOWS:
def kill(self, pid):
# There's no SIGKILL on Win32...
self.terminate(pid)
else:
def kill(self, pid):
try:
os.killpg(pid, signal.SIGKILL)
except OSError as e:
if e.errno not in [errno.EPERM, errno.ESRCH]:
raise
Expand Down
25 changes: 11 additions & 14 deletions honcho/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
'name': 'SIGTERM',
'rc': 143,
},
signal.SIGKILL: {
'name': 'SIGKILL',
}
}
SYSTEM_PRINTER_NAME = 'system'

Expand Down Expand Up @@ -127,7 +124,7 @@ def _terminate(signum, frame):

if exit_start is not None:
# If we've been in this loop for more than KILL_WAIT seconds,
# it's time to SIGKILL all remaining children.
# it's time to kill all remaining children.
waiting = self._env.now() - exit_start
if waiting > datetime.timedelta(seconds=KILL_WAIT):
self.kill()
Expand All @@ -139,20 +136,16 @@ def terminate(self):
if self._terminating:
return
self._terminating = True
self._killall(signal.SIGTERM)
self._killall()

def kill(self):
"""
Kill all processes managed by this ProcessManager.
"""
self._killall(signal.SIGKILL)
self._killall(force=True)

def _kill(self, pid, signum=None):
"""Kill a single process ID"""
self._env.killpg(pid, signum)

def _killall(self, signum):
"""Kill all remaining processes with the specified signal"""
def _killall(self, force=False):
"""Kill all remaining processes, forcefully if requested."""
for_termination = []

for n, p in iteritems(self._processes):
Expand All @@ -161,9 +154,13 @@ def _killall(self, signum):

for n in for_termination:
p = self._processes[n]
signame = 'SIGKILL' if force else 'SIGTERM'
self._system_print("sending %s to %s (pid %s)\n" %
(SIGNALS[signum]['name'], n, p['pid']))
self._kill(p['pid'], signum)
(signame, n, p['pid']))
if force:
self._env.kill(p['pid'])
else:
self._env.terminate(p['pid'])

def _start(self):
for name, p in self._processes.items():
Expand Down
5 changes: 4 additions & 1 deletion honcho/test/unit/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ class FakeEnv(object):
def now(self):
return datetime.datetime(2012, 8, 11, 12, 42)

def killpg(self, pid, sig=None):
def terminate(self, pid):
pass

def kill(self, pid):
pass


Expand Down

0 comments on commit 3f72aad

Please sign in to comment.