Port #1220212 (os.kill for Win32) to py3k.

This commit is contained in:
Brian Curtin 2010-04-12 17:16:38 +00:00
parent b2416e54b1
commit eb24d7498f
10 changed files with 200 additions and 6 deletions

View File

@ -1491,7 +1491,14 @@ written in Python, such as a mail server's external command delivery program.
Send signal *sig* to the process *pid*. Constants for the specific signals
available on the host platform are defined in the :mod:`signal` module.
Availability: Unix.
Windows: The :data:`signal.CTRL_C_EVENT` and
:data:`signal.CTRL_BREAK_EVENT` signals are special signals which can
only be sent to console processes which share a common console window,
e.g., some subprocesses. Any other value for *sig* will cause the process
to be unconditionally killed by the TerminateProcess API, and the exit code
will be set to *sig*. The Windows version of :func:`kill` additionally takes
process handles to be killed.
.. function:: killpg(pgid, sig)

View File

@ -74,6 +74,20 @@ The variables defined in the :mod:`signal` module are:
the system are defined by this module.
.. data:: CTRL_C_EVENT
The signal corresponding to the CTRL+C keystroke event.
Availability: Windows.
.. data:: CTRL_BREAK_EVENT
The signal corresponding to the CTRL+BREAK keystroke event.
Availability: Windows.
.. data:: NSIG
One more than the number of the highest signal number.

View File

@ -373,8 +373,9 @@ Instances of the :class:`Popen` class have the following methods:
.. note::
On Windows only SIGTERM is supported so far. It's an alias for
:meth:`terminate`.
On Windows, SIGTERM is an alias for :meth:`terminate`. CTRL_C_EVENT and
CTRL_BREAK_EVENT can be sent to processes started with a `creationflags`
parameter which includes `CREATE_NEW_PROCESS_GROUP`.
.. method:: Popen.terminate()

View File

@ -980,6 +980,10 @@ class Popen(object):
"""
if sig == signal.SIGTERM:
self.terminate()
elif sig == signal.CTRL_C_EVENT:
os.kill(self.pid, signal.CTRL_C_EVENT)
elif sig == signal.CTRL_BREAK_EVENT:
os.kill(self.pid, signal.CTRL_BREAK_EVENT)
else:
raise ValueError("Only SIGTERM is supported on Windows")

View File

@ -7,9 +7,13 @@ import errno
import unittest
import warnings
import sys
import signal
import subprocess
import time
import shutil
from test import support
# Tests creating TESTFN
class FileTests(unittest.TestCase):
def setUp(self):
@ -739,7 +743,6 @@ if sys.platform != 'win32':
def test_setreuid_neg1(self):
# Needs to accept -1. We run this in a subprocess to avoid
# altering the test runner's process state (issue8045).
import subprocess
subprocess.check_call([
sys.executable, '-c',
'import os,sys;os.setreuid(-1,-1);sys.exit(0)'])
@ -754,7 +757,6 @@ if sys.platform != 'win32':
def test_setregid_neg1(self):
# Needs to accept -1. We run this in a subprocess to avoid
# altering the test runner's process state (issue8045).
import subprocess
subprocess.check_call([
sys.executable, '-c',
'import os,sys;os.setregid(-1,-1);sys.exit(0)'])
@ -798,6 +800,63 @@ else:
class Pep383Tests(unittest.TestCase):
pass
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
class Win32KillTests(unittest.TestCase):
def _kill(self, sig, *args):
# Send a subprocess a signal (or in some cases, just an int to be
# the return value)
proc = subprocess.Popen(*args)
os.kill(proc.pid, sig)
self.assertEqual(proc.wait(), sig)
def test_kill_sigterm(self):
# SIGTERM doesn't mean anything special, but make sure it works
self._kill(signal.SIGTERM, [sys.executable])
def test_kill_int(self):
# os.kill on Windows can take an int which gets set as the exit code
self._kill(100, [sys.executable])
def _kill_with_event(self, event, name):
# Run a script which has console control handling enabled.
proc = subprocess.Popen([sys.executable,
os.path.join(os.path.dirname(__file__),
"win_console_handler.py")],
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
# Let the interpreter startup before we send signals. See #3137.
time.sleep(0.5)
os.kill(proc.pid, event)
# proc.send_signal(event) could also be done here.
# Allow time for the signal to be passed and the process to exit.
time.sleep(0.5)
if not proc.poll():
# Forcefully kill the process if we weren't able to signal it.
os.kill(proc.pid, signal.SIGINT)
self.fail("subprocess did not stop on {}".format(name))
@unittest.skip("subprocesses aren't inheriting CTRL+C property")
def test_CTRL_C_EVENT(self):
from ctypes import wintypes
import ctypes
# Make a NULL value by creating a pointer with no argument.
NULL = ctypes.POINTER(ctypes.c_int)()
SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler
SetConsoleCtrlHandler.argtypes = (ctypes.POINTER(ctypes.c_int),
wintypes.BOOL)
SetConsoleCtrlHandler.restype = wintypes.BOOL
# Calling this with NULL and FALSE causes the calling process to
# handle CTRL+C, rather than ignore it. This property is inherited
# by subprocesses.
SetConsoleCtrlHandler(NULL, 0)
self._kill_with_event(signal.CTRL_C_EVENT, "CTRL_C_EVENT")
def test_CTRL_BREAK_EVENT(self):
self._kill_with_event(signal.CTRL_BREAK_EVENT, "CTRL_BREAK_EVENT")
def test_main():
support.run_unittest(
ArgTests,
@ -812,7 +871,8 @@ def test_main():
Win32ErrorTests,
TestInvalidFD,
PosixUidGidTests,
Pep383Tests
Pep383Tests,
Win32KillTests
)
if __name__ == "__main__":

View File

@ -0,0 +1,43 @@
"""Script used to test os.kill on Windows, for issue #1220212
This script is started as a subprocess in test_os and is used to test the
CTRL_C_EVENT and CTRL_BREAK_EVENT signals, which requires a custom handler
to be written into the kill target.
See http://msdn.microsoft.com/en-us/library/ms685049%28v=VS.85%29.aspx for a
similar example in C.
"""
from ctypes import wintypes
import signal
import ctypes
# Function prototype for the handler function. Returns BOOL, takes a DWORD.
HandlerRoutine = wintypes.WINFUNCTYPE(wintypes.BOOL, wintypes.DWORD)
def _ctrl_handler(sig):
"""Handle a sig event and return 0 to terminate the process"""
if sig == signal.CTRL_C_EVENT:
pass
elif sig == signal.CTRL_BREAK_EVENT:
pass
else:
print("UNKNOWN EVENT")
return 0
ctrl_handler = HandlerRoutine(_ctrl_handler)
SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler
SetConsoleCtrlHandler.argtypes = (HandlerRoutine, wintypes.BOOL)
SetConsoleCtrlHandler.restype = wintypes.BOOL
if __name__ == "__main__":
# Add our console control handling function with value 1
if not SetConsoleCtrlHandler(ctrl_handler, 1):
print("Unable to add SetConsoleCtrlHandler")
exit(-1)
# Do nothing but wait for the signal
while True:
pass

View File

@ -1,6 +1,7 @@
import gc
import io
import os
import sys
import signal
import weakref
@ -8,6 +9,7 @@ import unittest
@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill")
@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows")
class TestBreak(unittest.TestCase):
def setUp(self):

View File

@ -4171,6 +4171,53 @@ posix_killpg(PyObject *self, PyObject *args)
}
#endif
#ifdef MS_WINDOWS
PyDoc_STRVAR(win32_kill__doc__,
"kill(pid, sig)\n\n\
Kill a process with a signal.");
static PyObject *
win32_kill(PyObject *self, PyObject *args)
{
PyObject *result, handle_obj;
DWORD pid, sig, err;
HANDLE handle;
if (!PyArg_ParseTuple(args, "kk:kill", &pid, &sig))
return NULL;
/* Console processes which share a common console can be sent CTRL+C or
CTRL+BREAK events, provided they handle said events. */
if (sig == CTRL_C_EVENT || sig == CTRL_BREAK_EVENT) {
if (GenerateConsoleCtrlEvent(sig, pid) == 0) {
err = GetLastError();
PyErr_SetFromWindowsErr(err);
}
else
Py_RETURN_NONE;
}
/* If the signal is outside of what GenerateConsoleCtrlEvent can use,
attempt to open and terminate the process. */
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (handle == NULL) {
err = GetLastError();
return PyErr_SetFromWindowsErr(err);
}
if (TerminateProcess(handle, sig) == 0) {
err = GetLastError();
result = PyErr_SetFromWindowsErr(err);
} else {
Py_INCREF(Py_None);
result = Py_None;
}
CloseHandle(handle);
return result;
}
#endif /* MS_WINDOWS */
#ifdef HAVE_PLOCK
#ifdef HAVE_SYS_LOCK_H
@ -7200,6 +7247,7 @@ static PyMethodDef posix_methods[] = {
#endif /* HAVE_PLOCK */
#ifdef MS_WINDOWS
{"startfile", win32_startfile, METH_VARARGS, win32_startfile__doc__},
{"kill", win32_kill, METH_VARARGS, win32_kill__doc__},
#endif
#ifdef HAVE_SETUID
{"setuid", posix_setuid, METH_VARARGS, posix_setuid__doc__},

View File

@ -7,6 +7,7 @@
#include "intrcheck.h"
#ifdef MS_WINDOWS
#include <Windows.h>
#ifdef HAVE_PROCESS_H
#include <process.h>
#endif
@ -805,6 +806,18 @@ PyInit_signal(void)
PyDict_SetItemString(d, "ItimerError", ItimerError);
#endif
#ifdef CTRL_C_EVENT
x = PyLong_FromLong(CTRL_C_EVENT);
PyDict_SetItemString(d, "CTRL_C_EVENT", x);
Py_DECREF(x);
#endif
#ifdef CTRL_BREAK_EVENT
x = PyLong_FromLong(CTRL_BREAK_EVENT);
PyDict_SetItemString(d, "CTRL_BREAK_EVENT", x);
Py_DECREF(x);
#endif
if (PyErr_Occurred()) {
Py_DECREF(m);
m = NULL;

View File

@ -599,5 +599,7 @@ PyInit__subprocess()
defint(d, "INFINITE", INFINITE);
defint(d, "WAIT_OBJECT_0", WAIT_OBJECT_0);
defint(d, "CREATE_NEW_CONSOLE", CREATE_NEW_CONSOLE);
defint(d, "CREATE_NEW_PROCESS_GROUP", CREATE_NEW_PROCESS_GROUP);
return m;
}