summaryrefslogtreecommitdiffstats
path: root/Modules/socketmodule.c
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2010-09-28 21:23:11 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2010-09-28 21:23:11 (GMT)
commit3e1fd27b74af4f1f040f8b11379015140240deff (patch)
treef85346e8049da8b34c291286dd8a250102b93f14 /Modules/socketmodule.c
parentcc868d430b0416e588d53698f560b236c6ba88d2 (diff)
downloadcpython-3e1fd27b74af4f1f040f8b11379015140240deff.zip
cpython-3e1fd27b74af4f1f040f8b11379015140240deff.tar.gz
cpython-3e1fd27b74af4f1f040f8b11379015140240deff.tar.bz2
Issue #9090: When a socket with a timeout fails with EWOULDBLOCK or EAGAIN,
retry the select() loop instead of bailing out. This is because select() can incorrectly report a socket as ready for reading (for example, if it received some data with an invalid checksum).
Diffstat (limited to 'Modules/socketmodule.c')
-rw-r--r--Modules/socketmodule.c103
1 files changed, 88 insertions, 15 deletions
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
index cd56ee3..017a63e 100644
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -474,6 +474,14 @@ select_error(void)
return NULL;
}
+#ifdef MS_WINDOWS
+#define CHECK_ERRNO(expected) \
+ (WSAGetLastError() == WSA ## expected)
+#else
+#define CHECK_ERRNO(expected) \
+ (errno == expected)
+#endif
+
/* Convenience function to raise an error according to errno
and return a NULL pointer from a function. */
@@ -637,7 +645,7 @@ internal_setblocking(PySocketSockObject *s, int block)
after they've reacquired the interpreter lock.
Returns 1 on timeout, -1 on error, 0 otherwise. */
static int
-internal_select(PySocketSockObject *s, int writing)
+internal_select_ex(PySocketSockObject *s, int writing, double interval)
{
int n;
@@ -649,6 +657,10 @@ internal_select(PySocketSockObject *s, int writing)
if (s->sock_fd < 0)
return 0;
+ /* Handling this condition here simplifies the select loops */
+ if (interval < 0.0)
+ return 1;
+
/* Prefer poll, if available, since you can poll() any fd
* which can't be done with select(). */
#ifdef HAVE_POLL
@@ -660,7 +672,7 @@ internal_select(PySocketSockObject *s, int writing)
pollfd.events = writing ? POLLOUT : POLLIN;
/* s->sock_timeout is in seconds, timeout in ms */
- timeout = (int)(s->sock_timeout * 1000 + 0.5);
+ timeout = (int)(interval * 1000 + 0.5);
n = poll(&pollfd, 1, timeout);
}
#else
@@ -668,8 +680,8 @@ internal_select(PySocketSockObject *s, int writing)
/* Construct the arguments to select */
fd_set fds;
struct timeval tv;
- tv.tv_sec = (int)s->sock_timeout;
- tv.tv_usec = (int)((s->sock_timeout - tv.tv_sec) * 1e6);
+ tv.tv_sec = (int)interval;
+ tv.tv_usec = (int)((interval - tv.tv_sec) * 1e6);
FD_ZERO(&fds);
FD_SET(s->sock_fd, &fds);
@@ -690,6 +702,53 @@ internal_select(PySocketSockObject *s, int writing)
return 0;
}
+static int
+internal_select(PySocketSockObject *s, int writing)
+{
+ return internal_select_ex(s, writing, s->sock_timeout);
+}
+
+/*
+ Two macros for automatic retry of select() in case of false positives
+ (for example, select() could indicate a socket is ready for reading
+ but the data then discarded by the OS because of a wrong checksum).
+ Here is an example of use:
+
+ BEGIN_SELECT_LOOP(s)
+ Py_BEGIN_ALLOW_THREADS
+ timeout = internal_select_ex(s, 0, interval);
+ if (!timeout)
+ outlen = recv(s->sock_fd, cbuf, len, flags);
+ Py_END_ALLOW_THREADS
+ if (timeout == 1) {
+ PyErr_SetString(socket_timeout, "timed out");
+ return -1;
+ }
+ END_SELECT_LOOP(s)
+*/
+
+#define BEGIN_SELECT_LOOP(s) \
+ { \
+ _PyTime_timeval now, deadline = {0, 0}; \
+ double interval = s->sock_timeout; \
+ int has_timeout = s->sock_timeout > 0.0; \
+ if (has_timeout) { \
+ _PyTime_gettimeofday(&now); \
+ deadline = now; \
+ _PyTime_ADD_SECONDS(deadline, s->sock_timeout); \
+ } \
+ while (1) { \
+ errno = 0; \
+
+#define END_SELECT_LOOP(s) \
+ if (!has_timeout || \
+ (!CHECK_ERRNO(EWOULDBLOCK) && !CHECK_ERRNO(EAGAIN))) \
+ break; \
+ _PyTime_gettimeofday(&now); \
+ interval = _PyTime_INTERVAL(now, deadline); \
+ } \
+ } \
+
/* Initialize a new socket object. */
static double defaulttimeout = -1.0; /* Default timeout for new sockets */
@@ -1591,8 +1650,9 @@ sock_accept(PySocketSockObject *s)
if (!IS_SELECTABLE(s))
return select_error();
+ BEGIN_SELECT_LOOP(s)
Py_BEGIN_ALLOW_THREADS
- timeout = internal_select(s, 0);
+ timeout = internal_select_ex(s, 0, interval);
if (!timeout)
newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen);
Py_END_ALLOW_THREADS
@@ -1601,6 +1661,7 @@ sock_accept(PySocketSockObject *s)
PyErr_SetString(socket_timeout, "timed out");
return NULL;
}
+ END_SELECT_LOOP(s)
if (newfd == INVALID_SOCKET)
return s->errorhandler();
@@ -2151,6 +2212,7 @@ will allow before refusing new connections.");
* also possible that we return a number of bytes smaller than the request
* bytes.
*/
+
static Py_ssize_t
sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
{
@@ -2171,8 +2233,9 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
}
#ifndef __VMS
+ BEGIN_SELECT_LOOP(s)
Py_BEGIN_ALLOW_THREADS
- timeout = internal_select(s, 0);
+ timeout = internal_select_ex(s, 0, interval);
if (!timeout)
outlen = recv(s->sock_fd, cbuf, len, flags);
Py_END_ALLOW_THREADS
@@ -2181,6 +2244,7 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
PyErr_SetString(socket_timeout, "timed out");
return -1;
}
+ END_SELECT_LOOP(s)
if (outlen < 0) {
/* Note: the call to errorhandler() ALWAYS indirectly returned
NULL, so ignore its return value */
@@ -2202,16 +2266,18 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
segment = remaining;
}
+ BEGIN_SELECT_LOOP(s)
Py_BEGIN_ALLOW_THREADS
- timeout = internal_select(s, 0);
+ timeout = internal_select_ex(s, 0, interval);
if (!timeout)
nread = recv(s->sock_fd, read_buf, segment, flags);
Py_END_ALLOW_THREADS
-
if (timeout == 1) {
PyErr_SetString(socket_timeout, "timed out");
return -1;
}
+ END_SELECT_LOOP(s)
+
if (nread < 0) {
s->errorhandler();
return -1;
@@ -2372,9 +2438,10 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags,
return -1;
}
+ BEGIN_SELECT_LOOP(s)
Py_BEGIN_ALLOW_THREADS
memset(&addrbuf, 0, addrlen);
- timeout = internal_select(s, 0);
+ timeout = internal_select_ex(s, 0, interval);
if (!timeout) {
#ifndef MS_WINDOWS
#if defined(PYOS_OS2) && !defined(PYCC_GCC)
@@ -2395,6 +2462,7 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags,
PyErr_SetString(socket_timeout, "timed out");
return -1;
}
+ END_SELECT_LOOP(s)
if (n < 0) {
s->errorhandler();
return -1;
@@ -2532,8 +2600,9 @@ sock_send(PySocketSockObject *s, PyObject *args)
buf = pbuf.buf;
len = pbuf.len;
+ BEGIN_SELECT_LOOP(s)
Py_BEGIN_ALLOW_THREADS
- timeout = internal_select(s, 1);
+ timeout = internal_select_ex(s, 1, interval);
if (!timeout)
#ifdef __VMS
n = sendsegmented(s->sock_fd, buf, len, flags);
@@ -2541,13 +2610,14 @@ sock_send(PySocketSockObject *s, PyObject *args)
n = send(s->sock_fd, buf, len, flags);
#endif
Py_END_ALLOW_THREADS
-
- PyBuffer_Release(&pbuf);
-
if (timeout == 1) {
+ PyBuffer_Release(&pbuf);
PyErr_SetString(socket_timeout, "timed out");
return NULL;
}
+ END_SELECT_LOOP(s)
+
+ PyBuffer_Release(&pbuf);
if (n < 0)
return s->errorhandler();
return PyLong_FromSsize_t(n);
@@ -2667,17 +2737,20 @@ sock_sendto(PySocketSockObject *s, PyObject *args)
return NULL;
}
+ BEGIN_SELECT_LOOP(s)
Py_BEGIN_ALLOW_THREADS
- timeout = internal_select(s, 1);
+ timeout = internal_select_ex(s, 1, interval);
if (!timeout)
n = sendto(s->sock_fd, buf, len, flags, SAS2SA(&addrbuf), addrlen);
Py_END_ALLOW_THREADS
- PyBuffer_Release(&pbuf);
if (timeout == 1) {
+ PyBuffer_Release(&pbuf);
PyErr_SetString(socket_timeout, "timed out");
return NULL;
}
+ END_SELECT_LOOP(s)
+ PyBuffer_Release(&pbuf);
if (n < 0)
return s->errorhandler();
return PyLong_FromSsize_t(n);