diff options
-rw-r--r-- | Doc/lib/libsocket.tex | 43 | ||||
-rw-r--r-- | Lib/test/test_socket.py | 5 | ||||
-rw-r--r-- | Lib/test/test_timeout.py | 6 | ||||
-rw-r--r-- | Modules/socketmodule.c | 277 | ||||
-rw-r--r-- | Modules/socketmodule.h | 5 |
5 files changed, 89 insertions, 247 deletions
diff --git a/Doc/lib/libsocket.tex b/Doc/lib/libsocket.tex index 54dfa6c..5ca0afc 100644 --- a/Doc/lib/libsocket.tex +++ b/Doc/lib/libsocket.tex @@ -72,8 +72,9 @@ argument types and out-of-memory conditions can be raised; errors related to socket or address semantics raise the error \exception{socket.error}. -Non-blocking mode is supported through the -\method{setblocking()} method. +Non-blocking mode is supported through +\method{setblocking()}. A generalization of this based on timeouts +is supported through \method{settimeout()}. The module \module{socket} exports the following constants and functions: @@ -284,8 +285,7 @@ checked --- subsequent operations on the object may fail if the file descriptor is invalid. This function is rarely needed, but can be used to get or set socket options on a socket passed to a program as standard input or output (such as a server started by the \UNIX{} inet -daemon). The socket is assumed to be in blocking mode without -a timeout. +daemon). The socket is assumed to be in blocking mode. Availability: \UNIX. \end{funcdesc} @@ -514,38 +514,39 @@ all sockets are in blocking mode. In non-blocking mode, if a \method{send()} call can't immediately dispose of the data, a \exception{error} exception is raised; in blocking mode, the calls block until they can proceed. +\code{s.setblocking(0)} is equivalent to \code{s.settimeout(0)}; +\code{s.setblocking(1)} is equivalent to \code{s.settimeout(None)}. \end{methoddesc} \begin{methoddesc}[socket]{settimeout}{value} -Set a timeout on blocking socket operations. Value can be a -nonnegative float expressing seconds, or \code{None}. If a float is +Set a timeout on blocking socket operations. The \var{value} argument +can be a nonnegative float expressing seconds, or \code{None}. +If a float is given, subsequent socket operations will raise an \exception{error} exception if the timeout period \var{value} has elapsed before the operation has completed. Setting a timeout of \code{None} disables timeouts on socket operations. +\code{s.settimeout(0.0)} is equivalent to \code{s.blocking(0)}; +\code{s.settimeout(None)} is equivalent to \code{s.setblocking(1)}. \versionadded{2.3} \end{methoddesc} \begin{methoddesc}[socket]{gettimeout}{} Returns the timeout in floating seconds associated with socket -operations, or \code{None} if no timeout is set. +operations, or \code{None} if no timeout is set. This reflects +the last call to \method{setblocking()} or \method{settimeout()}. \versionadded{2.3} \end{methoddesc} -Some notes on the interaction between socket blocking and timeouts: A -socket object can be in one of three modes: blocking, non-blocking, or -timout. Sockets are always created in blocking mode. In blocking -mode, operations block until complete. In non-blocking mode, -operations fail (with an error that is unfortunately system-dependent) -if they cannot be completed immediately. In timeout mode, operations -fail if they cannot be completed within the timeout specified for the -socket. - -Calling \method{settimeout()} cancels non-blocking mode as set by -\method{setblocking()}; calling \method{setblocking()} cancels a -previously set timeout. Setting the timeout to zero acts similarly -but is implemented different than setting the socket in non-blocking -mode (this could be considered a bug and may even be fixed). +Some notes on socket blocking and timeouts: A socket object can be in +one of three modes: blocking, non-blocking, or timout. Sockets are +always created in blocking mode. In blocking mode, operations block +until complete. In non-blocking mode, operations fail (with an error +that is unfortunately system-dependent) if they cannot be completed +immediately. In timeout mode, operations fail if they cannot be +completed within the timeout specified for the socket. The +\method{setblocking()} method is simply a shorthand for certain +\method{settimeout()} calls. Timeout mode internally sets the socket in non-blocking mode. The blocking and timeout modes are shared between file descriptors and diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index cbe1ec0..485e038 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -51,7 +51,10 @@ class ThreadableTest: self.queue = Queue.Queue(1) # Do some munging to start the client test. - test_method = getattr(self, '_' + self._TestCase__testMethodName) + methodname = self.id() + i = methodname.rfind('.') + methodname = methodname[i+1:] + test_method = getattr(self, '_' + methodname) self.client_thread = thread.start_new_thread( self.clientRun, (test_method,)) diff --git a/Lib/test/test_timeout.py b/Lib/test/test_timeout.py index 8a84258..7c72222 100644 --- a/Lib/test/test_timeout.py +++ b/Lib/test/test_timeout.py @@ -59,17 +59,17 @@ class CreationTestCase(unittest.TestCase): self.assertRaises(ValueError, self.sock.settimeout, -1L) self.assertRaises(ValueError, self.sock.settimeout, -1.0) - def testTimeoutThenoBlocking(self): + def testTimeoutThenBlocking(self): "Test settimeout() followed by setblocking()" self.sock.settimeout(10) self.sock.setblocking(1) self.assertEqual(self.sock.gettimeout(), None) self.sock.setblocking(0) - self.assertEqual(self.sock.gettimeout(), None) + self.assertEqual(self.sock.gettimeout(), 0.0) self.sock.settimeout(10) self.sock.setblocking(0) - self.assertEqual(self.sock.gettimeout(), None) + self.assertEqual(self.sock.gettimeout(), 0.0) self.sock.setblocking(1) self.assertEqual(self.sock.gettimeout(), None) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index ff67c3d..3896831 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -446,26 +446,6 @@ set_gaierror(int error) return NULL; } -/* For timeout errors */ -static PyObject * -timeout_err(void) -{ - PyObject *v; - -#ifdef MS_WINDOWS - v = Py_BuildValue("(is)", WSAETIMEDOUT, "Socket operation timed out"); -#else - v = Py_BuildValue("(is)", ETIMEDOUT, "Socket operation timed out"); -#endif - - if (v != NULL) { - PyErr_SetObject(socket_error, v); - Py_DECREF(v); - } - - return NULL; -} - /* Function to perform the setting of socket blocking mode internally. block = (1 | 0). */ static int @@ -508,16 +488,18 @@ internal_setblocking(PySocketSockObject *s, int block) return 1; } -/* For access to the select module to poll the socket for timeout - functionality. writing is 1 for writing, 0 for reading. - Return value: -1 if error, 0 if not ready, >= 1 if ready. - An exception is set when the return value is <= 0 (!). */ -static int +/* Do a select() on the socket, if necessary (sock_timeout > 0). + The argument writing indicates the direction. + This does not raise an exception or return a success indicator; + we'll let the actual socket call do that. */ +static void internal_select(PySocketSockObject *s, int writing) { fd_set fds; struct timeval tv; - int count; + + if (s->sock_timeout <= 0.0) + return; /* Construct the arguments to select */ tv.tv_sec = (int)s->sock_timeout; @@ -527,22 +509,9 @@ internal_select(PySocketSockObject *s, int writing) /* See if the socket is ready */ if (writing) - count = select(s->sock_fd+1, NULL, &fds, NULL, &tv); + select(s->sock_fd+1, NULL, &fds, NULL, &tv); else - count = select(s->sock_fd+1, &fds, NULL, NULL, &tv); - - /* Check for errors */ - if (count < 0) { - s->errorhandler(); - return -1; - } - - /* Set the error if the timeout has elapsed, i.e, we were not - polled. */ - if (count == 0) - timeout_err(); - - return count; + select(s->sock_fd+1, &fds, NULL, NULL, &tv); } /* Initialize a new socket object. */ @@ -558,7 +527,6 @@ init_sockobject(PySocketSockObject *s, s->sock_family = family; s->sock_type = type; s->sock_proto = proto; - s->sock_blocking = 1; /* Start in blocking mode */ s->sock_timeout = -1.0; /* Start without timeout */ s->errorhandler = &set_error; @@ -997,45 +965,11 @@ sock_accept(PySocketSockObject *s) return NULL; memset(addrbuf, 0, addrlen); - errno = 0; /* Reset indicator for use with timeout behavior */ - Py_BEGIN_ALLOW_THREADS + internal_select(s, 0); newfd = accept(s->sock_fd, (struct sockaddr *) addrbuf, &addrlen); Py_END_ALLOW_THREADS - if (s->sock_timeout >= 0.0) { -#ifdef MS_WINDOWS - if (newfd == INVALID_SOCKET) - if (!s->sock_blocking) - return s->errorhandler(); - /* Check if we have a true failure - for a blocking socket */ - if (errno != WSAEWOULDBLOCK) - return s->errorhandler(); -#else - if (newfd < 0) { - if (!s->sock_blocking) - return s->errorhandler(); - /* Check if we have a true failure - for a blocking socket */ - if (errno != EAGAIN && errno != EWOULDBLOCK) - return s->errorhandler(); - } -#endif - - /* try waiting the timeout period */ - if (internal_select(s, 0) <= 0) - return NULL; - - Py_BEGIN_ALLOW_THREADS - newfd = accept(s->sock_fd, - (struct sockaddr *)addrbuf, - &addrlen); - Py_END_ALLOW_THREADS - } - - /* At this point, we really have an error, whether using timeout - behavior or regular socket behavior */ #ifdef MS_WINDOWS if (newfd == INVALID_SOCKET) #else @@ -1074,7 +1008,10 @@ Wait for an incoming connection. Return a new socket representing the\n\ connection, and the address of the client. For IP sockets, the address\n\ info is a pair (hostaddr, port)."; -/* s.setblocking(1 | 0) method */ +/* s.setblocking(flag) method. Argument: + False -- non-blocking mode; same as settimeout(0) + True -- blocking mode; same as settimeout(None) +*/ static PyObject * sock_setblocking(PySocketSockObject *s, PyObject *arg) @@ -1085,8 +1022,7 @@ sock_setblocking(PySocketSockObject *s, PyObject *arg) if (block == -1 && PyErr_Occurred()) return NULL; - s->sock_blocking = block; - s->sock_timeout = -1.0; /* Always clear the timeout */ + s->sock_timeout = block ? -1.0 : 0.0; internal_setblocking(s, block); Py_INCREF(Py_None); @@ -1097,44 +1033,34 @@ static char setblocking_doc[] = "setblocking(flag)\n\ \n\ Set the socket to blocking (flag is true) or non-blocking (false).\n\ -This uses the FIONBIO ioctl with the O_NDELAY flag."; - -/* s.settimeout(None | float) method. - Causes an exception to be raised when the given time has - elapsed when performing a blocking socket operation. */ +setblocking(True) is equivalent to settimeout(None);\n\ +setblocking(False) is equivalent to settimeout(0.0)."; + +/* s.settimeout(timeout) method. Argument: + None -- no timeout, blocking mode; same as setblocking(True) + 0.0 -- non-blocking mode; same as setblocking(False) + > 0 -- timeout mode; operations time out after timeout seconds + < 0 -- illegal; raises an exception +*/ static PyObject * sock_settimeout(PySocketSockObject *s, PyObject *arg) { - double value; + double timeout; if (arg == Py_None) - value = -1.0; + timeout = -1.0; else { - value = PyFloat_AsDouble(arg); - if (value < 0.0) { + timeout = PyFloat_AsDouble(arg); + if (timeout < 0.0) { if (!PyErr_Occurred()) PyErr_SetString(PyExc_ValueError, - "Invalid timeout value"); + "Timeout value out of range"); return NULL; } } - s->sock_timeout = value; - - /* The semantics of setting socket timeouts are: - If you settimeout(!=None): - The actual socket gets put in non-blocking mode and the select - is used to control timeouts. - Else if you settimeout(None) [then value is -1.0]: - The old behavior is used AND automatically, the socket is set - to blocking mode. That means that someone who was doing - non-blocking stuff before, sets a timeout, and then unsets - one, will have to call setblocking(0) again if he wants - non-blocking stuff. This makes sense because timeout stuff is - blocking by nature. */ - internal_setblocking(s, value < 0.0); - - s->sock_blocking = 1; /* Always negate setblocking() */ + s->sock_timeout = timeout; + internal_setblocking(s, timeout < 0.0); Py_INCREF(Py_None); return Py_None; @@ -1143,8 +1069,10 @@ sock_settimeout(PySocketSockObject *s, PyObject *arg) static char settimeout_doc[] = "settimeout(timeout)\n\ \n\ -Set a timeout on blocking socket operations. 'timeout' can be a float,\n\ -giving seconds, or None. Setting a timeout of None disables timeout."; +Set a timeout on socket operations. 'timeout' can be a float,\n\ +giving in seconds, or None. Setting a timeout of None disables\n\ +the timeout feature and is equivalent to setblocking(1).\n\ +Setting a timeout of zero is the same as setblocking(0)."; /* s.gettimeout() method. Returns the timeout associated with a socket. */ @@ -1355,50 +1283,20 @@ sock_connect(PySocketSockObject *s, PyObject *addro) if (!getsockaddrarg(s, addro, &addr, &addrlen)) return NULL; - errno = 0; /* Reset the err indicator for use with timeouts */ - Py_BEGIN_ALLOW_THREADS - res = connect(s->sock_fd, addr, addrlen); - Py_END_ALLOW_THREADS - - if (s->sock_timeout >= 0.0) { - if (res < 0) { - /* Return if we're already connected */ -#ifdef MS_WINDOWS - if (errno == WSAEINVAL || errno == WSAEISCONN) -#else - if (errno == EISCONN) -#endif - goto connected; - - /* Check if we have an error */ - if (!s->sock_blocking) - return s->errorhandler(); - /* Check if we have a true failure - for a blocking socket */ -#ifdef MS_WINDOWS - if (errno != WSAEWOULDBLOCK) -#else - if (errno != EINPROGRESS && errno != EALREADY && - errno != EWOULDBLOCK) -#endif - return s->errorhandler(); - } - - /* Check if we're ready for the connect via select */ - if (internal_select(s, 1) <= 0) - return NULL; - - /* Complete the connection now */ - Py_BEGIN_ALLOW_THREADS + if (s->sock_timeout > 0.0) { res = connect(s->sock_fd, addr, addrlen); - Py_END_ALLOW_THREADS + if (res == EINPROGRESS) { + internal_select(s, 1); + res = connect(s->sock_fd, addr, addrlen); + } } + else + res = connect(s->sock_fd, addr, addrlen); + Py_END_ALLOW_THREADS if (res < 0) return s->errorhandler(); - -connected: Py_INCREF(Py_None); return Py_None; } @@ -1422,47 +1320,18 @@ sock_connect_ex(PySocketSockObject *s, PyObject *addro) if (!getsockaddrarg(s, addro, &addr, &addrlen)) return NULL; - errno = 0; /* Reset the err indicator for use with timeouts */ - Py_BEGIN_ALLOW_THREADS - res = connect(s->sock_fd, addr, addrlen); - Py_END_ALLOW_THREADS - - if (s->sock_timeout >= 0.0) { - if (res < 0) { - /* Return if we're already connected */ -#ifdef MS_WINDOWS - if (errno == WSAEINVAL || errno == WSAEISCONN) -#else - if (errno == EISCONN) -#endif - goto conex_finally; - - /* Check if we have an error */ - if (!s->sock_blocking) - goto conex_finally; - /* Check if we have a true failure - for a blocking socket */ -#ifdef MS_WINDOWS - if (errno != WSAEWOULDBLOCK) -#else - if (errno != EINPROGRESS && errno != EALREADY && - errno != EWOULDBLOCK) -#endif - goto conex_finally; - } - - /* Check if we're ready for the connect via select */ - if (internal_select(s, 1) <= 0) - return NULL; - - /* Complete the connection now */ - Py_BEGIN_ALLOW_THREADS + if (s->sock_timeout > 0.0) { res = connect(s->sock_fd, addr, addrlen); - Py_END_ALLOW_THREADS + if (res == EINPROGRESS) { + internal_select(s, 1); + res = connect(s->sock_fd, addr, addrlen); + } } + else + res = connect(s->sock_fd, addr, addrlen); + Py_END_ALLOW_THREADS -conex_finally: if (res != 0) { #ifdef MS_WINDOWS res = WSAGetLastError(); @@ -1683,7 +1552,7 @@ sock_recv(PySocketSockObject *s, PyObject *args) if (len < 0) { PyErr_SetString(PyExc_ValueError, - "negative buffersize in connect"); + "negative buffersize in recv"); return NULL; } @@ -1691,14 +1560,8 @@ sock_recv(PySocketSockObject *s, PyObject *args) if (buf == NULL) return NULL; - if (s->sock_timeout >= 0.0) { - if (s->sock_blocking) { - if (internal_select(s, 0) <= 0) - return NULL; - } - } - Py_BEGIN_ALLOW_THREADS + internal_select(s, 0); n = recv(s->sock_fd, PyString_AS_STRING(buf), len, flags); Py_END_ALLOW_THREADS @@ -1741,15 +1604,9 @@ sock_recvfrom(PySocketSockObject *s, PyObject *args) if (buf == NULL) return NULL; - if (s->sock_timeout >= 0.0) { - if (s->sock_blocking) { - if (internal_select(s, 0) <= 0) - return NULL; - } - } - Py_BEGIN_ALLOW_THREADS memset(addrbuf, 0, addrlen); + internal_select(s, 0); n = recvfrom(s->sock_fd, PyString_AS_STRING(buf), len, flags, #ifndef MS_WINDOWS #if defined(PYOS_OS2) && !defined(PYCC_GCC) @@ -1799,14 +1656,8 @@ sock_send(PySocketSockObject *s, PyObject *args) if (!PyArg_ParseTuple(args, "s#|i:send", &buf, &len, &flags)) return NULL; - if (s->sock_timeout >= 0.0) { - if (s->sock_blocking) { - if (internal_select(s, 1) <= 0) - return NULL; - } - } - Py_BEGIN_ALLOW_THREADS + internal_select(s, 1); n = send(s->sock_fd, buf, len, flags); Py_END_ALLOW_THREADS @@ -1834,14 +1685,8 @@ sock_sendall(PySocketSockObject *s, PyObject *args) if (!PyArg_ParseTuple(args, "s#|i:sendall", &buf, &len, &flags)) return NULL; - if (s->sock_timeout >= 0.0) { - if (s->sock_blocking) { - if (internal_select(s, 1) <= 0) - return NULL; - } - } - Py_BEGIN_ALLOW_THREADS + internal_select(s, 1); do { n = send(s->sock_fd, buf, len, flags); if (n < 0) @@ -1888,14 +1733,8 @@ sock_sendto(PySocketSockObject *s, PyObject *args) if (!getsockaddrarg(s, addro, &addr, &addrlen)) return NULL; - if (s->sock_timeout >= 0.0) { - if (s->sock_blocking) { - if (internal_select(s, 1) <= 0) - return NULL; - } - } - Py_BEGIN_ALLOW_THREADS + internal_select(s, 1); n = sendto(s->sock_fd, buf, len, flags, addr, addrlen); Py_END_ALLOW_THREADS diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index 0c6cd1b..936acac 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -83,9 +83,8 @@ typedef struct { PyObject *(*errorhandler)(void); /* Error handler; checks errno, returns NULL and sets a Python exception */ - int sock_blocking; /* Flag indicated whether the - socket is in blocking mode */ - double sock_timeout; /* Operation timeout value */ + double sock_timeout; /* Operation timeout in seconds; + 0.0 means non-blocking */ } PySocketSockObject; /* --- C API ----------------------------------------------------*/ |