diff -r 6b536f0516ea Lib/test/test_signal.py
--- a/Lib/test/test_signal.py Mon Jul 21 12:30:22 2014 +0200
+++ b/Lib/test/test_signal.py Mon Jul 21 15:25:28 2014 +0200
@@ -6,6 +6,7 @@ import gc
import pickle
import select
import signal
+import socket
import struct
import subprocess
import traceback
@@ -268,8 +269,25 @@ class WakeupFDTests(unittest.TestCase):
self.assertIs(signal.set_wakeup_fd(-1), -1)
+class WakeupSocketTests(unittest.TestCase):
+
+ def test_invalid_socket(self):
+ 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
@@ -440,6 +458,86 @@ 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()
+ read.setblocking(False)
+ write.setblocking(False)
+ 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()
+ read.setblocking(False)
+ write.setblocking(False)
+
+ 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):
@@ -988,7 +1086,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 6b536f0516ea Modules/signalmodule.c
--- a/Modules/signalmodule.c Mon Jul 21 12:30:22 2014 +0200
+++ b/Modules/signalmodule.c Mon Jul 21 15:25:28 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,75 @@ report_wakeup_error(void *data)
return 0;
}
+static int
+report_wakeup_send_error(void* Py_UNUSED(data))
+{
+ PyObject *res;
+ int save_errno = errno;
+#ifdef MS_WINDOWS
+ int save_win_error = GetLastError();
+
+ SetLastError(wakeup_socket.win_error);
+#endif
+ errno = wakeup_socket.send_errno;
+
+ 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;
+
+ errno = save_errno;
+#ifdef MS_WINDOWS
+ SetLastError(save_win_error);
+#endif
+ 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
@@ -428,8 +489,10 @@ signal_set_wakeup_fd(PyObject *self, PyO
{
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,
@@ -437,24 +500,92 @@ signal_set_wakeup_fd(PyObject *self, PyO
return NULL;
}
#endif
+
if (fd != -1 && (!_PyVerify_fd(fd) || fstat(fd, &buf) != 0)) {
PyErr_SetString(PyExc_ValueError, "invalid fd");
return NULL;
}
+
old_fd = wakeup_fd;
wakeup_fd = fd;
+
return PyLong_FromLong(old_fd);
}
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\
\n\
The fd must be non-blocking.");
+static PyObject *
+signal_set_wakeup_socket(PyObject *self, PyObject *args)
+{
+ PyObject *obj;
+ PySocketSockObject *sock, *old_sock;
+ int res;
+ 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) {
+ res = PyObject_IsInstance(obj, (PyObject *)sock_api->Sock_Type);
+ if (res < 0)
+ return NULL;
+ if (!res) {
+ PyErr_SetString(PyExc_TypeError, "expect a socket object");
+ 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\
+Sets the socket 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\
+\n\
+The socket must be non-blocking.");
+
/* C API for the same, without all the error checking */
int
PySignal_SetWakeupFd(int fd)
@@ -881,6 +1012,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 6b536f0516ea Modules/socketmodule.c
--- a/Modules/socketmodule.c Mon Jul 21 12:30:22 2014 +0200
+++ b/Modules/socketmodule.c Mon Jul 21 15:25:28 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 6b536f0516ea Modules/socketmodule.h
--- a/Modules/socketmodule.h Mon Jul 21 12:30:22 2014 +0200
+++ b/Modules/socketmodule.h Mon Jul 21 15:25:28 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 6b536f0516ea PCbuild/pythoncore.vcxproj
--- a/PCbuild/pythoncore.vcxproj Mon Jul 21 12:30:22 2014 +0200
+++ b/PCbuild/pythoncore.vcxproj Mon Jul 21 15:25:28 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