[Python-checkins] r84887 - in python/branches/py3k: Lib/socket.py Lib/test/test_socket.py Misc/NEWS

antoine.pitrou python-checkins at python.org
Sun Sep 19 00:59:00 CEST 2010


Author: antoine.pitrou
Date: Sun Sep 19 00:59:00 2010
New Revision: 84887

Log:
Issue #9854: SocketIO objects now observe the RawIOBase interface in
non-blocking mode: they return None when an operation would block (instead
of raising an exception).



Modified:
   python/branches/py3k/Lib/socket.py
   python/branches/py3k/Lib/test/test_socket.py
   python/branches/py3k/Misc/NEWS

Modified: python/branches/py3k/Lib/socket.py
==============================================================================
--- python/branches/py3k/Lib/socket.py	(original)
+++ python/branches/py3k/Lib/socket.py	Sun Sep 19 00:59:00 2010
@@ -54,6 +54,8 @@
     errno = None
 EBADF = getattr(errno, 'EBADF', 9)
 EINTR = getattr(errno, 'EINTR', 4)
+EAGAIN = getattr(errno, 'EAGAIN', 11)
+EWOULDBLOCK = getattr(errno, 'EWOULDBLOCK', 11)
 
 __all__ = ["getfqdn", "create_connection"]
 __all__.extend(os._get_exports_list(_socket))
@@ -220,6 +222,8 @@
         return a, b
 
 
+_blocking_errnos = { EAGAIN, EWOULDBLOCK }
+
 class SocketIO(io.RawIOBase):
 
     """Raw I/O implementation for stream sockets.
@@ -262,8 +266,11 @@
             try:
                 return self._sock.recv_into(b)
             except error as e:
-                if e.args[0] == EINTR:
+                n = e.args[0]
+                if n == EINTR:
                     continue
+                if n in _blocking_errnos:
+                    return None
                 raise
 
     def write(self, b):
@@ -274,7 +281,13 @@
         """
         self._checkClosed()
         self._checkWritable()
-        return self._sock.send(b)
+        try:
+            return self._sock.send(b)
+        except error as e:
+            # XXX what about EINTR?
+            if e.args[0] in _blocking_errnos:
+                return None
+            raise
 
     def readable(self):
         """True if the SocketIO is open for reading.

Modified: python/branches/py3k/Lib/test/test_socket.py
==============================================================================
--- python/branches/py3k/Lib/test/test_socket.py	(original)
+++ python/branches/py3k/Lib/test/test_socket.py	Sun Sep 19 00:59:00 2010
@@ -137,8 +137,8 @@
         self.done.wait()
 
         if self.queue.qsize():
-            msg = self.queue.get()
-            self.fail(msg)
+            exc = self.queue.get()
+            raise exc
 
     def clientRun(self, test_func):
         self.server_ready.wait()
@@ -148,9 +148,10 @@
             raise TypeError("test_func must be a callable function")
         try:
             test_func()
-        except Exception as strerror:
-            self.queue.put(strerror)
-        self.clientTearDown()
+        except BaseException as e:
+            self.queue.put(e)
+        finally:
+            self.clientTearDown()
 
     def clientSetUp(self):
         raise NotImplementedError("clientSetUp must be implemented.")
@@ -932,10 +933,13 @@
         SocketConnectedTest.__init__(self, methodName=methodName)
 
     def setUp(self):
+        self.evt1, self.evt2, self.serv_finished, self.cli_finished = [
+            threading.Event() for i in range(4)]
         SocketConnectedTest.setUp(self)
         self.serv_file = self.cli_conn.makefile('rb', self.bufsize)
 
     def tearDown(self):
+        self.serv_finished.set()
         self.serv_file.close()
         self.assertTrue(self.serv_file.closed)
         self.serv_file = None
@@ -943,9 +947,10 @@
 
     def clientSetUp(self):
         SocketConnectedTest.clientSetUp(self)
-        self.cli_file = self.serv_conn.makefile('wb')
+        self.cli_file = self.serv_conn.makefile('wb', self.bufsize)
 
     def clientTearDown(self):
+        self.cli_finished.set()
         self.cli_file.close()
         self.assertTrue(self.cli_file.closed)
         self.cli_file = None
@@ -1196,6 +1201,62 @@
     def _testMakefileCloseSocketDestroy(self):
         pass
 
+    # Non-blocking ops
+    # NOTE: to set `serv_file` as non-blocking, we must call
+    # `cli_conn.setblocking` and vice-versa (see setUp / clientSetUp).
+
+    def testSmallReadNonBlocking(self):
+        self.cli_conn.setblocking(False)
+        self.assertEqual(self.serv_file.readinto(bytearray(10)), None)
+        self.assertEqual(self.serv_file.read(len(MSG) - 3), None)
+        self.evt1.set()
+        self.evt2.wait(1.0)
+        first_seg = self.serv_file.read(len(MSG) - 3)
+        buf = bytearray(10)
+        n = self.serv_file.readinto(buf)
+        self.assertEqual(n, 3)
+        msg = first_seg + buf[:n]
+        self.assertEqual(msg, MSG)
+        self.assertEqual(self.serv_file.readinto(bytearray(16)), None)
+        self.assertEqual(self.serv_file.read(1), None)
+
+    def _testSmallReadNonBlocking(self):
+        self.evt1.wait(1.0)
+        self.cli_file.write(MSG)
+        self.cli_file.flush()
+        self.evt2.set()
+        # Avoid cloding the socket before the server test has finished,
+        # otherwise system recv() will return 0 instead of EWOULDBLOCK.
+        self.serv_finished.wait(5.0)
+
+    def testWriteNonBlocking(self):
+        self.cli_finished.wait(5.0)
+        # The client thread can't skip directly - the SkipTest exception
+        # would appear as a failure.
+        if self.serv_skipped:
+            self.skipTest(self.serv_skipped)
+
+    def _testWriteNonBlocking(self):
+        self.serv_skipped = None
+        self.serv_conn.setblocking(False)
+        # Try to saturate the socket buffer pipe with repeated large writes.
+        BIG = b"x" * (1024 ** 2)
+        LIMIT = 10
+        # The first write() succeeds since a chunk of data can be buffered
+        n = self.cli_file.write(BIG)
+        self.assertGreater(n, 0)
+        for i in range(LIMIT):
+            n = self.cli_file.write(BIG)
+            if n is None:
+                # Succeeded
+                break
+            self.assertGreater(n, 0)
+        else:
+            # Let us know that this test didn't manage to establish
+            # the expected conditions. This is not a failure in itself but,
+            # if it happens repeatedly, the test should be fixed.
+            self.serv_skipped = "failed to saturate the socket buffer"
+
 
 class LineBufferedFileObjectClassTestCase(FileObjectClassTestCase):
 

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Sun Sep 19 00:59:00 2010
@@ -52,6 +52,10 @@
 Library
 -------
 
+- Issue #9854: SocketIO objects now observe the RawIOBase interface in
+  non-blocking mode: they return None when an operation would block (instead
+  of raising an exception).
+
 - Issue #1730136: Fix the comparison between a tk.font.Font and an object of
   another kind.
 


More information about the Python-checkins mailing list