Commit 27fe494d authored by Victor Stinner's avatar Victor Stinner

Issue #22117: The socket module uses _PyTime_t timestamp for timeouts

parent 15ab3ff1
...@@ -460,7 +460,7 @@ static PyTypeObject sock_type; ...@@ -460,7 +460,7 @@ static PyTypeObject sock_type;
#else #else
/* If there's no timeout left, we don't have to call select, so it's a safe, /* If there's no timeout left, we don't have to call select, so it's a safe,
* little white lie. */ * little white lie. */
#define IS_SELECTABLE(s) (_PyIsSelectable_fd((s)->sock_fd) || (s)->sock_timeout <= 0.0) #define IS_SELECTABLE(s) (_PyIsSelectable_fd((s)->sock_fd) || (s)->sock_timeout <= 0)
#endif #endif
static PyObject* static PyObject*
...@@ -597,9 +597,17 @@ internal_setblocking(PySocketSockObject *s, int block) ...@@ -597,9 +597,17 @@ internal_setblocking(PySocketSockObject *s, int block)
after they've reacquired the interpreter lock. after they've reacquired the interpreter lock.
Returns 1 on timeout, -1 on error, 0 otherwise. */ Returns 1 on timeout, -1 on error, 0 otherwise. */
static int static int
internal_select_ex(PySocketSockObject *s, int writing, double interval) internal_select_ex(PySocketSockObject *s, int writing, _PyTime_t interval)
{ {
int n; int n;
#ifdef HAVE_POLL
struct pollfd pollfd;
_PyTime_t timeout;
int timeout_int;
#else
fd_set fds;
struct timeval tv;
#endif
#ifdef WITH_THREAD #ifdef WITH_THREAD
/* must be called with the GIL held */ /* must be called with the GIL held */
...@@ -607,7 +615,7 @@ internal_select_ex(PySocketSockObject *s, int writing, double interval) ...@@ -607,7 +615,7 @@ internal_select_ex(PySocketSockObject *s, int writing, double interval)
#endif #endif
/* Nothing to do unless we're in timeout mode (not non-blocking) */ /* Nothing to do unless we're in timeout mode (not non-blocking) */
if (s->sock_timeout <= 0.0) if (s->sock_timeout <= 0)
return 0; return 0;
/* Guard against closed socket */ /* Guard against closed socket */
...@@ -615,33 +623,28 @@ internal_select_ex(PySocketSockObject *s, int writing, double interval) ...@@ -615,33 +623,28 @@ internal_select_ex(PySocketSockObject *s, int writing, double interval)
return 0; return 0;
/* Handling this condition here simplifies the select loops */ /* Handling this condition here simplifies the select loops */
if (interval < 0.0) if (interval < 0)
return 1; return 1;
/* Prefer poll, if available, since you can poll() any fd /* Prefer poll, if available, since you can poll() any fd
* which can't be done with select(). */ * which can't be done with select(). */
#ifdef HAVE_POLL #ifdef HAVE_POLL
{
struct pollfd pollfd;
int timeout;
pollfd.fd = s->sock_fd; pollfd.fd = s->sock_fd;
pollfd.events = writing ? POLLOUT : POLLIN; pollfd.events = writing ? POLLOUT : POLLIN;
/* s->sock_timeout is in seconds, timeout in ms */ /* s->sock_timeout is in seconds, timeout in ms */
timeout = (int)(interval * 1000 + 0.5); timeout = _PyTime_AsMilliseconds(interval, _PyTime_ROUND_UP);
assert(timeout <= INT_MAX);
timeout_int = (int)timeout;
Py_BEGIN_ALLOW_THREADS; Py_BEGIN_ALLOW_THREADS;
n = poll(&pollfd, 1, timeout); n = poll(&pollfd, 1, timeout_int);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
}
#else #else
{ /* conversion was already checked for overflow when
/* Construct the arguments to select */ the timeout was set */
fd_set fds; (void)_PyTime_AsTimeval(interval, &tv, _PyTime_ROUND_UP);
struct timeval tv;
tv.tv_sec = (int)interval;
tv.tv_usec = (int)((interval - tv.tv_sec) * 1e6);
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(s->sock_fd, &fds); FD_SET(s->sock_fd, &fds);
...@@ -654,7 +657,6 @@ internal_select_ex(PySocketSockObject *s, int writing, double interval) ...@@ -654,7 +657,6 @@ internal_select_ex(PySocketSockObject *s, int writing, double interval)
n = select(Py_SAFE_DOWNCAST(s->sock_fd+1, SOCKET_T, int), n = select(Py_SAFE_DOWNCAST(s->sock_fd+1, SOCKET_T, int),
&fds, NULL, NULL, &tv); &fds, NULL, NULL, &tv);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
}
#endif #endif
if (n < 0) if (n < 0)
...@@ -694,14 +696,11 @@ internal_select(PySocketSockObject *s, int writing) ...@@ -694,14 +696,11 @@ internal_select(PySocketSockObject *s, int writing)
#define BEGIN_SELECT_LOOP(s) \ #define BEGIN_SELECT_LOOP(s) \
{ \ { \
_PyTime_timeval now, deadline = {0, 0}; \ _PyTime_t deadline = 0; \
double interval = s->sock_timeout; \ _PyTime_t interval = s->sock_timeout; \
int has_timeout = s->sock_timeout > 0.0; \ int has_timeout = (s->sock_timeout > 0); \
if (has_timeout) { \ if (has_timeout) \
_PyTime_monotonic(&now); \ deadline = _PyTime_GetMonotonicClock() + interval; \
deadline = now; \
_PyTime_AddDouble(&deadline, s->sock_timeout, _PyTime_ROUND_UP); \
} \
while (1) { \ while (1) { \
errno = 0; \ errno = 0; \
...@@ -709,14 +708,13 @@ internal_select(PySocketSockObject *s, int writing) ...@@ -709,14 +708,13 @@ internal_select(PySocketSockObject *s, int writing)
if (!has_timeout || \ if (!has_timeout || \
(!CHECK_ERRNO(EWOULDBLOCK) && !CHECK_ERRNO(EAGAIN))) \ (!CHECK_ERRNO(EWOULDBLOCK) && !CHECK_ERRNO(EAGAIN))) \
break; \ break; \
_PyTime_monotonic(&now); \ interval = deadline - _PyTime_GetMonotonicClock(); \
interval = _PyTime_INTERVAL(now, deadline); \
} \ } \
} \ } \
/* Initialize a new socket object. */ /* Initialize a new socket object. */
static double defaulttimeout = -1.0; /* Default timeout for new sockets */ static _PyTime_t defaulttimeout = -1; /* Default timeout for new sockets */
static void static void
init_sockobject(PySocketSockObject *s, init_sockobject(PySocketSockObject *s,
...@@ -730,12 +728,12 @@ init_sockobject(PySocketSockObject *s, ...@@ -730,12 +728,12 @@ init_sockobject(PySocketSockObject *s,
s->errorhandler = &set_error; s->errorhandler = &set_error;
#ifdef SOCK_NONBLOCK #ifdef SOCK_NONBLOCK
if (type & SOCK_NONBLOCK) if (type & SOCK_NONBLOCK)
s->sock_timeout = 0.0; s->sock_timeout = 0;
else else
#endif #endif
{ {
s->sock_timeout = defaulttimeout; s->sock_timeout = defaulttimeout;
if (defaulttimeout >= 0.0) if (defaulttimeout >= 0)
internal_setblocking(s, 0); internal_setblocking(s, 0);
} }
...@@ -2168,7 +2166,7 @@ sock_setblocking(PySocketSockObject *s, PyObject *arg) ...@@ -2168,7 +2166,7 @@ sock_setblocking(PySocketSockObject *s, PyObject *arg)
if (block == -1 && PyErr_Occurred()) if (block == -1 && PyErr_Occurred())
return NULL; return NULL;
s->sock_timeout = block ? -1.0 : 0.0; s->sock_timeout = block ? -1 : 0;
internal_setblocking(s, block); internal_setblocking(s, block);
Py_INCREF(Py_None); Py_INCREF(Py_None);
...@@ -2182,6 +2180,43 @@ Set the socket to blocking (flag is true) or non-blocking (false).\n\ ...@@ -2182,6 +2180,43 @@ Set the socket to blocking (flag is true) or non-blocking (false).\n\
setblocking(True) is equivalent to settimeout(None);\n\ setblocking(True) is equivalent to settimeout(None);\n\
setblocking(False) is equivalent to settimeout(0.0)."); setblocking(False) is equivalent to settimeout(0.0).");
static int
socket_parse_timeout(_PyTime_t *timeout, PyObject *timeout_obj)
{
#ifdef MS_WINDOWS
struct timeval tv;
#endif
int overflow = 0;
if (timeout_obj == Py_None) {
*timeout = -1;
return 0;
}
if (_PyTime_FromSecondsObject(timeout, timeout_obj, _PyTime_ROUND_UP) < 0)
return -1;
if (*timeout < 0) {
PyErr_SetString(PyExc_ValueError, "Timeout value out of range");
return -1;
}
#ifdef MS_WINDOWS
overflow = (_PyTime_AsTimeval(timeout, &tv, _PyTime_ROUND_UP) < 0);
#endif
#ifndef HAVE_POLL
timeout = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_UP);
overflow = (timeout > INT_MAX);
#endif
if (overflow) {
PyErr_SetString(PyExc_OverflowError,
"timeout doesn't fit into C timeval");
return -1;
}
return 0;
}
/* s.settimeout(timeout) method. Argument: /* s.settimeout(timeout) method. Argument:
None -- no timeout, blocking mode; same as setblocking(True) None -- no timeout, blocking mode; same as setblocking(True)
0.0 -- non-blocking mode; same as setblocking(False) 0.0 -- non-blocking mode; same as setblocking(False)
...@@ -2191,22 +2226,13 @@ setblocking(False) is equivalent to settimeout(0.0)."); ...@@ -2191,22 +2226,13 @@ setblocking(False) is equivalent to settimeout(0.0).");
static PyObject * static PyObject *
sock_settimeout(PySocketSockObject *s, PyObject *arg) sock_settimeout(PySocketSockObject *s, PyObject *arg)
{ {
double timeout; _PyTime_t timeout;
if (arg == Py_None) if (socket_parse_timeout(&timeout, arg) < 0)
timeout = -1.0;
else {
timeout = PyFloat_AsDouble(arg);
if (timeout < 0.0) {
if (!PyErr_Occurred())
PyErr_SetString(PyExc_ValueError,
"Timeout value out of range");
return NULL; return NULL;
}
}
s->sock_timeout = timeout; s->sock_timeout = timeout;
internal_setblocking(s, timeout < 0.0); internal_setblocking(s, timeout < 0);
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
...@@ -2225,12 +2251,14 @@ Setting a timeout of zero is the same as setblocking(0)."); ...@@ -2225,12 +2251,14 @@ Setting a timeout of zero is the same as setblocking(0).");
static PyObject * static PyObject *
sock_gettimeout(PySocketSockObject *s) sock_gettimeout(PySocketSockObject *s)
{ {
if (s->sock_timeout < 0.0) { if (s->sock_timeout < 0) {
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
else else {
return PyFloat_FromDouble(s->sock_timeout); double seconds = _PyTime_AsSecondsDouble(s->sock_timeout);
return PyFloat_FromDouble(seconds);
}
} }
PyDoc_STRVAR(gettimeout_doc, PyDoc_STRVAR(gettimeout_doc,
...@@ -2409,27 +2437,28 @@ internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen, ...@@ -2409,27 +2437,28 @@ internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen,
{ {
int res, timeout; int res, timeout;
timeout = 0; timeout = 0;
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
res = connect(s->sock_fd, addr, addrlen); res = connect(s->sock_fd, addr, addrlen);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (s->sock_timeout > 0.0 if (s->sock_timeout > 0
&& res < 0 && WSAGetLastError() == WSAEWOULDBLOCK && res < 0 && WSAGetLastError() == WSAEWOULDBLOCK
&& IS_SELECTABLE(s)) { && IS_SELECTABLE(s)) {
/* This is a mess. Best solution: trust select */ /* This is a mess. Best solution: trust select */
fd_set fds; fd_set fds;
fd_set fds_exc; fd_set fds_exc;
struct timeval tv; struct timeval tv;
int conv;
/* conversion was already checked for overflow when
the timeout was set */
(void)_PyTime_AsTimeval(s->sock_timeout, &tv, _PyTime_ROUND_UP);
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
tv.tv_sec = (int)s->sock_timeout;
tv.tv_usec = (int)((s->sock_timeout - tv.tv_sec) * 1e6);
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(s->sock_fd, &fds); FD_SET(s->sock_fd, &fds);
FD_ZERO(&fds_exc); FD_ZERO(&fds_exc);
...@@ -2469,7 +2498,7 @@ internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen, ...@@ -2469,7 +2498,7 @@ internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen,
#else #else
if (s->sock_timeout > 0.0 if (s->sock_timeout > 0
&& res < 0 && errno == EINPROGRESS && IS_SELECTABLE(s)) { && res < 0 && errno == EINPROGRESS && IS_SELECTABLE(s)) {
timeout = internal_select(s, 1); timeout = internal_select(s, 1);
...@@ -2498,6 +2527,7 @@ internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen, ...@@ -2498,6 +2527,7 @@ internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen,
#endif #endif
*timeoutp = timeout; *timeoutp = timeout;
assert(res >= 0);
return res; return res;
} }
...@@ -2520,8 +2550,11 @@ sock_connect(PySocketSockObject *s, PyObject *addro) ...@@ -2520,8 +2550,11 @@ sock_connect(PySocketSockObject *s, PyObject *addro)
PyErr_SetString(socket_timeout, "timed out"); PyErr_SetString(socket_timeout, "timed out");
return NULL; return NULL;
} }
if (res != 0) if (res < 0)
return NULL;
if (res != 0) {
return s->errorhandler(); return s->errorhandler();
}
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
...@@ -2548,6 +2581,9 @@ sock_connect_ex(PySocketSockObject *s, PyObject *addro) ...@@ -2548,6 +2581,9 @@ sock_connect_ex(PySocketSockObject *s, PyObject *addro)
res = internal_connect(s, SAS2SA(&addrbuf), addrlen, &timeout); res = internal_connect(s, SAS2SA(&addrbuf), addrlen, &timeout);
if (res < 0)
return NULL;
/* Signals are not errors (though they may raise exceptions). Adapted /* Signals are not errors (though they may raise exceptions). Adapted
from PyErr_SetFromErrnoWithFilenameObject(). */ from PyErr_SetFromErrnoWithFilenameObject(). */
if (res == EINTR && PyErr_CheckSignals()) if (res == EINTR && PyErr_CheckSignals())
...@@ -3967,10 +4003,14 @@ static PyMemberDef sock_memberlist[] = { ...@@ -3967,10 +4003,14 @@ static PyMemberDef sock_memberlist[] = {
{"family", T_INT, offsetof(PySocketSockObject, sock_family), READONLY, "the socket family"}, {"family", T_INT, offsetof(PySocketSockObject, sock_family), READONLY, "the socket family"},
{"type", T_INT, offsetof(PySocketSockObject, sock_type), READONLY, "the socket type"}, {"type", T_INT, offsetof(PySocketSockObject, sock_type), READONLY, "the socket type"},
{"proto", T_INT, offsetof(PySocketSockObject, sock_proto), READONLY, "the socket protocol"}, {"proto", T_INT, offsetof(PySocketSockObject, sock_proto), READONLY, "the socket protocol"},
{"timeout", T_DOUBLE, offsetof(PySocketSockObject, sock_timeout), READONLY, "the socket timeout"},
{0}, {0},
}; };
static PyGetSetDef sock_getsetlist[] = {
{"timeout", (getter)sock_gettimeout, NULL, PyDoc_STR("the socket timeout")},
{NULL} /* sentinel */
};
/* Deallocate a socket object in response to the last Py_DECREF(). /* Deallocate a socket object in response to the last Py_DECREF().
First close the file description. */ First close the file description. */
...@@ -4034,7 +4074,7 @@ sock_new(PyTypeObject *type, PyObject *args, PyObject *kwds) ...@@ -4034,7 +4074,7 @@ sock_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
new = type->tp_alloc(type, 0); new = type->tp_alloc(type, 0);
if (new != NULL) { if (new != NULL) {
((PySocketSockObject *)new)->sock_fd = -1; ((PySocketSockObject *)new)->sock_fd = -1;
((PySocketSockObject *)new)->sock_timeout = -1.0; ((PySocketSockObject *)new)->sock_timeout = -1;
((PySocketSockObject *)new)->errorhandler = &set_error; ((PySocketSockObject *)new)->errorhandler = &set_error;
} }
return new; return new;
...@@ -4217,7 +4257,7 @@ static PyTypeObject sock_type = { ...@@ -4217,7 +4257,7 @@ static PyTypeObject sock_type = {
0, /* tp_iternext */ 0, /* tp_iternext */
sock_methods, /* tp_methods */ sock_methods, /* tp_methods */
sock_memberlist, /* tp_members */ sock_memberlist, /* tp_members */
0, /* tp_getset */ sock_getsetlist, /* tp_getset */
0, /* tp_base */ 0, /* tp_base */
0, /* tp_dict */ 0, /* tp_dict */
0, /* tp_descr_get */ 0, /* tp_descr_get */
...@@ -5540,12 +5580,14 @@ Get host and port for a sockaddr."); ...@@ -5540,12 +5580,14 @@ Get host and port for a sockaddr.");
static PyObject * static PyObject *
socket_getdefaulttimeout(PyObject *self) socket_getdefaulttimeout(PyObject *self)
{ {
if (defaulttimeout < 0.0) { if (defaulttimeout < 0) {
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
else else {
return PyFloat_FromDouble(defaulttimeout); double seconds = _PyTime_AsSecondsDouble(defaulttimeout);
return PyFloat_FromDouble(seconds);
}
} }
PyDoc_STRVAR(getdefaulttimeout_doc, PyDoc_STRVAR(getdefaulttimeout_doc,
...@@ -5558,19 +5600,10 @@ When the socket module is first imported, the default is None."); ...@@ -5558,19 +5600,10 @@ When the socket module is first imported, the default is None.");
static PyObject * static PyObject *
socket_setdefaulttimeout(PyObject *self, PyObject *arg) socket_setdefaulttimeout(PyObject *self, PyObject *arg)
{ {
double timeout; _PyTime_t timeout;
if (arg == Py_None) if (socket_parse_timeout(&timeout, arg) < 0)
timeout = -1.0;
else {
timeout = PyFloat_AsDouble(arg);
if (timeout < 0.0) {
if (!PyErr_Occurred())
PyErr_SetString(PyExc_ValueError,
"Timeout value out of range");
return NULL; return NULL;
}
}
defaulttimeout = timeout; defaulttimeout = timeout;
......
...@@ -174,7 +174,7 @@ typedef struct { ...@@ -174,7 +174,7 @@ typedef struct {
PyObject *(*errorhandler)(void); /* Error handler; checks PyObject *(*errorhandler)(void); /* Error handler; checks
errno, returns NULL and errno, returns NULL and
sets a Python exception */ sets a Python exception */
double sock_timeout; /* Operation timeout in seconds; _PyTime_t sock_timeout; /* Operation timeout in seconds;
0.0 means non-blocking */ 0.0 means non-blocking */
} PySocketSockObject; } PySocketSockObject;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment