cpython: fbd104359ef8 (original) (raw)
Mercurial > cpython
changeset 91933:fbd104359ef8
Issue #22018: On Windows, signal.set_wakeup_fd() now also supports sockets. A side effect is that Python depends to the WinSock library. [#22018]
Victor Stinner victor.stinner@gmail.com | |
---|---|
date | Tue, 29 Jul 2014 23:31:34 +0200 |
parents | 741e58bcaa65 |
children | 79a5fbe2c78f |
files | Doc/c-api/exceptions.rst Doc/library/signal.rst Lib/test/test_signal.py Misc/NEWS Modules/signalmodule.c PCbuild/pythoncore.vcxproj |
diffstat | 6 files changed, 294 insertions(+), 29 deletions(-)[+] [-] Doc/c-api/exceptions.rst 11 Doc/library/signal.rst 3 Lib/test/test_signal.py 107 Misc/NEWS 3 Modules/signalmodule.c 183 PCbuild/pythoncore.vcxproj 16 |
line wrap: on
line diff
--- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -443,13 +443,18 @@ in various ways. There is a separate er .. c:function:: int PySignal_SetWakeupFd(int fd)
- This utility function specifies a file descriptor to which a
'\0'
byte will - be written whenever a signal is received. It returns the previous such file
- descriptor. The value
-1
disables the feature; this is the initial state.
- This utility function specifies a file descriptor to which the signal number
- is written as a single byte whenever a signal is received. fd must be
- non-blocking. It returns the previous such file descriptor. +
- The value
-1
disables the feature; this is the initial state. This is equivalent to :func:signal.set_wakeup_fd
in Python, but without any error checking. fd should be a valid file descriptor. The function should only be called from the main thread. - .. versionchanged:: 3.5
On Windows, the function now also supports socket handles.[](#l1.20)
+ .. c:function:: PyObject* PyErr_NewException(char *name, PyObject *base, PyObject *dict)
--- a/Doc/library/signal.rst
+++ b/Doc/library/signal.rst
@@ -318,6 +318,9 @@ The :mod:signal
module defines the fol
attempting to call it from other threads will cause a :exc:ValueError
exception to be raised.
+ .. function:: siginterrupt(signalnum, flag)
--- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -6,6 +6,7 @@ import gc import pickle import select import signal +import socket import struct import subprocess import traceback @@ -255,6 +256,13 @@ class WakeupFDTests(unittest.TestCase): self.assertRaises((ValueError, OSError), signal.set_wakeup_fd, fd)
- def test_invalid_socket(self):
sock = socket.socket()[](#l3.16)
fd = sock.fileno()[](#l3.17)
sock.close()[](#l3.18)
self.assertRaises((ValueError, OSError),[](#l3.19)
signal.set_wakeup_fd, fd)[](#l3.20)
+ def test_set_wakeup_fd_result(self): r1, w1 = os.pipe() self.addCleanup(os.close, r1) @@ -268,6 +276,20 @@ class WakeupFDTests(unittest.TestCase): self.assertEqual(signal.set_wakeup_fd(-1), w2) self.assertEqual(signal.set_wakeup_fd(-1), -1)
- def test_set_wakeup_fd_socket_result(self):
sock1 = socket.socket()[](#l3.30)
self.addCleanup(sock1.close)[](#l3.31)
fd1 = sock1.fileno()[](#l3.32)
sock2 = socket.socket()[](#l3.34)
self.addCleanup(sock2.close)[](#l3.35)
fd2 = sock2.fileno()[](#l3.36)
signal.set_wakeup_fd(fd1)[](#l3.38)
self.assertIs(signal.set_wakeup_fd(fd2), fd1)[](#l3.39)
self.assertIs(signal.set_wakeup_fd(-1), fd2)[](#l3.40)
self.assertIs(signal.set_wakeup_fd(-1), -1)[](#l3.41)
+ @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") class WakeupSignalTests(unittest.TestCase): @@ -435,6 +457,90 @@ class WakeupSignalTests(unittest.TestCas """, signal.SIGUSR1, signal.SIGUSR2, ordered=False) +@unittest.skipUnless(hasattr(socket, 'socketpair'), 'need socket.socketpair') +class WakeupSocketSignalTests(unittest.TestCase): +
- @unittest.skipIf(_testcapi is None, 'need _testcapi')
- def test_socket(self):
# use a subprocess to have only one thread[](#l3.55)
code = """if 1:[](#l3.56)
import signal[](#l3.57)
import socket[](#l3.58)
import struct[](#l3.59)
import _testcapi[](#l3.60)
signum = signal.SIGINT[](#l3.62)
signals = (signum,)[](#l3.63)
def handler(signum, frame):[](#l3.65)
pass[](#l3.66)
signal.signal(signum, handler)[](#l3.68)
read, write = socket.socketpair()[](#l3.70)
read.setblocking(False)[](#l3.71)
write.setblocking(False)[](#l3.72)
signal.set_wakeup_fd(write.fileno())[](#l3.73)
_testcapi.raise_signal(signum)[](#l3.75)
data = read.recv(1)[](#l3.77)
if not data:[](#l3.78)
raise Exception("no signum written")[](#l3.79)
raised = struct.unpack('B', data)[](#l3.80)
if raised != signals:[](#l3.81)
raise Exception("%r != %r" % (raised, signals))[](#l3.82)
read.close()[](#l3.84)
write.close()[](#l3.85)
"""[](#l3.86)
assert_python_ok('-c', code)[](#l3.88)
- @unittest.skipIf(_testcapi is None, 'need _testcapi')
- def test_send_error(self):
# Use a subprocess to have only one thread.[](#l3.92)
if os.name == 'nt':[](#l3.93)
action = 'send'[](#l3.94)
else:[](#l3.95)
action = 'write'[](#l3.96)
code = """if 1:[](#l3.97)
import errno[](#l3.98)
import signal[](#l3.99)
import socket[](#l3.100)
import sys[](#l3.101)
import time[](#l3.102)
import _testcapi[](#l3.103)
from test.support import captured_stderr[](#l3.104)
signum = signal.SIGINT[](#l3.106)
def handler(signum, frame):[](#l3.108)
pass[](#l3.109)
signal.signal(signum, handler)[](#l3.111)
read, write = socket.socketpair()[](#l3.113)
read.setblocking(False)[](#l3.114)
write.setblocking(False)[](#l3.115)
signal.set_wakeup_fd(write.fileno())[](#l3.117)
# Close sockets: send() will fail[](#l3.119)
read.close()[](#l3.120)
write.close()[](#l3.121)
with captured_stderr() as err:[](#l3.123)
_testcapi.raise_signal(signum)[](#l3.124)
err = err.getvalue()[](#l3.126)
if ('Exception ignored when trying to {action} to the signal wakeup fd'[](#l3.127)
not in err):[](#l3.128)
raise AssertionError(err)[](#l3.129)
""".format(action=action)[](#l3.130)
assert_python_ok('-c', code)[](#l3.131)
+ + @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") class SiginterruptTest(unittest.TestCase): @@ -984,6 +1090,7 @@ def test_main(): try: support.run_unittest(GenericTests, PosixTests, InterProcessSignalTests, WakeupFDTests, WakeupSignalTests,
finally:WakeupSocketSignalTests,[](#l3.141) SiginterruptTest, ItimerTest, WindowsSignalTests,[](#l3.142) PendingSignalsTests)[](#l3.143)
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -113,6 +113,9 @@ Core and Builtins Library ------- +- Issue #22018: On Windows, signal.set_wakeup_fd() now also supports sockets.
- Issue #22054: Add os.get_blocking() and os.set_blocking() functions to get and set the blocking mode of a file descriptor (False if the O_NONBLOCK flag is set, True otherwise). These functions are not available on Windows.
--- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -7,6 +7,9 @@ #ifndef MS_WINDOWS #include "posixmodule.h" #endif +#ifdef MS_WINDOWS +#include "socketmodule.h" /* needed for SOCKET_T */ +#endif #ifdef MS_WINDOWS #include <windows.h> @@ -87,7 +90,20 @@ static volatile struct { PyObject *func; } Handlers[NSIG]; +#ifdef MS_WINDOWS +#define INVALID_FD ((SOCKET_T)-1) + +static volatile struct {
+} wakeup = {INVALID_FD, 0, 0}; +#else +#define INVALID_FD (-1) static volatile sig_atomic_t wakeup_fd = -1; +#endif /* Speed up sigcheck() when none tripped */ static volatile sig_atomic_t is_tripped = 0; @@ -172,7 +188,7 @@ checksignals_witharg(void * unused) } static int -report_wakeup_error(void *data) +report_wakeup_write_error(void *data) { int save_errno = errno; errno = (int) (Py_intptr_t) data; @@ -184,25 +200,86 @@ report_wakeup_error(void data) return 0; } +#ifdef MS_WINDOWS +static int +report_wakeup_send_error(void Py_UNUSED(data)) +{
- if (wakeup.send_win_error) {
/* PyErr_SetExcFromWindowsErr() invokes FormatMessage() which[](#l5.54)
recognizes the error codes used by both GetLastError() and[](#l5.55)
WSAGetLastError */[](#l5.56)
res = PyErr_SetExcFromWindowsErr(PyExc_OSError, wakeup.send_win_error);[](#l5.57)
- }
- else {
errno = wakeup.send_errno;[](#l5.60)
res = PyErr_SetFromErrno(PyExc_OSError);[](#l5.61)
- }
- PySys_WriteStderr("Exception ignored when trying to send to the "
"signal wakeup fd:\n");[](#l5.68)
- PyErr_WriteUnraisable(NULL);
+} +#endif /* MS_WINDOWS */ + static void trip_signal(int sig_num) { unsigned char byte;
Handlers[sig_num].tripped = 1;
while ((rc = write(wakeup_fd, &byte, 1)) == -1 && errno == EINTR);[](#l5.94)
if (rc == -1)[](#l5.95)
Py_AddPendingCall(report_wakeup_error, (void *) (Py_intptr_t) errno);[](#l5.96)
if (wakeup.use_send) {[](#l5.98)
do {[](#l5.99)
rc = send(fd, &byte, 1, 0);[](#l5.100)
} while (rc < 0 && errno == EINTR);[](#l5.101)
/* we only have a storage for one error in the wakeup structure */[](#l5.103)
if (rc < 0 && !wakeup.send_err_set) {[](#l5.104)
wakeup.send_err_set = 1;[](#l5.105)
wakeup.send_errno = errno;[](#l5.106)
wakeup.send_win_error = GetLastError();[](#l5.107)
Py_AddPendingCall(report_wakeup_send_error, NULL);[](#l5.108)
}[](#l5.109)
}[](#l5.110)
else[](#l5.111)
{[](#l5.113)
byte = (unsigned char)sig_num;[](#l5.114)
do {[](#l5.115)
rc = write(fd, &byte, 1);[](#l5.116)
} while (rc < 0 && errno == EINTR);[](#l5.117)
if (rc < 0) {[](#l5.119)
Py_AddPendingCall(report_wakeup_write_error,[](#l5.120)
(void *)(Py_intptr_t)errno);[](#l5.121)
}[](#l5.122)
}}[](#l5.123)
- if (is_tripped)
return;[](#l5.126)
- /* Set is_tripped after setting .tripped, as it gets
cleared in PyErr_CheckSignals() before .tripped. */[](#l5.128)
- is_tripped = 1;
- Py_AddPendingCall(checksignals_witharg, NULL);
- if (!is_tripped) {
/* Set is_tripped after setting .tripped, as it gets[](#l5.133)
cleared in PyErr_CheckSignals() before .tripped. */[](#l5.134)
is_tripped = 1;[](#l5.135)
Py_AddPendingCall(checksignals_witharg, NULL);[](#l5.136)
- }
} static void @@ -426,10 +503,29 @@ signal_siginterrupt(PyObject *self, PyOb static PyObject * signal_set_wakeup_fd(PyObject *self, PyObject *args) {
- PyObject *fdobj;
- SOCKET_T fd, old_fd;
- int res;
- int res_size = sizeof res;
- PyObject *mod;
- struct stat st;
- int is_socket;
- fd = PyLong_AsSocket_t(fdobj);
- if (fd == (SOCKET_T)(-1) && PyErr_Occurred())
return NULL;[](#l5.160)
+ if (!PyArg_ParseTuple(args, "i:set_wakeup_fd", &fd)) return NULL; +#endif + #ifdef WITH_THREAD if (PyThread_get_thread_ident() != main_thread) { PyErr_SetString(PyExc_ValueError, @@ -438,28 +534,72 @@ signal_set_wakeup_fd(PyObject *self, PyO } #endif +#ifdef MS_WINDOWS
- is_socket = 0;
- if (fd != INVALID_FD) {
/* Import the _socket module to call WSAStartup() */[](#l5.179)
mod = PyImport_ImportModuleNoBlock("_socket");[](#l5.180)
if (mod == NULL)[](#l5.181)
return NULL;[](#l5.182)
Py_DECREF(mod);[](#l5.183)
/* test the socket */[](#l5.185)
if (getsockopt(fd, SOL_SOCKET, SO_ERROR,[](#l5.186)
(char *)&res, &res_size) != 0) {[](#l5.187)
int err = WSAGetLastError();[](#l5.188)
if (err != WSAENOTSOCK) {[](#l5.189)
PyErr_SetExcFromWindowsErr(PyExc_OSError, err);[](#l5.190)
return NULL;[](#l5.191)
}[](#l5.192)
if (!_PyVerify_fd(fd)) {[](#l5.194)
PyErr_SetString(PyExc_ValueError, "invalid fd");[](#l5.195)
return NULL;[](#l5.196)
}[](#l5.197)
if (fstat(fd, &st) != 0) {[](#l5.199)
PyErr_SetFromErrno(PyExc_OSError);[](#l5.200)
return NULL;[](#l5.201)
}[](#l5.202)
}[](#l5.203)
else[](#l5.204)
is_socket = 1;[](#l5.205)
- }
- if (old_fd != INVALID_FD)
return PyLong_FromSocket_t(old_fd);[](#l5.213)
- else
return PyLong_FromLong(-1);[](#l5.215)
+#else if (fd != -1) { if (!_PyVerify_fd(fd)) { PyErr_SetString(PyExc_ValueError, "invalid fd"); return NULL; }
if (fstat(fd, &buf) != 0)[](#l5.223)
return PyErr_SetFromErrno(PyExc_OSError);[](#l5.224)
if (fstat(fd, &st) != 0) {[](#l5.225)
PyErr_SetFromErrno(PyExc_OSError);[](#l5.226)
return NULL;[](#l5.227)
} old_fd = wakeup_fd; wakeup_fd = fd; return PyLong_FromLong(old_fd); +#endif } PyDoc_STRVAR(set_wakeup_fd_doc, "set_wakeup_fd(fd) -> fd\n[](#l5.239) \n[](#l5.240) -Sets the fd to be written to (with '\0') when a signal\n[](#l5.241) +Sets the fd to be written to (with the signal number) when a signal\n[](#l5.242) comes in. A library can use this to wakeup select or poll.\n[](#l5.243) -The previous fd is returned.\n[](#l5.244) +The previous fd or -1 is returned.\n[](#l5.245) \n[](#l5.246) The fd must be non-blocking."); @@ -467,10 +607,17 @@ The fd must be non-blocking."); int PySignal_SetWakeupFd(int fd) {}[](#l5.228)
--- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -176,7 +176,7 @@ "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)"
<AdditionalDependencies>$(IntDir)getbuildinfo.o;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.7)
<AdditionalDependencies>$(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.8) <OutputFile>$(OutDir)$(PyDllName).dll</OutputFile>[](#l6.9) <IgnoreSpecificDefaultLibraries>libc;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>[](#l6.10) <BaseAddress>0x1e000000</BaseAddress>[](#l6.11)
@@ -212,7 +212,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)"
<AdditionalDependencies>$(IntDir)getbuildinfo.o;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.16)
[](#l6.20) @@ -247,7 +247,7 @@ IF %ERRORLEVEL% NEQ 0 ([](#l6.21) "$(SolutionDir)make_buildinfo.exe" Debug "$(IntDir)"[](#l6.22) [](#l6.23) [](#l6.24)<AdditionalDependencies>$(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.17) <IgnoreSpecificDefaultLibraries>libc;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>[](#l6.18) <BaseAddress>0x1e000000</BaseAddress>[](#l6.19)
<AdditionalDependencies>$(IntDir)getbuildinfo.o;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.25)
[](#l6.29) @@ -285,7 +285,7 @@ IF %ERRORLEVEL% NEQ 0 ([](#l6.30) "$(SolutionDir)make_buildinfo.exe" Debug "$(IntDir)"[](#l6.31) [](#l6.32) [](#l6.33)<AdditionalDependencies>$(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.26) <IgnoreSpecificDefaultLibraries>libc;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>[](#l6.27) <BaseAddress>0x1e000000</BaseAddress>[](#l6.28)
<AdditionalDependencies>$(IntDir)getbuildinfo.o;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.34)
[](#l6.38) @@ -317,7 +317,7 @@ IF %ERRORLEVEL% NEQ 0 ([](#l6.39) "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)\"[](#l6.40) [](#l6.41) [](#l6.42)<AdditionalDependencies>$(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.35) <IgnoreSpecificDefaultLibraries>libc;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>[](#l6.36) <BaseAddress>0x1e000000</BaseAddress>[](#l6.37)
<AdditionalDependencies>$(IntDir)getbuildinfo.o;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.43)
<AdditionalDependencies>$(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.44) <OutputFile>$(OutDir)$(PyDllName).dll</OutputFile>[](#l6.45) <IgnoreSpecificDefaultLibraries>libc;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>[](#l6.46) <BaseAddress>0x1e000000</BaseAddress>[](#l6.47)
@@ -353,7 +353,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)"
<AdditionalDependencies>$(IntDir)getbuildinfo.o;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.52)
<AdditionalDependencies>$(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.53) <IgnoreSpecificDefaultLibraries>libc;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>[](#l6.54) <BaseAddress>0x1e000000</BaseAddress>[](#l6.55) <TargetMachine>MachineX64</TargetMachine>[](#l6.56)
@@ -386,7 +386,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)"
<AdditionalDependencies>$(IntDir)getbuildinfo.o;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.61)
<AdditionalDependencies>$(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.62) <OutputFile>$(OutDir)$(PyDllName).dll</OutputFile>[](#l6.63) <IgnoreSpecificDefaultLibraries>libc;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>[](#l6.64) <BaseAddress>0x1e000000</BaseAddress>[](#l6.65)
@@ -422,7 +422,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)"
<AdditionalDependencies>$(IntDir)getbuildinfo.o;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.70)
<AdditionalDependencies>$(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>[](#l6.71) <IgnoreSpecificDefaultLibraries>libc;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>[](#l6.72) <BaseAddress>0x1e000000</BaseAddress>[](#l6.73) <TargetMachine>MachineX64</TargetMachine>[](#l6.74)