Index: Lib/socket.py =================================================================== --- Lib/socket.py (révision 84819) +++ Lib/socket.py (copie de travail) @@ -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. @@ -248,14 +252,23 @@ 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): 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): return self._reading and not self.closed Index: Lib/test/test_socket.py =================================================================== --- Lib/test/test_socket.py (révision 84820) +++ Lib/test/test_socket.py (copie de travail) @@ -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,7 +1201,63 @@ 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): bufsize = 1 # Default-buffered for reading; line-buffered for writing