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

Skip to content

Commit 3e1fd27

Browse files
committed
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).
1 parent cc868d4 commit 3e1fd27

3 files changed

Lines changed: 104 additions & 15 deletions

File tree

Include/pytime.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ typedef struct {
2525
*/
2626
PyAPI_FUNC(void) _PyTime_gettimeofday(_PyTime_timeval *tp);
2727

28+
#define _PyTime_ADD_SECONDS(tv, interval) \
29+
do { \
30+
tv.tv_usec += (long) (((long) interval - interval) * 1000000); \
31+
tv.tv_sec += (time_t) interval + (time_t) (tv.tv_usec / 1000000); \
32+
tv.tv_usec %= 1000000; \
33+
} while (0)
34+
35+
#define _PyTime_INTERVAL(tv_start, tv_end) \
36+
((tv_end.tv_sec - tv_start.tv_sec) + \
37+
(tv_end.tv_usec - tv_start.tv_usec) * 0.000001)
38+
2839
/* Dummy to force linking. */
2940
PyAPI_FUNC(void) _PyTime_Init(void);
3041

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ Core and Builtins
7474
Library
7575
-------
7676

77+
- Issue #9090: When a socket with a timeout fails with EWOULDBLOCK or EAGAIN,
78+
retry the select() loop instead of bailing out. This is because select()
79+
can incorrectly report a socket as ready for reading (for example, if it
80+
received some data with an invalid checksum).
81+
7782
- Issue #3612: Added new types to ctypes.wintypes. (CHAR and pointers)
7883

7984
- Issue #9950: Fix socket.sendall() crash or misbehaviour when a signal is

Modules/socketmodule.c

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,14 @@ select_error(void)
474474
return NULL;
475475
}
476476

477+
#ifdef MS_WINDOWS
478+
#define CHECK_ERRNO(expected) \
479+
(WSAGetLastError() == WSA ## expected)
480+
#else
481+
#define CHECK_ERRNO(expected) \
482+
(errno == expected)
483+
#endif
484+
477485
/* Convenience function to raise an error according to errno
478486
and return a NULL pointer from a function. */
479487

@@ -637,7 +645,7 @@ internal_setblocking(PySocketSockObject *s, int block)
637645
after they've reacquired the interpreter lock.
638646
Returns 1 on timeout, -1 on error, 0 otherwise. */
639647
static int
640-
internal_select(PySocketSockObject *s, int writing)
648+
internal_select_ex(PySocketSockObject *s, int writing, double interval)
641649
{
642650
int n;
643651

@@ -649,6 +657,10 @@ internal_select(PySocketSockObject *s, int writing)
649657
if (s->sock_fd < 0)
650658
return 0;
651659

660+
/* Handling this condition here simplifies the select loops */
661+
if (interval < 0.0)
662+
return 1;
663+
652664
/* Prefer poll, if available, since you can poll() any fd
653665
* which can't be done with select(). */
654666
#ifdef HAVE_POLL
@@ -660,16 +672,16 @@ internal_select(PySocketSockObject *s, int writing)
660672
pollfd.events = writing ? POLLOUT : POLLIN;
661673

662674
/* s->sock_timeout is in seconds, timeout in ms */
663-
timeout = (int)(s->sock_timeout * 1000 + 0.5);
675+
timeout = (int)(interval * 1000 + 0.5);
664676
n = poll(&pollfd, 1, timeout);
665677
}
666678
#else
667679
{
668680
/* Construct the arguments to select */
669681
fd_set fds;
670682
struct timeval tv;
671-
tv.tv_sec = (int)s->sock_timeout;
672-
tv.tv_usec = (int)((s->sock_timeout - tv.tv_sec) * 1e6);
683+
tv.tv_sec = (int)interval;
684+
tv.tv_usec = (int)((interval - tv.tv_sec) * 1e6);
673685
FD_ZERO(&fds);
674686
FD_SET(s->sock_fd, &fds);
675687

@@ -690,6 +702,53 @@ internal_select(PySocketSockObject *s, int writing)
690702
return 0;
691703
}
692704

705+
static int
706+
internal_select(PySocketSockObject *s, int writing)
707+
{
708+
return internal_select_ex(s, writing, s->sock_timeout);
709+
}
710+
711+
/*
712+
Two macros for automatic retry of select() in case of false positives
713+
(for example, select() could indicate a socket is ready for reading
714+
but the data then discarded by the OS because of a wrong checksum).
715+
Here is an example of use:
716+
717+
BEGIN_SELECT_LOOP(s)
718+
Py_BEGIN_ALLOW_THREADS
719+
timeout = internal_select_ex(s, 0, interval);
720+
if (!timeout)
721+
outlen = recv(s->sock_fd, cbuf, len, flags);
722+
Py_END_ALLOW_THREADS
723+
if (timeout == 1) {
724+
PyErr_SetString(socket_timeout, "timed out");
725+
return -1;
726+
}
727+
END_SELECT_LOOP(s)
728+
*/
729+
730+
#define BEGIN_SELECT_LOOP(s) \
731+
{ \
732+
_PyTime_timeval now, deadline = {0, 0}; \
733+
double interval = s->sock_timeout; \
734+
int has_timeout = s->sock_timeout > 0.0; \
735+
if (has_timeout) { \
736+
_PyTime_gettimeofday(&now); \
737+
deadline = now; \
738+
_PyTime_ADD_SECONDS(deadline, s->sock_timeout); \
739+
} \
740+
while (1) { \
741+
errno = 0; \
742+
743+
#define END_SELECT_LOOP(s) \
744+
if (!has_timeout || \
745+
(!CHECK_ERRNO(EWOULDBLOCK) && !CHECK_ERRNO(EAGAIN))) \
746+
break; \
747+
_PyTime_gettimeofday(&now); \
748+
interval = _PyTime_INTERVAL(now, deadline); \
749+
} \
750+
} \
751+
693752
/* Initialize a new socket object. */
694753

695754
static double defaulttimeout = -1.0; /* Default timeout for new sockets */
@@ -1591,8 +1650,9 @@ sock_accept(PySocketSockObject *s)
15911650
if (!IS_SELECTABLE(s))
15921651
return select_error();
15931652

1653+
BEGIN_SELECT_LOOP(s)
15941654
Py_BEGIN_ALLOW_THREADS
1595-
timeout = internal_select(s, 0);
1655+
timeout = internal_select_ex(s, 0, interval);
15961656
if (!timeout)
15971657
newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen);
15981658
Py_END_ALLOW_THREADS
@@ -1601,6 +1661,7 @@ sock_accept(PySocketSockObject *s)
16011661
PyErr_SetString(socket_timeout, "timed out");
16021662
return NULL;
16031663
}
1664+
END_SELECT_LOOP(s)
16041665

16051666
if (newfd == INVALID_SOCKET)
16061667
return s->errorhandler();
@@ -2151,6 +2212,7 @@ will allow before refusing new connections.");
21512212
* also possible that we return a number of bytes smaller than the request
21522213
* bytes.
21532214
*/
2215+
21542216
static Py_ssize_t
21552217
sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
21562218
{
@@ -2171,8 +2233,9 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
21712233
}
21722234

21732235
#ifndef __VMS
2236+
BEGIN_SELECT_LOOP(s)
21742237
Py_BEGIN_ALLOW_THREADS
2175-
timeout = internal_select(s, 0);
2238+
timeout = internal_select_ex(s, 0, interval);
21762239
if (!timeout)
21772240
outlen = recv(s->sock_fd, cbuf, len, flags);
21782241
Py_END_ALLOW_THREADS
@@ -2181,6 +2244,7 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
21812244
PyErr_SetString(socket_timeout, "timed out");
21822245
return -1;
21832246
}
2247+
END_SELECT_LOOP(s)
21842248
if (outlen < 0) {
21852249
/* Note: the call to errorhandler() ALWAYS indirectly returned
21862250
NULL, so ignore its return value */
@@ -2202,16 +2266,18 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
22022266
segment = remaining;
22032267
}
22042268

2269+
BEGIN_SELECT_LOOP(s)
22052270
Py_BEGIN_ALLOW_THREADS
2206-
timeout = internal_select(s, 0);
2271+
timeout = internal_select_ex(s, 0, interval);
22072272
if (!timeout)
22082273
nread = recv(s->sock_fd, read_buf, segment, flags);
22092274
Py_END_ALLOW_THREADS
2210-
22112275
if (timeout == 1) {
22122276
PyErr_SetString(socket_timeout, "timed out");
22132277
return -1;
22142278
}
2279+
END_SELECT_LOOP(s)
2280+
22152281
if (nread < 0) {
22162282
s->errorhandler();
22172283
return -1;
@@ -2372,9 +2438,10 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags,
23722438
return -1;
23732439
}
23742440

2441+
BEGIN_SELECT_LOOP(s)
23752442
Py_BEGIN_ALLOW_THREADS
23762443
memset(&addrbuf, 0, addrlen);
2377-
timeout = internal_select(s, 0);
2444+
timeout = internal_select_ex(s, 0, interval);
23782445
if (!timeout) {
23792446
#ifndef MS_WINDOWS
23802447
#if defined(PYOS_OS2) && !defined(PYCC_GCC)
@@ -2395,6 +2462,7 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags,
23952462
PyErr_SetString(socket_timeout, "timed out");
23962463
return -1;
23972464
}
2465+
END_SELECT_LOOP(s)
23982466
if (n < 0) {
23992467
s->errorhandler();
24002468
return -1;
@@ -2532,22 +2600,24 @@ sock_send(PySocketSockObject *s, PyObject *args)
25322600
buf = pbuf.buf;
25332601
len = pbuf.len;
25342602

2603+
BEGIN_SELECT_LOOP(s)
25352604
Py_BEGIN_ALLOW_THREADS
2536-
timeout = internal_select(s, 1);
2605+
timeout = internal_select_ex(s, 1, interval);
25372606
if (!timeout)
25382607
#ifdef __VMS
25392608
n = sendsegmented(s->sock_fd, buf, len, flags);
25402609
#else
25412610
n = send(s->sock_fd, buf, len, flags);
25422611
#endif
25432612
Py_END_ALLOW_THREADS
2544-
2545-
PyBuffer_Release(&pbuf);
2546-
25472613
if (timeout == 1) {
2614+
PyBuffer_Release(&pbuf);
25482615
PyErr_SetString(socket_timeout, "timed out");
25492616
return NULL;
25502617
}
2618+
END_SELECT_LOOP(s)
2619+
2620+
PyBuffer_Release(&pbuf);
25512621
if (n < 0)
25522622
return s->errorhandler();
25532623
return PyLong_FromSsize_t(n);
@@ -2667,17 +2737,20 @@ sock_sendto(PySocketSockObject *s, PyObject *args)
26672737
return NULL;
26682738
}
26692739

2740+
BEGIN_SELECT_LOOP(s)
26702741
Py_BEGIN_ALLOW_THREADS
2671-
timeout = internal_select(s, 1);
2742+
timeout = internal_select_ex(s, 1, interval);
26722743
if (!timeout)
26732744
n = sendto(s->sock_fd, buf, len, flags, SAS2SA(&addrbuf), addrlen);
26742745
Py_END_ALLOW_THREADS
26752746

2676-
PyBuffer_Release(&pbuf);
26772747
if (timeout == 1) {
2748+
PyBuffer_Release(&pbuf);
26782749
PyErr_SetString(socket_timeout, "timed out");
26792750
return NULL;
26802751
}
2752+
END_SELECT_LOOP(s)
2753+
PyBuffer_Release(&pbuf);
26812754
if (n < 0)
26822755
return s->errorhandler();
26832756
return PyLong_FromSsize_t(n);

0 commit comments

Comments
 (0)