diff -r c2f6b3677630 Doc/library/socket.rst --- a/Doc/library/socket.rst Wed Apr 08 18:12:53 2015 +0300 +++ b/Doc/library/socket.rst Thu Apr 09 10:29:38 2015 +0200 @@ -574,12 +574,25 @@ correspond to Unix system calls applicab Connect to a remote socket at *address*. (The format of *address* depends on the address family --- see above.) + If the connection is interrupted by a signal, the method waits until the + connection completes, or raise a :exc:`socket.timeout` on timeout, if the + signal handler doesn't raise an exception and the socket is blocking or has + a timeout. For non-blocking sockets, the method raises a :exc:`socket.error` + exception with :py:data:`~errno.EINTR` error code if the connection is + interrupted by a signal (or the exception raised by the signal handler). + .. note:: This method has historically accepted a pair of parameters for :const:`AF_INET` addresses instead of only a tuple. This was never intentional and is no longer available in Python 2.0 and later. + .. versionchanged:: 2.7.10 + The method now waits until the connection completes instead of raising a + :exc:`socket.error` exception with :py:data:`~errno.EINTR` error code if + the connection is interrupted by a signal, the signal handler doesn't + raise an exception and the socket is blocking or has a timeout. + .. method:: socket.connect_ex(address) diff -r c2f6b3677630 Modules/socketmodule.c --- a/Modules/socketmodule.c Wed Apr 08 18:12:53 2015 +0300 +++ b/Modules/socketmodule.c Thu Apr 09 10:29:38 2015 +0200 @@ -479,15 +479,24 @@ select_error(void) } #ifdef MS_WINDOWS -#ifndef WSAEAGAIN -#define WSAEAGAIN WSAEWOULDBLOCK -#endif -#define CHECK_ERRNO(expected) \ - (WSAGetLastError() == WSA ## expected) +# ifndef WSAEAGAIN +# define WSAEAGAIN WSAEWOULDBLOCK +# endif +# define SOCK_ERR(name) (WSA ## name) +# define CHECK_ERRNO(expected) \ + (WSAGetLastError() == SOCK_ERR(expected)) +# define GET_SOCK_ERROR() GetLastError() +# define SET_SOCK_ERROR(err) SetLastError(err) +# define IN_PROGRESS_ERR WSAEWOULDBLOCK #else -#define CHECK_ERRNO(expected) \ - (errno == expected) -#endif +# define SOCK_ERR(name) name +# define CHECK_ERRNO(expected) \ + (errno == expected) +# define GET_SOCK_ERROR() errno +# define SET_SOCK_ERROR(err) do { errno = err; } while (0) +# define IN_PROGRESS_ERR EINPROGRESS +#endif + /* Convenience function to raise an error according to errno and return a NULL pointer from a function. */ @@ -732,11 +741,6 @@ internal_select_ex(PySocketSockObject *s 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 @@ -2031,90 +2035,138 @@ Close the socket. It cannot be used aft static int internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen, - int *timeoutp) + int raise) { - int res, timeout; - - timeout = 0; + int res, err, wait_connect; + fd_set fds; + fd_set fds_exc; + struct timeval tv, *tvp; + socklen_t err_size; + int has_timeout; + double timeout, deadline = 0; + + Py_BEGIN_ALLOW_THREADS res = connect(s->sock_fd, addr, addrlen); - -#ifdef MS_WINDOWS - - if (s->sock_timeout > 0.0) { - if (res < 0 && WSAGetLastError() == WSAEWOULDBLOCK && - IS_SELECTABLE(s)) { - /* This is a mess. Best solution: trust select */ - fd_set fds; - fd_set fds_exc; - struct timeval tv; - tv.tv_sec = (int)s->sock_timeout; - tv.tv_usec = (int)((s->sock_timeout - tv.tv_sec) * 1e6); - FD_ZERO(&fds); - FD_SET(s->sock_fd, &fds); - FD_ZERO(&fds_exc); - FD_SET(s->sock_fd, &fds_exc); - res = select(s->sock_fd+1, NULL, &fds, &fds_exc, &tv); - if (res == 0) { - res = WSAEWOULDBLOCK; - timeout = 1; - } else if (res > 0) { - if (FD_ISSET(s->sock_fd, &fds)) - /* The socket is in the writeable set - this - means connected */ - res = 0; - else { - /* As per MS docs, we need to call getsockopt() - to get the underlying error */ - int res_size = sizeof res; - /* It must be in the exception set */ - assert(FD_ISSET(s->sock_fd, &fds_exc)); - if (0 == getsockopt(s->sock_fd, SOL_SOCKET, SO_ERROR, - (char *)&res, &res_size)) - /* getsockopt also clears WSAGetLastError, - so reset it back. */ - WSASetLastError(res); - else - res = WSAGetLastError(); - } + Py_END_ALLOW_THREADS + + if (!res) { + /* connect() succeeded, the socket is connected */ + return 0; + } + /* connect() failed */ + + /* save error, PyErr_CheckSignals() can replace it */ + err = GET_SOCK_ERROR(); + if (err == SOCK_ERR(EINTR)) { + if (PyErr_CheckSignals()) + return -1; + + /* Issue #23618: when connect() fails with EINTR, the connection is + running asynchronously. + + If the socket is blocking or has a timeout, wait until the + connection completes, fails or timed out using select(), and then + get the connection status using getsockopt(SO_ERROR). + + If the socket is non-blocking, raise InterruptedError. The caller is + responsible to wait until the connection completes, fails or timed + out (it's the case in asyncio for example). */ + wait_connect = (s->sock_timeout != 0 && IS_SELECTABLE(s)); + } + else { + wait_connect = (s->sock_timeout > 0.0 && err == IN_PROGRESS_ERR + && IS_SELECTABLE(s)); + } + + if (!wait_connect) + goto sock_err; + + timeout = s->sock_timeout; + has_timeout = (timeout > 0); + if (has_timeout) + deadline = _PyTime_FloatTime() + s->sock_timeout; + + /* inner loop to retry select() on EINTR */ + while (1) { + FD_ZERO(&fds); + FD_SET(s->sock_fd, &fds); + FD_ZERO(&fds_exc); + FD_SET(s->sock_fd, &fds_exc); + + if (0 <= timeout) { + tv.tv_sec = (int)timeout; + tv.tv_usec = (int)((timeout - tv.tv_sec) * 1e6); + tvp = &tv; + } + else + tvp = NULL; + + /* wait until the socket is writable or got an error */ + Py_BEGIN_ALLOW_THREADS + res = select(s->sock_fd+1, NULL, &fds, &fds_exc, tvp); + Py_END_ALLOW_THREADS + + if (!(res < 0 && CHECK_ERRNO(EINTR))) + break; + + /* select() was interrupted by a signal */ + if (PyErr_CheckSignals()) + return -1; + + if (has_timeout) { + timeout = deadline - _PyTime_FloatTime(); + if (timeout < 0) { + res = 0; + break; } - /* else if (res < 0) an error occurred */ } + + /* retry select() with the recomputed timeout */ } - if (res < 0) - res = WSAGetLastError(); - -#else - - if (s->sock_timeout > 0.0) { - if (res < 0 && errno == EINPROGRESS && IS_SELECTABLE(s)) { - timeout = internal_select(s, 1); - if (timeout == 0) { - /* Bug #1019808: in case of an EINPROGRESS, - use getsockopt(SO_ERROR) to get the real - error. */ - socklen_t res_size = sizeof res; - (void)getsockopt(s->sock_fd, SOL_SOCKET, - SO_ERROR, &res, &res_size); - if (res == EISCONN) - res = 0; - errno = res; - } - else if (timeout == -1) { - res = errno; /* had error */ - } - else - res = EWOULDBLOCK; /* timed out */ + if (res < 0) { + /* select() failed */ + err = GET_SOCK_ERROR(); + goto sock_err; + } + + if (res == 0) { + /* timed out */ + if (raise) { + PyErr_SetString(socket_timeout, "timed out"); + return -1; } + else + return SOCK_ERR(EWOULDBLOCK); } - if (res < 0) - res = errno; - -#endif - *timeoutp = timeout; - - return res; + /* get SO_ERROR to check if the socket is connected */ + err_size = sizeof err; + if (getsockopt(s->sock_fd, SOL_SOCKET, SO_ERROR, + (char *)&err, &err_size)) { + /* getsockopt() failed */ + err = GET_SOCK_ERROR(); + goto sock_err; + } + + if (err == EISCONN) { + /* the socket is connected */ + return 0; + } + + if (err != 0) + goto sock_err; + + /* the socket is connected */ + return 0; + +sock_err: + if (raise) { + SET_SOCK_ERROR(err); + s->errorhandler(); + return -1; + } + return err; } /* s.connect(sockaddr) method */ @@ -2125,21 +2177,14 @@ sock_connect(PySocketSockObject *s, PyOb sock_addr_t addrbuf; int addrlen; int res; - int timeout; if (!getsockaddrarg(s, addro, SAS2SA(&addrbuf), &addrlen)) return NULL; - Py_BEGIN_ALLOW_THREADS - res = internal_connect(s, SAS2SA(&addrbuf), addrlen, &timeout); - Py_END_ALLOW_THREADS - - if (timeout == 1) { - PyErr_SetString(socket_timeout, "timed out"); + res = internal_connect(s, SAS2SA(&addrbuf), addrlen, 1); + if (res < 0) return NULL; - } - if (res != 0) - return s->errorhandler(); + Py_INCREF(Py_None); return Py_None; } @@ -2159,21 +2204,13 @@ sock_connect_ex(PySocketSockObject *s, P sock_addr_t addrbuf; int addrlen; int res; - int timeout; if (!getsockaddrarg(s, addro, SAS2SA(&addrbuf), &addrlen)) return NULL; - Py_BEGIN_ALLOW_THREADS - res = internal_connect(s, SAS2SA(&addrbuf), addrlen, &timeout); - Py_END_ALLOW_THREADS - - /* Signals are not errors (though they may raise exceptions). Adapted - from PyErr_SetFromErrnoWithFilenameObject(). */ -#ifdef EINTR - if (res == EINTR && PyErr_CheckSignals()) + res = internal_connect(s, SAS2SA(&addrbuf), addrlen, 0); + if (res < 0) return NULL; -#endif return PyInt_FromLong((long) res); }