Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eb24d74

Browse files
committed
Port #1220212 (os.kill for Win32) to py3k.
1 parent b2416e5 commit eb24d74

10 files changed

Lines changed: 200 additions & 6 deletions

File tree

Doc/library/os.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1491,7 +1491,14 @@ written in Python, such as a mail server's external command delivery program.
14911491

14921492
Send signal *sig* to the process *pid*. Constants for the specific signals
14931493
available on the host platform are defined in the :mod:`signal` module.
1494-
Availability: Unix.
1494+
1495+
Windows: The :data:`signal.CTRL_C_EVENT` and
1496+
:data:`signal.CTRL_BREAK_EVENT` signals are special signals which can
1497+
only be sent to console processes which share a common console window,
1498+
e.g., some subprocesses. Any other value for *sig* will cause the process
1499+
to be unconditionally killed by the TerminateProcess API, and the exit code
1500+
will be set to *sig*. The Windows version of :func:`kill` additionally takes
1501+
process handles to be killed.
14951502

14961503

14971504
.. function:: killpg(pgid, sig)

Doc/library/signal.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@ The variables defined in the :mod:`signal` module are:
7474
the system are defined by this module.
7575

7676

77+
.. data:: CTRL_C_EVENT
78+
79+
The signal corresponding to the CTRL+C keystroke event.
80+
81+
Availability: Windows.
82+
83+
84+
.. data:: CTRL_BREAK_EVENT
85+
86+
The signal corresponding to the CTRL+BREAK keystroke event.
87+
88+
Availability: Windows.
89+
90+
7791
.. data:: NSIG
7892

7993
One more than the number of the highest signal number.

Doc/library/subprocess.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,9 @@ Instances of the :class:`Popen` class have the following methods:
373373

374374
.. note::
375375

376-
On Windows only SIGTERM is supported so far. It's an alias for
377-
:meth:`terminate`.
376+
On Windows, SIGTERM is an alias for :meth:`terminate`. CTRL_C_EVENT and
377+
CTRL_BREAK_EVENT can be sent to processes started with a `creationflags`
378+
parameter which includes `CREATE_NEW_PROCESS_GROUP`.
378379

379380

380381
.. method:: Popen.terminate()

Lib/subprocess.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,10 @@ def send_signal(self, sig):
980980
"""
981981
if sig == signal.SIGTERM:
982982
self.terminate()
983+
elif sig == signal.CTRL_C_EVENT:
984+
os.kill(self.pid, signal.CTRL_C_EVENT)
985+
elif sig == signal.CTRL_BREAK_EVENT:
986+
os.kill(self.pid, signal.CTRL_BREAK_EVENT)
983987
else:
984988
raise ValueError("Only SIGTERM is supported on Windows")
985989

Lib/test/test_os.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
import unittest
88
import warnings
99
import sys
10+
import signal
11+
import subprocess
12+
import time
1013
import shutil
1114
from test import support
1215

16+
1317
# Tests creating TESTFN
1418
class FileTests(unittest.TestCase):
1519
def setUp(self):
@@ -739,7 +743,6 @@ def test_setreuid(self):
739743
def test_setreuid_neg1(self):
740744
# Needs to accept -1. We run this in a subprocess to avoid
741745
# altering the test runner's process state (issue8045).
742-
import subprocess
743746
subprocess.check_call([
744747
sys.executable, '-c',
745748
'import os,sys;os.setreuid(-1,-1);sys.exit(0)'])
@@ -754,7 +757,6 @@ def test_setregid(self):
754757
def test_setregid_neg1(self):
755758
# Needs to accept -1. We run this in a subprocess to avoid
756759
# altering the test runner's process state (issue8045).
757-
import subprocess
758760
subprocess.check_call([
759761
sys.executable, '-c',
760762
'import os,sys;os.setregid(-1,-1);sys.exit(0)'])
@@ -798,6 +800,63 @@ class PosixUidGidTests(unittest.TestCase):
798800
class Pep383Tests(unittest.TestCase):
799801
pass
800802

803+
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
804+
class Win32KillTests(unittest.TestCase):
805+
def _kill(self, sig, *args):
806+
# Send a subprocess a signal (or in some cases, just an int to be
807+
# the return value)
808+
proc = subprocess.Popen(*args)
809+
os.kill(proc.pid, sig)
810+
self.assertEqual(proc.wait(), sig)
811+
812+
def test_kill_sigterm(self):
813+
# SIGTERM doesn't mean anything special, but make sure it works
814+
self._kill(signal.SIGTERM, [sys.executable])
815+
816+
def test_kill_int(self):
817+
# os.kill on Windows can take an int which gets set as the exit code
818+
self._kill(100, [sys.executable])
819+
820+
def _kill_with_event(self, event, name):
821+
# Run a script which has console control handling enabled.
822+
proc = subprocess.Popen([sys.executable,
823+
os.path.join(os.path.dirname(__file__),
824+
"win_console_handler.py")],
825+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
826+
# Let the interpreter startup before we send signals. See #3137.
827+
time.sleep(0.5)
828+
os.kill(proc.pid, event)
829+
# proc.send_signal(event) could also be done here.
830+
# Allow time for the signal to be passed and the process to exit.
831+
time.sleep(0.5)
832+
if not proc.poll():
833+
# Forcefully kill the process if we weren't able to signal it.
834+
os.kill(proc.pid, signal.SIGINT)
835+
self.fail("subprocess did not stop on {}".format(name))
836+
837+
@unittest.skip("subprocesses aren't inheriting CTRL+C property")
838+
def test_CTRL_C_EVENT(self):
839+
from ctypes import wintypes
840+
import ctypes
841+
842+
# Make a NULL value by creating a pointer with no argument.
843+
NULL = ctypes.POINTER(ctypes.c_int)()
844+
SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler
845+
SetConsoleCtrlHandler.argtypes = (ctypes.POINTER(ctypes.c_int),
846+
wintypes.BOOL)
847+
SetConsoleCtrlHandler.restype = wintypes.BOOL
848+
849+
# Calling this with NULL and FALSE causes the calling process to
850+
# handle CTRL+C, rather than ignore it. This property is inherited
851+
# by subprocesses.
852+
SetConsoleCtrlHandler(NULL, 0)
853+
854+
self._kill_with_event(signal.CTRL_C_EVENT, "CTRL_C_EVENT")
855+
856+
def test_CTRL_BREAK_EVENT(self):
857+
self._kill_with_event(signal.CTRL_BREAK_EVENT, "CTRL_BREAK_EVENT")
858+
859+
801860
def test_main():
802861
support.run_unittest(
803862
ArgTests,
@@ -812,7 +871,8 @@ def test_main():
812871
Win32ErrorTests,
813872
TestInvalidFD,
814873
PosixUidGidTests,
815-
Pep383Tests
874+
Pep383Tests,
875+
Win32KillTests
816876
)
817877

818878
if __name__ == "__main__":

Lib/test/win_console_handler.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Script used to test os.kill on Windows, for issue #1220212
2+
3+
This script is started as a subprocess in test_os and is used to test the
4+
CTRL_C_EVENT and CTRL_BREAK_EVENT signals, which requires a custom handler
5+
to be written into the kill target.
6+
7+
See http://msdn.microsoft.com/en-us/library/ms685049%28v=VS.85%29.aspx for a
8+
similar example in C.
9+
"""
10+
11+
from ctypes import wintypes
12+
import signal
13+
import ctypes
14+
15+
# Function prototype for the handler function. Returns BOOL, takes a DWORD.
16+
HandlerRoutine = wintypes.WINFUNCTYPE(wintypes.BOOL, wintypes.DWORD)
17+
18+
def _ctrl_handler(sig):
19+
"""Handle a sig event and return 0 to terminate the process"""
20+
if sig == signal.CTRL_C_EVENT:
21+
pass
22+
elif sig == signal.CTRL_BREAK_EVENT:
23+
pass
24+
else:
25+
print("UNKNOWN EVENT")
26+
return 0
27+
28+
ctrl_handler = HandlerRoutine(_ctrl_handler)
29+
30+
31+
SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler
32+
SetConsoleCtrlHandler.argtypes = (HandlerRoutine, wintypes.BOOL)
33+
SetConsoleCtrlHandler.restype = wintypes.BOOL
34+
35+
if __name__ == "__main__":
36+
# Add our console control handling function with value 1
37+
if not SetConsoleCtrlHandler(ctrl_handler, 1):
38+
print("Unable to add SetConsoleCtrlHandler")
39+
exit(-1)
40+
41+
# Do nothing but wait for the signal
42+
while True:
43+
pass

Lib/unittest/test/test_break.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import gc
22
import io
33
import os
4+
import sys
45
import signal
56
import weakref
67

78
import unittest
89

910

1011
@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill")
12+
@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows")
1113
class TestBreak(unittest.TestCase):
1214

1315
def setUp(self):

Modules/posixmodule.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4171,6 +4171,53 @@ posix_killpg(PyObject *self, PyObject *args)
41714171
}
41724172
#endif
41734173

4174+
#ifdef MS_WINDOWS
4175+
PyDoc_STRVAR(win32_kill__doc__,
4176+
"kill(pid, sig)\n\n\
4177+
Kill a process with a signal.");
4178+
4179+
static PyObject *
4180+
win32_kill(PyObject *self, PyObject *args)
4181+
{
4182+
PyObject *result, handle_obj;
4183+
DWORD pid, sig, err;
4184+
HANDLE handle;
4185+
4186+
if (!PyArg_ParseTuple(args, "kk:kill", &pid, &sig))
4187+
return NULL;
4188+
4189+
/* Console processes which share a common console can be sent CTRL+C or
4190+
CTRL+BREAK events, provided they handle said events. */
4191+
if (sig == CTRL_C_EVENT || sig == CTRL_BREAK_EVENT) {
4192+
if (GenerateConsoleCtrlEvent(sig, pid) == 0) {
4193+
err = GetLastError();
4194+
PyErr_SetFromWindowsErr(err);
4195+
}
4196+
else
4197+
Py_RETURN_NONE;
4198+
}
4199+
4200+
/* If the signal is outside of what GenerateConsoleCtrlEvent can use,
4201+
attempt to open and terminate the process. */
4202+
handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
4203+
if (handle == NULL) {
4204+
err = GetLastError();
4205+
return PyErr_SetFromWindowsErr(err);
4206+
}
4207+
4208+
if (TerminateProcess(handle, sig) == 0) {
4209+
err = GetLastError();
4210+
result = PyErr_SetFromWindowsErr(err);
4211+
} else {
4212+
Py_INCREF(Py_None);
4213+
result = Py_None;
4214+
}
4215+
4216+
CloseHandle(handle);
4217+
return result;
4218+
}
4219+
#endif /* MS_WINDOWS */
4220+
41744221
#ifdef HAVE_PLOCK
41754222

41764223
#ifdef HAVE_SYS_LOCK_H
@@ -7200,6 +7247,7 @@ static PyMethodDef posix_methods[] = {
72007247
#endif /* HAVE_PLOCK */
72017248
#ifdef MS_WINDOWS
72027249
{"startfile", win32_startfile, METH_VARARGS, win32_startfile__doc__},
7250+
{"kill", win32_kill, METH_VARARGS, win32_kill__doc__},
72037251
#endif
72047252
#ifdef HAVE_SETUID
72057253
{"setuid", posix_setuid, METH_VARARGS, posix_setuid__doc__},

Modules/signalmodule.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "intrcheck.h"
88

99
#ifdef MS_WINDOWS
10+
#include <Windows.h>
1011
#ifdef HAVE_PROCESS_H
1112
#include <process.h>
1213
#endif
@@ -805,6 +806,18 @@ PyInit_signal(void)
805806
PyDict_SetItemString(d, "ItimerError", ItimerError);
806807
#endif
807808

809+
#ifdef CTRL_C_EVENT
810+
x = PyLong_FromLong(CTRL_C_EVENT);
811+
PyDict_SetItemString(d, "CTRL_C_EVENT", x);
812+
Py_DECREF(x);
813+
#endif
814+
815+
#ifdef CTRL_BREAK_EVENT
816+
x = PyLong_FromLong(CTRL_BREAK_EVENT);
817+
PyDict_SetItemString(d, "CTRL_BREAK_EVENT", x);
818+
Py_DECREF(x);
819+
#endif
820+
808821
if (PyErr_Occurred()) {
809822
Py_DECREF(m);
810823
m = NULL;

PC/_subprocess.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,5 +599,7 @@ PyInit__subprocess()
599599
defint(d, "INFINITE", INFINITE);
600600
defint(d, "WAIT_OBJECT_0", WAIT_OBJECT_0);
601601
defint(d, "CREATE_NEW_CONSOLE", CREATE_NEW_CONSOLE);
602+
defint(d, "CREATE_NEW_PROCESS_GROUP", CREATE_NEW_PROCESS_GROUP);
603+
602604
return m;
603605
}

0 commit comments

Comments
 (0)