]> granicus.if.org Git - python/commitdiff
SF patch 555085 (timeout socket implementation) by Michael Gilfix.
authorGuido van Rossum <guido@python.org>
Thu, 6 Jun 2002 21:08:16 +0000 (21:08 +0000)
committerGuido van Rossum <guido@python.org>
Thu, 6 Jun 2002 21:08:16 +0000 (21:08 +0000)
I've made considerable changes to Michael's code, specifically to use
the select() system call directly and to store the timeout as a C
double instead of a Python object; internally, -1.0 (or anything
negative) represents the None from the API.

I'm not 100% sure that all corner cases are covered correctly, so
please keep an eye on this.  Next I'm going to try it Windows before
Tim complains.

No way is this a bugfix candidate. :-)

Lib/socket.py
Lib/test/test_socket.py
Lib/test/test_timeout.py [new file with mode: 0644]
Modules/socketmodule.c
Modules/socketmodule.h

index 153f602b26f2cf6f14156cb6c8597307c7c45af5..04e2e33f05045331d73027d218239093cd189aa6 100644 (file)
@@ -134,7 +134,8 @@ def getfqdn(name=''):
 _socketmethods = (
     'bind', 'connect', 'connect_ex', 'fileno', 'listen',
     'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
-    'recv', 'recvfrom', 'send', 'sendall', 'sendto', 'setblocking', 'shutdown')
+    'recv', 'recvfrom', 'send', 'sendall', 'sendto', 'setblocking',
+    'settimeout', 'gettimeout', 'shutdown')
 
 class _socketobject:
 
@@ -168,94 +169,108 @@ class _socketobject:
 
 
 class _fileobject:
+    """Implements a file object on top of a regular socket object."""
 
-    def __init__(self, sock, mode, bufsize):
+    def __init__(self, sock, mode='rb', bufsize=8192):
         self._sock = sock
         self._mode = mode
-        if bufsize < 0:
+        if bufsize <= 0:
             bufsize = 512
-        self._rbufsize = max(1, bufsize)
+        self._rbufsize = bufsize
         self._wbufsize = bufsize
-        self._wbuf = self._rbuf = ""
+        self._rbuf = [ ]
+        self._wbuf = [ ]
 
     def close(self):
         try:
             if self._sock:
                 self.flush()
         finally:
-            self._sock = 0
+            self._sock = None
 
     def __del__(self):
         self.close()
 
     def flush(self):
         if self._wbuf:
-            self._sock.sendall(self._wbuf)
-            self._wbuf = ""
+            buffer = ''.join(self._wbuf)
+            self._sock.sendall(buffer)
+            self._wbuf = [ ]
 
-    def fileno(self):
+    def fileno (self):
         return self._sock.fileno()
 
     def write(self, data):
-        self._wbuf = self._wbuf + data
+        self._wbuf.append (data)
+        # A _wbufsize of 1 means we're doing unbuffered IO.
+        # Flush accordingly.
         if self._wbufsize == 1:
             if '\n' in data:
-                self.flush()
-        else:
-            if len(self._wbuf) >= self._wbufsize:
-                self.flush()
+                self.flush ()
+        elif self.__get_wbuf_len() >= self._wbufsize:
+            self.flush()
 
     def writelines(self, list):
         filter(self._sock.sendall, list)
         self.flush()
 
-    def read(self, n=-1):
-        if n >= 0:
-            k = len(self._rbuf)
-            if n <= k:
-                data = self._rbuf[:n]
-                self._rbuf = self._rbuf[n:]
-                return data
-            n = n - k
-            L = [self._rbuf]
-            self._rbuf = ""
-            while n > 0:
-                new = self._sock.recv(max(n, self._rbufsize))
-                if not new: break
-                k = len(new)
-                if k > n:
-                    L.append(new[:n])
-                    self._rbuf = new[n:]
-                    break
-                L.append(new)
-                n = n - k
-            return "".join(L)
-        k = max(512, self._rbufsize)
-        L = [self._rbuf]
-        self._rbuf = ""
-        while 1:
-            new = self._sock.recv(k)
-            if not new: break
-            L.append(new)
-            k = min(k*2, 1024**2)
-        return "".join(L)
-
-    def readline(self, limit=-1):
-        data = ""
-        i = self._rbuf.find('\n')
-        while i < 0 and not (0 < limit <= len(self._rbuf)):
-            new = self._sock.recv(self._rbufsize)
-            if not new: break
-            i = new.find('\n')
-            if i >= 0: i = i + len(self._rbuf)
-            self._rbuf = self._rbuf + new
-        if i < 0: i = len(self._rbuf)
-        else: i = i+1
-        if 0 <= limit < len(self._rbuf): i = limit
-        data, self._rbuf = self._rbuf[:i], self._rbuf[i:]
+    def __get_wbuf_len (self):
+        buf_len = 0
+        for i in [len(x) for x in self._wbuf]:
+            buf_len += i
+        return buf_len
+
+    def __get_rbuf_len(self):
+        buf_len = 0
+        for i in [len(x) for x in self._rbuf]:
+            buf_len += i
+        return buf_len
+
+    def read(self, size=-1):
+        buf_len = self.__get_rbuf_len()
+        while size < 0 or buf_len < size:
+            recv_size = max(self._rbufsize, size - buf_len)
+            data = self._sock.recv(recv_size)
+            if not data:
+                break
+            buf_len += len(data)
+            self._rbuf.append(data)
+        data = ''.join(self._rbuf)
+        # Clear the rbuf at the end so we're not affected by
+        # an exception during a recv
+        self._rbuf = [ ]
+        if buf_len > size and size >= 0:
+           self._rbuf.append(data[size:])
+           data = data[:size]
+        return data
+
+    def readline(self, size=-1):
+        index = -1
+        buf_len = self.__get_rbuf_len()
+        if len (self._rbuf):
+            index = min([x.find('\n') for x in self._rbuf])
+        while index < 0 and (size < 0 or buf_len < size):
+            recv_size = max(self._rbufsize, size - buf_len)
+            data = self._sock.recv(recv_size)
+            if not data:
+                break
+            buf_len += len(data)
+            self._rbuf.append(data)
+            index = data.find('\n')
+        data = ''.join(self._rbuf)
+        self._rbuf = [ ]
+        index = data.find('\n')
+        if index >= 0:
+            index += 1
+        elif buf_len > size:
+            index = size
+        else:
+            index = buf_len
+        self._rbuf.append(data[index:])
+        data = data[:index]
         return data
 
-    def readlines(self, sizehint = 0):
+    def readlines(self, sizehint=0):
         total = 0
         list = []
         while 1:
index 9cef3167fd6fe6645a6891c9eb5d2478d89e7cd6..56c7fb16cbacc8ef0c557d6131b8b9ddb06985e6 100644 (file)
@@ -109,6 +109,7 @@ except socket.error:
 canfork = hasattr(os, 'fork')
 try:
     PORT = 50007
+    msg = 'socket test\n'
     if not canfork or os.fork():
         # parent is server
         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -133,13 +134,52 @@ try:
             f = conn.makefile()
             if verbose:
                 print 'file obj:', f
+            data = conn.recv(1024)
+            if verbose:
+                print 'received:', data
+            conn.sendall(data)
+
+            # Perform a few tests on the windows file object
+            if verbose:
+                print "Staring _fileobject tests..."
+            f = socket._fileobject (conn, 'rb', 8192)
+            first_seg = f.read(7)
+            second_seg = f.read(5)
+            if not first_seg == 'socket ' or not second_seg == 'test\n':
+                print "Error performing read with the python _fileobject class"
+                os._exit (1)
+            elif verbose:
+                print "_fileobject buffered read works"
+            f.write (data)
+            f.flush ()
+
+            buf = ''
             while 1:
-                data = conn.recv(1024)
-                if not data:
+                char = f.read(1)
+                if not char:
+                    print "Error performing unbuffered read with the python ", \
+                          "_fileobject class"
+                    os._exit (1)
+                buf += char
+                if buf == msg:
+                    if verbose:
+                        print "__fileobject unbuffered read works"
                     break
-                if verbose:
-                    print 'received:', data
-                conn.sendall(data)
+            if verbose:
+                # If we got this far, write() must work as well
+                print "__fileobject write works"
+            f.write(buf)
+            f.flush()
+
+            line = f.readline()
+            if not line == msg:
+                print "Error perferming readline with the python _fileobject class"
+                os._exit (1)
+            f.write(line)
+            f.flush()
+            if verbose:
+                print "__fileobject readline works"
+
             conn.close()
     else:
         try:
@@ -149,11 +189,18 @@ try:
             if verbose:
                 print 'child connecting'
             s.connect(("127.0.0.1", PORT))
-            msg = 'socket test'
-            s.send(msg)
-            data = s.recv(1024)
-            if msg != data:
-                print 'parent/client mismatch'
+
+            iteration = 0
+            while 1:
+                s.send(msg)
+                data = s.recv(12)
+                if not data:
+                    break
+                if msg != data:
+                    print "parent/client mismatch. Failed in %s iteration. Received: [%s]" \
+                          %(iteration, data)
+                time.sleep (1)
+                iteration += 1
             s.close()
         finally:
             os._exit(1)
diff --git a/Lib/test/test_timeout.py b/Lib/test/test_timeout.py
new file mode 100644 (file)
index 0000000..c71efa7
--- /dev/null
@@ -0,0 +1,136 @@
+#!/home/bernie/src/python23/dist/src/python
+
+import unittest
+
+import time
+import socket
+
+class creationTestCase(unittest.TestCase):
+    """Test Case for socket.gettimeout() and socket.settimeout()"""
+    def setUp(self):
+        self.__s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+    def tearDown(self):
+        self.__s.close()
+
+    def testObjectCreation(self):
+        "Test Socket creation"
+        self.assertEqual(self.__s.gettimeout(), None, 
+            "Timeout socket not default to disable (None)")
+
+    def testFloatReturnValue(self):
+        "Test return value of getter/setter"
+        self.__s.settimeout(7.345)
+        self.assertEqual(self.__s.gettimeout(), 7.345, 
+            "settimeout() and gettimeout() return different result")
+
+        self.__s.settimeout(3)
+        self.assertEqual(self.__s.gettimeout(), 3, 
+            "settimeout() and gettimeout() return different result")
+
+    def testReturnType(self):
+        "Test return type of getter/setter"
+        self.__s.settimeout(1)
+        self.assertEqual(type(self.__s.gettimeout()), type(1.0),
+            "return type of gettimeout() is not FloatType")
+
+        self.__s.settimeout(3.9)
+        self.assertEqual(type(self.__s.gettimeout()), type(1.0),
+            "return type of gettimeout() is not FloatType")
+
+
+class timeoutTestCase(unittest.TestCase):
+    """Test Case for socket.socket() timeout functions"""
+    def setUp(self):
+        self.__s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.__addr_remote = ('www.google.com', 80)
+        self.__addr_local  = ('127.0.0.1', 25339)
+
+    def tearDown(self):
+        self.__s.close()
+
+    def testConnectTimeout(self):
+        "Test connect() timeout"
+        _timeout = 0.02
+        self.__s.settimeout(_timeout)
+
+        _t1 = time.time()
+        self.failUnlessRaises(socket.error, self.__s.connect,
+                self.__addr_remote)
+        _t2 = time.time()
+
+        _delta = abs(_t1 - _t2)
+        self.assert_(_delta < _timeout + 0.5,
+                "timeout (%f) is 0.5 seconds more than required (%f)"
+                %(_delta, _timeout))
+
+    def testRecvTimeout(self):
+        "Test recv() timeout"
+        _timeout = 0.02
+        self.__s.connect(self.__addr_remote)
+        self.__s.settimeout(_timeout)
+
+        _t1 = time.time()
+        self.failUnlessRaises(socket.error, self.__s.recv, 1024)
+        _t2 = time.time()
+
+        _delta = abs(_t1 - _t2)
+        self.assert_(_delta < _timeout + 0.5,
+                "timeout (%f) is 0.5 seconds more than required (%f)" 
+                %(_delta, _timeout))
+
+    def testAcceptTimeout(self):
+        "Test accept() timeout()"
+        _timeout = 2
+        self.__s.settimeout(_timeout)
+        self.__s.bind(self.__addr_local)
+        self.__s.listen(5)
+
+        _t1 = time.time()
+        self.failUnlessRaises(socket.error, self.__s.accept)
+        _t2 = time.time()
+
+        _delta = abs(_t1 - _t2)
+        self.assert_(_delta < _timeout + 0.5,
+                "timeout (%f) is 0.5 seconds more than required (%f)" 
+                %(_delta, _timeout))
+
+    def testRecvfromTimeout(self):
+        "Test recvfrom() timeout()"
+        _timeout = 2
+        self.__s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        self.__s.settimeout(_timeout)
+        self.__s.bind(self.__addr_local)
+
+        _t1 = time.time()
+        self.failUnlessRaises(socket.error, self.__s.recvfrom, 8192)
+        _t2 = time.time()
+
+        _delta = abs(_t1 - _t2)
+        self.assert_(_delta < _timeout + 0.5,
+                "timeout (%f) is 0.5 seconds more than required (%f)" 
+                %(_delta, _timeout))
+
+    def testSend(self):
+        "Test send() timeout"
+        # couldn't figure out how to test it
+        pass
+
+    def testSendto(self):
+        "Test sendto() timeout"
+        # couldn't figure out how to test it
+        pass
+
+    def testSendall(self):
+        "Test sendall() timeout"
+        # couldn't figure out how to test it
+        pass
+
+
+def suite():
+    suite = unittest.TestSuite()
+
+    return suite
+
+if __name__ == "__main__":
+    unittest.main()
index 89e8da638114143d60620e0b6dca6f4906331eb5..06d3f794f565ef660b630e2b22a715f1188f9422 100644 (file)
@@ -66,6 +66,8 @@ Socket methods:
 - s.sendall(string [,flags]) # tries to send everything in a loop
 - s.sendto(string, [flags,] sockaddr) --> nbytes
 - s.setblocking(0 | 1) --> None
+- s.settimeout(None | float) -> None # Argument in seconds
+- s.gettimeout() -> None or float seconds
 - s.setsockopt(level, optname, value) --> None
 - s.shutdown(how) --> None
 - repr(s) --> "<socket object, fd=%d, family=%d, type=%d, protocol=%d>"
@@ -421,6 +423,107 @@ PyGAI_Err(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(PySocket_Error, v);
+               Py_DECREF(v);
+       }
+
+       return NULL;
+}
+
+/* Function to perfrom the setting of socket blocking mode
+ * internally. block = (1 | 0).
+ */
+static int
+internal_setblocking(PySocketSockObject *s, int block)
+{
+#ifndef RISCOS
+#ifndef MS_WINDOWS
+       int delay_flag;
+#endif
+#endif
+
+       Py_BEGIN_ALLOW_THREADS
+#ifdef __BEOS__
+       block = !block;
+       setsockopt( s->sock_fd, SOL_SOCKET, SO_NONBLOCK,
+                               (void *)(&block), sizeof(int));
+#else
+#ifndef RISCOS
+#ifndef MS_WINDOWS
+#if defined(PYOS_OS2) && !defined(PYCC_GCC)
+       block = !block;
+       ioctl(s->sock_fd, FIONBIO, (caddr_t)&block, sizeof(block));
+#else /* !PYOS_OS2 */
+       delay_flag = fcntl(s->sock_fd, F_GETFL, 0);
+       if (block)
+               delay_flag &= (~O_NDELAY);
+       else
+               delay_flag |= O_NDELAY;
+       fcntl(s->sock_fd, F_SETFL, delay_flag);
+#endif /* !PYOS_OS2 */
+#else /* MS_WINDOWS */
+       block = !block;
+       ioctlsocket(s->sock_fd, FIONBIO, (u_long*)&block);
+#endif /* MS_WINDOWS */
+#endif /* __BEOS__ */
+#endif /* RISCOS */
+       Py_END_ALLOW_THREADS
+
+       /* Since these don't return anything */
+       return 1;
+}
+
+/* For access to the select module to poll the socket for timeout
+ * functionality. If reading is: 1 poll as read, 0, poll as write.
+ * Return value: -1 if error, 0 if not ready, >= 1 if ready.
+ */
+static int
+internal_select(PySocketSockObject *s, int reading)
+{
+       fd_set fds;
+       struct timeval tv;
+       int count;
+
+       /* Construct the arguments to select */
+       tv.tv_sec = (int)s->sock_timeout;
+       tv.tv_usec = (int)(s->sock_timeout/1e6);
+       FD_ZERO(&fds);
+       FD_SET(s->sock_fd, &fds);
+
+       /* See if the socket is ready */
+       if (reading)
+               count = select(s->sock_fd, &fds, NULL, NULL, &tv);
+       else
+               count = select(s->sock_fd, NULL, &fds, 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;
+}
+
 /* Initialize a new socket object. */
 
 static void
@@ -434,6 +537,9 @@ 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 = &PySocket_Err;
 #ifdef RISCOS
        if(taskwindow) {
@@ -691,7 +797,7 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args,
                struct sockaddr_un* addr;
                char *path;
                int len;
-               addr = (struct sockaddr_un* )&(s->sock_addr).un;
+               addr = (struct sockaddr_un*)&(s->sock_addr).un;
                if (!PyArg_Parse(args, "t#", &path, &len))
                        return 0;
                if (len > sizeof addr->sun_path) {
@@ -861,9 +967,47 @@ PySocketSock_accept(PySocketSockObject *s)
        if (!getsockaddrlen(s, &addrlen))
                return NULL;
        memset(addrbuf, 0, addrlen);
+
+       errno = 0; /* Reset indicator for use with timeout behavior */
+
        Py_BEGIN_ALLOW_THREADS
        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
@@ -877,18 +1021,19 @@ PySocketSock_accept(PySocketSockObject *s)
                                        s->sock_family,
                                        s->sock_type,
                                        s->sock_proto);
+
        if (sock == NULL) {
                SOCKETCLOSE(newfd);
                goto finally;
        }
        addr = makesockaddr(s->sock_fd, (struct sockaddr *)addrbuf,
-                           addrlen);
+                           addrlen);
        if (addr == NULL)
                goto finally;
 
        res = Py_BuildValue("OO", sock, addr);
 
-  finally:
+finally:
        Py_XDECREF(sock);
        Py_XDECREF(addr);
        return res;
@@ -901,47 +1046,24 @@ 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 */
 
 static PyObject *
 PySocketSock_setblocking(PySocketSockObject *s, PyObject *arg)
 {
        int block;
-#ifndef RISCOS
-#ifndef MS_WINDOWS
-       int delay_flag;
-#endif
-#endif
+
        block = PyInt_AsLong(arg);
        if (block == -1 && PyErr_Occurred())
                return NULL;
-       Py_BEGIN_ALLOW_THREADS
-#ifdef __BEOS__
-       block = !block;
-       setsockopt( s->sock_fd, SOL_SOCKET, SO_NONBLOCK,
-                               (void *)(&block), sizeof( int ) );
-#else
-#ifndef RISCOS
-#ifndef MS_WINDOWS
-#if defined(PYOS_OS2) && !defined(PYCC_GCC)
-       block = !block;
-       ioctl(s->sock_fd, FIONBIO, (caddr_t)&block, sizeof(block));
-#else /* !PYOS_OS2 */
-       delay_flag = fcntl (s->sock_fd, F_GETFL, 0);
-       if (block)
-               delay_flag &= (~O_NDELAY);
-       else
-               delay_flag |= O_NDELAY;
-       fcntl (s->sock_fd, F_SETFL, delay_flag);
-#endif /* !PYOS_OS2 */
-#else /* MS_WINDOWS */
-       block = !block;
-       ioctlsocket(s->sock_fd, FIONBIO, (u_long*)&block);
-#endif /* MS_WINDOWS */
-#endif /* __BEOS__ */
-#endif /* RISCOS */
-       Py_END_ALLOW_THREADS
+
+       s->sock_blocking = block;
+
+       /* If we're not using timeouts, actually set the blocking to give
+        * old python behavior.
+        */
+       if (s->sock_timeout < 0.0)
+               internal_setblocking(s, block);
 
        Py_INCREF(Py_None);
        return Py_None;
@@ -953,6 +1075,81 @@ static char setblocking_doc[] =
 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 (float | int | long) method.
+ * Causes an exception to be raised when the integer number of seconds
+ * has elapsed when performing a blocking socket operation.
+ */
+static PyObject *
+PySocketSock_settimeout(PySocketSockObject *s, PyObject *arg)
+{
+       double value;
+
+       if (arg == Py_None)
+               value = -1.0;
+       else {
+               value = PyFloat_AsDouble(arg);
+               if (value < 0.0) {
+                       if (!PyErr_Occurred())
+                               PyErr_SetString(PyExc_ValueError,
+                                               "Invalid timeout value");
+                       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.
+        */
+       if (value < 0.0)
+               internal_setblocking(s, 0);
+       else
+               internal_setblocking(s, 1);
+
+       s->sock_blocking = 1; /* Always negate setblocking() */
+
+       Py_INCREF(Py_None);
+       return Py_None;
+}
+
+static char settimeout_doc[] =
+"settimeout(seconds)\n\
+\n\
+Set a timeout on blocking socket operations. 'seconds' can be a floating,\n\
+integer, or long number of seconds, or the None value. Socket operations\n\
+will raise an exception if the timeout period has elapsed before the\n\
+operation has completed. Setting a timeout of None disables timeouts\n\
+on socket operations.";
+
+/* s.gettimeout () method.
+ * Returns the timeout associated with a socket.
+ */
+static PyObject *
+PySocketSock_gettimeout(PySocketSockObject *s)
+{
+       if (s->sock_timeout < 0.0) {
+               Py_INCREF(Py_None);
+               return Py_None;
+       }
+       else
+               return PyFloat_FromDouble(s->sock_timeout);
+}
+
+static char gettimeout_doc[] =
+"gettimeout()\n\
+\n\
+Returns the timeout in floating seconds associated with socket \n\
+operations. A timeout of None indicates that timeouts on socket \n\
+operations are disabled.";
 
 #ifdef RISCOS
 /* s.sleeptaskw(1 | 0) method */
@@ -960,16 +1157,16 @@ This uses the FIONBIO ioctl with the O_NDELAY flag.";
 static PyObject *
 PySocketSock_sleeptaskw(PySocketSockObject *s,PyObject *args)
 {
- int block;
- int delay_flag;
- if (!PyArg_Parse(args, "i", &block))
-  return NULL;
- Py_BEGIN_ALLOW_THREADS
-  socketioctl(s->sock_fd, 0x80046679, (u_long*)&block);
- Py_END_ALLOW_THREADS
      int block;
      int delay_flag;
      if (!PyArg_Parse(args, "i", &block))
+               return NULL;
      Py_BEGIN_ALLOW_THREADS
+       socketioctl(s->sock_fd, 0x80046679, (u_long*)&block);
      Py_END_ALLOW_THREADS
 
- Py_INCREF(Py_None);
- return Py_None;
      Py_INCREF(Py_None);
      return Py_None;
 }
 static char sleeptaskw_doc[] =
 "sleeptaskw(flag)\n\
@@ -1142,11 +1339,50 @@ PySocketSock_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
+               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;
 }
@@ -1169,9 +1405,46 @@ PySocketSock_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
+               res = connect(s->sock_fd, addr, addrlen);
+               Py_END_ALLOW_THREADS
+       }
+
        if (res != 0) {
 #ifdef MS_WINDOWS
                res = WSAGetLastError();
@@ -1179,6 +1452,8 @@ PySocketSock_connect_ex(PySocketSockObject *s, PyObject *addro)
                res = errno;
 #endif
        }
+
+conex_finally:
        return PyInt_FromLong((long) res);
 }
 
@@ -1360,7 +1635,7 @@ PySocketSock_makefile(PySocketSockObject *s, PyObject *args)
        }
 #ifdef USE_GUSI2
        /* Workaround for bug in Metrowerks MSL vs. GUSI I/O library */
-       if (strchr(mode, 'b') != NULL )
+       if (strchr(mode, 'b') != NULL)
                bufsize = 0;
 #endif
        f = PyFile_FromFile(fp, "<socket>", mode, fclose);
@@ -1385,19 +1660,31 @@ PySocketSock_recv(PySocketSockObject *s, PyObject *args)
 {
        int len, n, flags = 0;
        PyObject *buf;
+
        if (!PyArg_ParseTuple(args, "i|i:recv", &len, &flags))
                return NULL;
-        if (len < 0) {
+
+       if (len < 0) {
                PyErr_SetString(PyExc_ValueError,
                                "negative buffersize in connect");
                return NULL;
        }
+
        buf = PyString_FromStringAndSize((char *) 0, len);
        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
        n = recv(s->sock_fd, PyString_AS_STRING(buf), len, flags);
        Py_END_ALLOW_THREADS
+
        if (n < 0) {
                Py_DECREF(buf);
                return s->errorhandler();
@@ -1425,16 +1712,25 @@ PySocketSock_recvfrom(PySocketSockObject *s, PyObject *args)
        PyObject *buf = NULL;
        PyObject *addr = NULL;
        PyObject *ret = NULL;
-
        int len, n, flags = 0;
        socklen_t addrlen;
+
        if (!PyArg_ParseTuple(args, "i|i:recvfrom", &len, &flags))
                return NULL;
+
        if (!getsockaddrlen(s, &addrlen))
                return NULL;
        buf = PyString_FromStringAndSize((char *) 0, len);
        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);
        n = recvfrom(s->sock_fd, PyString_AS_STRING(buf), len, flags,
@@ -1449,18 +1745,22 @@ PySocketSock_recvfrom(PySocketSockObject *s, PyObject *args)
 #endif
                     );
        Py_END_ALLOW_THREADS
+
        if (n < 0) {
                Py_DECREF(buf);
                return s->errorhandler();
        }
+
        if (n != len && _PyString_Resize(&buf, n) < 0)
                return NULL;
 
-       if (!(addr = makesockaddr(s->sock_fd, (struct sockaddr *)addrbuf, addrlen)))
+       if (!(addr = makesockaddr(s->sock_fd, (struct sockaddr *)addrbuf,
+                                 addrlen)))
                goto finally;
 
        ret = Py_BuildValue("OO", buf, addr);
-  finally:
+
+finally:
        Py_XDECREF(addr);
        Py_XDECREF(buf);
        return ret;
@@ -1471,7 +1771,6 @@ static char recvfrom_doc[] =
 \n\
 Like recv(buffersize, flags) but also return the sender's address info.";
 
-
 /* s.send(data [,flags]) method */
 
 static PyObject *
@@ -1479,11 +1778,21 @@ PySocketSock_send(PySocketSockObject *s, PyObject *args)
 {
        char *buf;
        int len, n, flags = 0;
+
        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
        n = send(s->sock_fd, buf, len, flags);
        Py_END_ALLOW_THREADS
+
        if (n < 0)
                return s->errorhandler();
        return PyInt_FromLong((long)n);
@@ -1504,8 +1813,17 @@ PySocketSock_sendall(PySocketSockObject *s, PyObject *args)
 {
        char *buf;
        int len, n, flags = 0;
+
        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
        do {
                n = send(s->sock_fd, buf, len, flags);
@@ -1515,8 +1833,10 @@ PySocketSock_sendall(PySocketSockObject *s, PyObject *args)
                len -= n;
        } while (len > 0);
        Py_END_ALLOW_THREADS
+
        if (n < 0)
                return s->errorhandler();
+
        Py_INCREF(Py_None);
        return Py_None;
 }
@@ -1539,6 +1859,7 @@ PySocketSock_sendto(PySocketSockObject *s, PyObject *args)
        char *buf;
        struct sockaddr *addr;
        int addrlen, len, n, flags;
+
        flags = 0;
        if (!PyArg_ParseTuple(args, "s#O:sendto", &buf, &len, &addro)) {
                PyErr_Clear();
@@ -1546,11 +1867,21 @@ PySocketSock_sendto(PySocketSockObject *s, PyObject *args)
                                      &buf, &len, &flags, &addro))
                        return NULL;
        }
+
        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
        n = sendto(s->sock_fd, buf, len, flags, addr, addrlen);
        Py_END_ALLOW_THREADS
+
        if (n < 0)
                return s->errorhandler();
        return PyInt_FromLong((long)n);
@@ -1635,6 +1966,10 @@ static PyMethodDef PySocketSock_methods[] = {
                        sendto_doc},
        {"setblocking", (PyCFunction)PySocketSock_setblocking, METH_O,
                        setblocking_doc},
+       {"settimeout", (PyCFunction)PySocketSock_settimeout, METH_O,
+                       settimeout_doc},
+       {"gettimeout", (PyCFunction)PySocketSock_gettimeout, METH_NOARGS,
+                       gettimeout_doc},
        {"setsockopt",  (PyCFunction)PySocketSock_setsockopt, METH_VARARGS,
                        setsockopt_doc},
        {"shutdown",    (PyCFunction)PySocketSock_shutdown, METH_O,
@@ -1692,6 +2027,7 @@ PySocketSock_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
        new = type->tp_alloc(type, 0);
        if (new != NULL) {
                ((PySocketSockObject *)new)->sock_fd = -1;
+               ((PySocketSockObject *)new)->sock_timeout = -1.0;
                ((PySocketSockObject *)new)->errorhandler = &PySocket_Err;
        }
        return new;
@@ -1713,9 +2049,11 @@ PySocketSock_init(PyObject *self, PyObject *args, PyObject *kwds)
                                         "|iii:socket", keywords,
                                         &family, &type, &proto))
                return -1;
+
        Py_BEGIN_ALLOW_THREADS
        fd = socket(family, type, proto);
        Py_END_ALLOW_THREADS
+
 #ifdef MS_WINDOWS
        if (fd == INVALID_SOCKET)
 #else
@@ -1731,7 +2069,9 @@ PySocketSock_init(PyObject *self, PyObject *args, PyObject *kwds)
 #ifdef SIGPIPE
        (void) signal(SIGPIPE, SIG_IGN);
 #endif
+
        return 0;
+
 }
 
 
@@ -1885,6 +2225,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af)
 #endif
                return NULL;
        }
+
        if (h->h_addrtype != af) {
 #ifdef HAVE_STRERROR
                /* Let's get real error message to return */
@@ -1895,35 +2236,47 @@ gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af)
 #endif
                return NULL;
        }
+
        switch (af) {
+
        case AF_INET:
                if (alen < sizeof(struct sockaddr_in))
                        return NULL;
                break;
+
 #ifdef ENABLE_IPV6
        case AF_INET6:
                if (alen < sizeof(struct sockaddr_in6))
                        return NULL;
                break;
 #endif
+
        }
+
        if ((name_list = PyList_New(0)) == NULL)
                goto err;
+
        if ((addr_list = PyList_New(0)) == NULL)
                goto err;
+
        for (pch = h->h_aliases; *pch != NULL; pch++) {
                int status;
                tmp = PyString_FromString(*pch);
                if (tmp == NULL)
                        goto err;
+
                status = PyList_Append(name_list, tmp);
                Py_DECREF(tmp);
+
                if (status)
                        goto err;
        }
+
        for (pch = h->h_addr_list; *pch != NULL; pch++) {
                int status;
+
                switch (af) {
+
                case AF_INET:
                    {
                        struct sockaddr_in sin;
@@ -1934,10 +2287,12 @@ gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af)
 #endif
                        memcpy(&sin.sin_addr, *pch, sizeof(sin.sin_addr));
                        tmp = makeipaddr((struct sockaddr *)&sin, sizeof(sin));
+
                        if (pch == h->h_addr_list && alen >= sizeof(sin))
                                memcpy((char *) addr, &sin, sizeof(sin));
                        break;
                    }
+
 #ifdef ENABLE_IPV6
                case AF_INET6:
                    {
@@ -1950,24 +2305,31 @@ gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af)
                        memcpy(&sin6.sin6_addr, *pch, sizeof(sin6.sin6_addr));
                        tmp = makeipaddr((struct sockaddr *)&sin6,
                                sizeof(sin6));
+
                        if (pch == h->h_addr_list && alen >= sizeof(sin6))
                                memcpy((char *) addr, &sin6, sizeof(sin6));
                        break;
                    }
 #endif
+
                default:        /* can't happen */
                        PyErr_SetString(PySocket_Error,
                                "unsupported address family");
                        return NULL;
                }
+
                if (tmp == NULL)
                        goto err;
+
                status = PyList_Append(addr_list, tmp);
                Py_DECREF(tmp);
+
                if (status)
                        goto err;
        }
+
        rtn_tuple = Py_BuildValue("sOO", h->h_name, name_list, addr_list);
+
  err:
        Py_XDECREF(name_list);
        Py_XDECREF(addr_list);
@@ -2022,10 +2384,14 @@ PySocket_gethostbyname_ex(PyObject *self, PyObject *args)
        h = gethostbyname(name);
 #endif /* HAVE_GETHOSTBYNAME_R */
        Py_END_ALLOW_THREADS
-       /* Some C libraries would require addr.__ss_family instead of addr.ss_family.
-           Therefore, we cast the sockaddr_storage into sockaddr to access sa_family. */
+       /* Some C libraries would require addr.__ss_family instead of
+        * addr.ss_family.
+        * Therefore, we cast the sockaddr_storage into sockaddr to
+        * access sa_family.
+        */
        sa = (struct sockaddr*)&addr;
-       ret = gethost_common(h, (struct sockaddr *)&addr, sizeof(addr), sa->sa_family);
+       ret = gethost_common(h, (struct sockaddr *)&addr, sizeof(addr),
+                            sa->sa_family);
 #ifdef USE_GETHOSTBYNAME_LOCK
        PyThread_release_lock(gethostbyname_lock);
 #endif
@@ -2170,7 +2536,7 @@ PySocket_getprotobyname(PyObject *self, PyObject *args)
        struct protoent *sp;
 #ifdef __BEOS__
 /* Not available in BeOS yet. - [cjh] */
-       PyErr_SetString( PySocket_Error, "getprotobyname not supported" );
+       PyErr_SetString(PySocket_Error, "getprotobyname not supported");
        return NULL;
 #else
        if (!PyArg_ParseTuple(args, "s:getprotobyname", &name))
index a59e6f750ca6d7696f795cae5fb0453226ce72eb..4d17f08c7f1aab32d37a603a55467d54f1cc06f1 100644 (file)
@@ -83,6 +83,9 @@ 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 */
 } PySocketSockObject;
 
 /* --- C API ----------------------------------------------------*/