[Python-checkins] r80008 - in python/branches/py3k: Doc/library/os.rst Doc/library/signal.rst Doc/library/subprocess.rst Lib/subprocess.py Lib/test/test_os.py Lib/test/win_console_handler.py Lib/unittest/test/test_break.py Modules/posixmodule.c Modules/signalmodule.c PC/_subprocess.c

brian.curtin python-checkins at python.org
Mon Apr 12 19:16:38 CEST 2010


Author: brian.curtin
Date: Mon Apr 12 19:16:38 2010
New Revision: 80008

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


Added:
   python/branches/py3k/Lib/test/win_console_handler.py   (contents, props changed)
      - copied, changed from r79633, /python/trunk/Lib/test/win_console_handler.py
Modified:
   python/branches/py3k/Doc/library/os.rst
   python/branches/py3k/Doc/library/signal.rst
   python/branches/py3k/Doc/library/subprocess.rst
   python/branches/py3k/Lib/subprocess.py
   python/branches/py3k/Lib/test/test_os.py
   python/branches/py3k/Lib/unittest/test/test_break.py
   python/branches/py3k/Modules/posixmodule.c
   python/branches/py3k/Modules/signalmodule.c
   python/branches/py3k/PC/_subprocess.c

Modified: python/branches/py3k/Doc/library/os.rst
==============================================================================
--- python/branches/py3k/Doc/library/os.rst	(original)
+++ python/branches/py3k/Doc/library/os.rst	Mon Apr 12 19:16:38 2010
@@ -1491,7 +1491,14 @@
 
    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)

Modified: python/branches/py3k/Doc/library/signal.rst
==============================================================================
--- python/branches/py3k/Doc/library/signal.rst	(original)
+++ python/branches/py3k/Doc/library/signal.rst	Mon Apr 12 19:16:38 2010
@@ -74,6 +74,20 @@
    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.

Modified: python/branches/py3k/Doc/library/subprocess.rst
==============================================================================
--- python/branches/py3k/Doc/library/subprocess.rst	(original)
+++ python/branches/py3k/Doc/library/subprocess.rst	Mon Apr 12 19:16:38 2010
@@ -373,8 +373,9 @@
 
    .. 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()

Modified: python/branches/py3k/Lib/subprocess.py
==============================================================================
--- python/branches/py3k/Lib/subprocess.py	(original)
+++ python/branches/py3k/Lib/subprocess.py	Mon Apr 12 19:16:38 2010
@@ -980,6 +980,10 @@
             """
             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")
 

Modified: python/branches/py3k/Lib/test/test_os.py
==============================================================================
--- python/branches/py3k/Lib/test/test_os.py	(original)
+++ python/branches/py3k/Lib/test/test_os.py	Mon Apr 12 19:16:38 2010
@@ -7,9 +7,13 @@
 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 @@
             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 @@
             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 @@
     class Pep383Tests(unittest.TestCase):
         pass
 
+ at 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 @@
         Win32ErrorTests,
         TestInvalidFD,
         PosixUidGidTests,
-        Pep383Tests
+        Pep383Tests,
+        Win32KillTests
     )
 
 if __name__ == "__main__":

Copied: python/branches/py3k/Lib/test/win_console_handler.py (from r79633, /python/trunk/Lib/test/win_console_handler.py)
==============================================================================
--- /python/trunk/Lib/test/win_console_handler.py	(original)
+++ python/branches/py3k/Lib/test/win_console_handler.py	Mon Apr 12 19:16:38 2010
@@ -1,42 +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
-    input()
+"""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

Modified: python/branches/py3k/Lib/unittest/test/test_break.py
==============================================================================
--- python/branches/py3k/Lib/unittest/test/test_break.py	(original)
+++ python/branches/py3k/Lib/unittest/test/test_break.py	Mon Apr 12 19:16:38 2010
@@ -1,6 +1,7 @@
 import gc
 import io
 import os
+import sys
 import signal
 import weakref
 
@@ -8,6 +9,7 @@
 
 
 @unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill")
+ at unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows")
 class TestBreak(unittest.TestCase):
 
     def setUp(self):

Modified: python/branches/py3k/Modules/posixmodule.c
==============================================================================
--- python/branches/py3k/Modules/posixmodule.c	(original)
+++ python/branches/py3k/Modules/posixmodule.c	Mon Apr 12 19:16:38 2010
@@ -4171,6 +4171,53 @@
 }
 #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 @@
 #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__},

Modified: python/branches/py3k/Modules/signalmodule.c
==============================================================================
--- python/branches/py3k/Modules/signalmodule.c	(original)
+++ python/branches/py3k/Modules/signalmodule.c	Mon Apr 12 19:16:38 2010
@@ -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 @@
     	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;

Modified: python/branches/py3k/PC/_subprocess.c
==============================================================================
--- python/branches/py3k/PC/_subprocess.c	(original)
+++ python/branches/py3k/PC/_subprocess.c	Mon Apr 12 19:16:38 2010
@@ -599,5 +599,7 @@
 	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;
 }


More information about the Python-checkins mailing list