]> granicus.if.org Git - python/commitdiff
Major overhaul of timeout sockets:
authorGuido van Rossum <guido@python.org>
Thu, 13 Jun 2002 15:07:44 +0000 (15:07 +0000)
committerGuido van Rossum <guido@python.org>
Thu, 13 Jun 2002 15:07:44 +0000 (15:07 +0000)
- setblocking(0) and settimeout(0) are now equivalent, and ditto for
  setblocking(1) and settimeout(None).

- Don't raise an exception from internal_select(); let the final call
  report the error (this means you will get an EAGAIN error instead of
  an ETIMEDOUT error -- I don't care).

- Move the select to inside the Py_{BEGIN,END}_ALLOW_THREADS brackets,
  so other theads can run (this was a bug in the original code).

- Redid the retry logic in connect() and connect_ex() to avoid masking
  errors.  This probably doesn't work for Windows yet; I'll fix that
  next.  It may also fail on other platforms, depending on what
  retrying a connect does; I need help with this.

- Get rid of the retry logic in accept().  I don't think it was needed
  at all.  But I may be wrong.

Doc/lib/libsocket.tex
Lib/test/test_socket.py
Lib/test/test_timeout.py
Modules/socketmodule.c
Modules/socketmodule.h

index 54dfa6c73d9c1c31775201b3e23ab214c101f217..5ca0afc50fd2b002559629ad8bf788cff7d99c27 100644 (file)
@@ -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
index cbe1ec0560c8a5f440220505fe7fb75de9d3d50e..485e038cac8f8dd24d1a3bfd014e705f8d764300 100644 (file)
@@ -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,))
 
index 8a84258ede6ad81bba9c3726e35c16ba8739e07c..7c7222290a12c205a2845dfe029dcb0aca60717f 100644 (file)
@@ -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)
 
index ff67c3dac81567a4fe43edecd1414bc9b481a09c..38968318e1823998113a7a621621bc2ba4fe90d4 100644 (file)
@@ -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
 
index 0c6cd1b1ec334ce5298bb87befadc4d88e7ffd1d..936acac5b9d6197a9bd7c7e5b400797a6ea70998 100644 (file)
@@ -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 ----------------------------------------------------*/