diff -r f1faeca3971f -r b11126a71301 Doc/c-api/exceptions.rst --- a/Doc/c-api/exceptions.rst Wed Jul 23 17:28:15 2014 -0400 +++ b/Doc/c-api/exceptions.rst Thu Jul 24 00:21:51 2014 +0200 @@ -443,12 +443,16 @@ 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 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. + This utility function specifies a file descriptor to which the signal number + will be written as a single byte whenever a signal is received. *fd* must + be non-blocking and must be a valid file descriptor. 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. The function should only be called from the main + thread. .. c:function:: PyObject* PyErr_NewException(char *name, PyObject *base, PyObject *dict) diff -r f1faeca3971f -r b11126a71301 Doc/library/signal.rst --- a/Doc/library/signal.rst Wed Jul 23 17:28:15 2014 -0400 +++ b/Doc/library/signal.rst Thu Jul 24 00:21:51 2014 +0200 @@ -308,15 +308,41 @@ The :mod:`signal` module defines the fol a library to wakeup a poll or select call, allowing the signal to be fully processed. - The old wakeup fd is returned. *fd* must be non-blocking. It is up to the - library to remove any bytes before calling poll or select again. + The old wakeup fd is returned, ``-1`` if there was no old wakeup fd. *fd* + must be non-blocking. It is up to the library to read any bytes before + calling poll or select again. Use for example ``struct.unpack('%uB' % len(data), data)`` to decode the signal numbers list. - When threads are enabled, this function can only be called from the main thread; - attempting to call it from other threads will cause a :exc:`ValueError` - exception to be raised. + When threads are enabled, this function can only be called from the main + thread; attempting to call it from other threads will cause a + :exc:`ValueError` exception to be raised. + + On Windows, this function only supports files. For sockets, + :func:`set_wakeup_socket` should be used instead to support all platforms. + + +.. function:: set_wakeup_socket(socket) + + Set the socket to non-blocking mode and set the wakeup socket to *socket*. + + When a signal is received, the signal number is sent as a single byte into + the socket. This can be used by a library to wakeup a poll or select call, + allowing the signal to be fully processed. + + The old wakeup socket is returned, or ``None`` if there was no socket. + *socket* must be non-blocking. It is up to the library to receive any bytes + before calling poll or select again. + + Use for example ``struct.unpack('%uB' % len(data), data)`` to decode the + signal numbers list. + + When threads are enabled, this function can only be called from the main + thread; attempting to call it from other threads will cause a + :exc:`ValueError` exception to be raised. + + .. versionadded:: 3.5 .. function:: siginterrupt(signalnum, flag) diff -r f1faeca3971f -r b11126a71301 Lib/test/test_signal.py --- a/Lib/test/test_signal.py Wed Jul 23 17:28:15 2014 -0400 +++ b/Lib/test/test_signal.py Thu Jul 24 00:21:51 2014 +0200 @@ -6,6 +6,7 @@ import gc import pickle import select import signal +import socket import struct import subprocess import traceback @@ -269,8 +270,26 @@ class WakeupFDTests(unittest.TestCase): self.assertIs(signal.set_wakeup_fd(-1), -1) +class WakeupSocketTests(unittest.TestCase): + + def test_invalid_socket(self): + # an integer is not a socket + self.assertRaises(TypeError, signal.set_wakeup_socket, 123) + + def test_set_wakeup_socket_result(self): + s1 = socket.socket() + self.addCleanup(s1.close) + s2 = socket.socket() + self.addCleanup(s2.close) + + signal.set_wakeup_socket(s1) + self.assertIs(signal.set_wakeup_socket(s2), s1) + self.assertIs(signal.set_wakeup_socket(None), s2) + self.assertIs(signal.set_wakeup_socket(None), None) + + @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") -class WakeupSignalTests(unittest.TestCase): +class WakeupFDSignalTests(unittest.TestCase): @unittest.skipIf(_testcapi is None, 'need _testcapi') def check_wakeup(self, test_body, *signals, ordered=True): # use a subprocess to have only one thread @@ -441,6 +460,82 @@ 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 + code = """if 1: + import signal + import socket + import struct + import _testcapi + + signum = signal.SIGINT + signals = (signum,) + + def handler(signum, frame): + pass + + signal.signal(signum, handler) + + read, write = socket.socketpair() + signal.set_wakeup_socket(write) + + _testcapi.raise_signal(signum) + + data = read.recv(1) + if not data: + raise Exception("no signum written") + raised = struct.unpack('B', data) + if raised != signals: + raise Exception("%r != %r" % (raised, signals)) + + read.close() + write.close() + """ + + assert_python_ok('-c', code) + + @unittest.skipIf(_testcapi is None, 'need _testcapi') + def test_send_error(self): + # Use a subprocess to have only one thread. + code = """if 1: + import errno + import signal + import socket + import sys + import time + import _testcapi + from test.support import captured_stderr + + signum = signal.SIGINT + + def handler(signum, frame): + pass + + signal.signal(signum, handler) + + read, write = socket.socketpair() + + signal.set_wakeup_socket(write) + + # Close sockets: send() will fail + read.close() + write.close() + + with captured_stderr() as err: + _testcapi.raise_signal(signum) + + err = err.getvalue() + if ('Exception ignored when trying to send to the signal wakeup socket' + not in err): + raise AssertionError(err) + """ + assert_python_ok('-c', code) + + @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") class SiginterruptTest(unittest.TestCase): @@ -989,7 +1084,8 @@ class PendingSignalsTests(unittest.TestC def test_main(): try: support.run_unittest(GenericTests, PosixTests, InterProcessSignalTests, - WakeupFDTests, WakeupSignalTests, + WakeupFDTests, WakeupSocketTests, + WakeupFDSignalTests, WakeupSocketSignalTests, SiginterruptTest, ItimerTest, WindowsSignalTests, PendingSignalsTests) finally: diff -r f1faeca3971f -r b11126a71301 Modules/signalmodule.c --- a/Modules/signalmodule.c Wed Jul 23 17:28:15 2014 -0400 +++ b/Modules/signalmodule.c Thu Jul 24 00:21:51 2014 +0200 @@ -4,6 +4,7 @@ /* XXX Signals should be recorded per thread, now we have thread state. */ #include "Python.h" +#include "socketmodule.h" #ifndef MS_WINDOWS #include "posixmodule.h" #endif @@ -89,6 +90,16 @@ static volatile struct { static volatile sig_atomic_t wakeup_fd = -1; +static volatile struct { + PySocketSockObject *socket; + SOCKET_T sock_fd; + int err_set; + int send_errno; +#ifdef MS_WINDOWS + int win_error; +#endif +} wakeup_socket = {NULL, INVALID_SOCKET, 0}; + /* Speed up sigcheck() when none tripped */ static volatile sig_atomic_t is_tripped = 0; @@ -172,7 +183,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 +195,70 @@ report_wakeup_error(void *data) return 0; } +static int +report_wakeup_send_error(void* Py_UNUSED(data)) +{ + PyObject *res; + + errno = wakeup_socket.send_errno; +#ifdef MS_WINDOWS + SetLastError(wakeup_socket.win_error); +#endif + res = wakeup_socket.socket->errorhandler(); + assert(res == NULL); + + PySys_WriteStderr("Exception ignored when trying to send to the " + "signal wakeup socket:\n"); + PyErr_WriteUnraisable(NULL); + + wakeup_socket.err_set = 0; + + return 0; +} + static void trip_signal(int sig_num) { unsigned char byte; - int rc = 0; + Py_ssize_t rc = 0; Handlers[sig_num].tripped = 1; + if (wakeup_fd != -1) { byte = (unsigned char)sig_num; - while ((rc = write(wakeup_fd, &byte, 1)) == -1 && errno == EINTR); - if (rc == -1) - Py_AddPendingCall(report_wakeup_error, (void *) (Py_intptr_t) errno); + do { + rc = write(wakeup_fd, &byte, 1); + } while (rc < 0 && errno == EINTR); + + if (rc < 0) + Py_AddPendingCall(report_wakeup_write_error, + (void *)(Py_intptr_t)errno); } - if (is_tripped) - return; - /* Set is_tripped after setting .tripped, as it gets - cleared in PyErr_CheckSignals() before .tripped. */ - is_tripped = 1; - Py_AddPendingCall(checksignals_witharg, NULL); + + if (wakeup_socket.sock_fd != INVALID_SOCKET) { + byte = (unsigned char)sig_num; + do { + rc = send(wakeup_socket.sock_fd, &byte, 1, 0); + } while (rc < 0 && errno == EINTR); + + /* we only have a storage for one error in wakeup_socket, cannot + report two errors */ + if (rc < 0 && !wakeup_socket.err_set) { + wakeup_socket.err_set = 1; + wakeup_socket.send_errno = errno; +#ifdef MS_WINDOWS + wakeup_socket.win_error = GetLastError(); +#endif + Py_AddPendingCall(report_wakeup_send_error, + (void *)(Py_intptr_t)errno); + } + } + if (!is_tripped) { + /* Set is_tripped after setting .tripped, as it gets + cleared in PyErr_CheckSignals() before .tripped. */ + is_tripped = 1; + Py_AddPendingCall(checksignals_witharg, NULL); + } } static void @@ -420,16 +476,18 @@ signal_siginterrupt(PyObject *self, PyOb Py_INCREF(Py_None); return Py_None; } +#endif /* HAVE_SIGINTERRUPT */ -#endif static PyObject * signal_set_wakeup_fd(PyObject *self, PyObject *args) { struct stat buf; int fd, old_fd; + if (!PyArg_ParseTuple(args, "i:set_wakeup_fd", &fd)) return NULL; + #ifdef WITH_THREAD if (PyThread_get_thread_ident() != main_thread) { PyErr_SetString(PyExc_ValueError, @@ -457,9 +515,9 @@ signal_set_wakeup_fd(PyObject *self, PyO PyDoc_STRVAR(set_wakeup_fd_doc, "set_wakeup_fd(fd) -> fd\n\ \n\ -Sets the fd to be written to (with '\\0') when a signal\n\ +Sets the fd to be written to (with the signal number) when a signal\n\ comes in. A library can use this to wakeup select or poll.\n\ -The previous fd is returned.\n\ +The previous fd or -1 is returned.\n\ \n\ The fd must be non-blocking."); @@ -475,6 +533,76 @@ PySignal_SetWakeupFd(int fd) } +static PyObject * +signal_set_wakeup_socket(PyObject *self, PyObject *args) +{ + PyObject *obj, *res; + PySocketSockObject *sock, *old_sock; + int is_sock; + static PySocketModule_APIObject *sock_api = NULL; + + if (sock_api == NULL) { + sock_api = PySocketModule_ImportModuleAndAPI(); + if (sock_api == NULL) + return NULL; + } + + if (!PyArg_ParseTuple(args, "O:set_wakeup_socket", &obj)) + return NULL; + + if (obj != Py_None) { + is_sock = PyObject_IsInstance(obj, (PyObject *)sock_api->Sock_Type); + if (is_sock < 0) + return NULL; + if (!is_sock) { + PyErr_SetString(PyExc_TypeError, "expect a socket object"); + return NULL; + } + + res = PyObject_CallMethod(obj, "setblocking", "i", 0); + if (!res) + return NULL; + + sock = (PySocketSockObject *)obj; + } + else { + sock = NULL; + } + +#ifdef WITH_THREAD + if (PyThread_get_thread_ident() != main_thread) { + PyErr_SetString(PyExc_ValueError, + "set_wakeup_socket only works in main thread"); + return NULL; + } +#endif + + old_sock = wakeup_socket.socket; + if (sock != NULL) { + Py_INCREF(sock); + wakeup_socket.socket = sock; + wakeup_socket.sock_fd = sock->sock_fd; + } + else { + wakeup_socket.socket = NULL; + wakeup_socket.sock_fd = INVALID_SOCKET; + } + + if (old_sock == NULL) + Py_RETURN_NONE; + + return (PyObject *)old_sock; +} + +PyDoc_STRVAR(set_wakeup_socket_doc, +"set_wakeup_socket(socket) -> socket\n\ +\n\ +Set the socket to non-blocking mode and set the socket to be \n\ +written to (with the signal number) when a signal comes in.\n\ +A library can use this to wakeup select or poll.\n\ +The previous socket or None is returned."); + + #ifdef HAVE_SETITIMER static PyObject * signal_setitimer(PyObject *self, PyObject *args) @@ -889,6 +1017,7 @@ static PyMethodDef signal_methods[] = { {"signal", signal_signal, METH_VARARGS, signal_doc}, {"getsignal", signal_getsignal, METH_VARARGS, getsignal_doc}, {"set_wakeup_fd", signal_set_wakeup_fd, METH_VARARGS, set_wakeup_fd_doc}, + {"set_wakeup_socket", signal_set_wakeup_socket, METH_VARARGS, set_wakeup_socket_doc}, #ifdef HAVE_SIGINTERRUPT {"siginterrupt", signal_siginterrupt, METH_VARARGS, siginterrupt_doc}, #endif diff -r f1faeca3971f -r b11126a71301 Modules/socketmodule.c --- a/Modules/socketmodule.c Wed Jul 23 17:28:15 2014 -0400 +++ b/Modules/socketmodule.c Thu Jul 24 00:21:51 2014 +0200 @@ -408,10 +408,6 @@ const char *inet_ntop(int af, const void #define NI_MAXSERV 32 #endif -#ifndef INVALID_SOCKET /* MS defines this */ -#define INVALID_SOCKET (-1) -#endif - #ifndef INADDR_NONE #define INADDR_NONE (-1) #endif diff -r f1faeca3971f -r b11126a71301 Modules/socketmodule.h --- a/Modules/socketmodule.h Wed Jul 23 17:28:15 2014 -0400 +++ b/Modules/socketmodule.h Thu Jul 24 00:21:51 2014 +0200 @@ -115,6 +115,10 @@ typedef int SOCKET_T; # define SIZEOF_SOCKET_T SIZEOF_INT #endif +#ifndef INVALID_SOCKET /* MS defines this */ +#define INVALID_SOCKET (-1) +#endif + #if SIZEOF_SOCKET_T <= SIZEOF_LONG #define PyLong_FromSocket_t(fd) PyLong_FromLong((SOCKET_T)(fd)) #define PyLong_AsSocket_t(fd) (SOCKET_T)PyLong_AsLong(fd) diff -r f1faeca3971f -r b11126a71301 PCbuild/pythoncore.vcxproj --- a/PCbuild/pythoncore.vcxproj Wed Jul 23 17:28:15 2014 -0400 +++ b/PCbuild/pythoncore.vcxproj Thu Jul 24 00:21:51 2014 +0200 @@ -176,7 +176,7 @@ "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)\" - $(IntDir)getbuildinfo.o;%(AdditionalDependencies) + $(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(PyDllName).dll libc;%(IgnoreSpecificDefaultLibraries) 0x1e000000 @@ -212,7 +212,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)\" - $(IntDir)getbuildinfo.o;%(AdditionalDependencies) + $(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies) libc;%(IgnoreSpecificDefaultLibraries) 0x1e000000 @@ -247,7 +247,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Debug "$(IntDir)" - $(IntDir)getbuildinfo.o;%(AdditionalDependencies) + $(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies) libc;%(IgnoreSpecificDefaultLibraries) 0x1e000000 @@ -285,7 +285,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Debug "$(IntDir)" - $(IntDir)getbuildinfo.o;%(AdditionalDependencies) + $(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies) libc;%(IgnoreSpecificDefaultLibraries) 0x1e000000 @@ -317,7 +317,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)\" - $(IntDir)getbuildinfo.o;%(AdditionalDependencies) + $(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(PyDllName).dll libc;%(IgnoreSpecificDefaultLibraries) 0x1e000000 @@ -353,7 +353,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)\" - $(IntDir)getbuildinfo.o;%(AdditionalDependencies) + $(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies) libc;%(IgnoreSpecificDefaultLibraries) 0x1e000000 MachineX64 @@ -386,7 +386,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)\" - $(IntDir)getbuildinfo.o;%(AdditionalDependencies) + $(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(PyDllName).dll libc;%(IgnoreSpecificDefaultLibraries) 0x1e000000 @@ -422,7 +422,7 @@ IF %ERRORLEVEL% NEQ 0 ( "$(SolutionDir)make_buildinfo.exe" Release "$(IntDir)\" - $(IntDir)getbuildinfo.o;%(AdditionalDependencies) + $(IntDir)getbuildinfo.o;ws2_32.lib;%(AdditionalDependencies) libc;%(IgnoreSpecificDefaultLibraries) 0x1e000000 MachineX64