summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2010-09-18 22:59:00 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2010-09-18 22:59:00 (GMT)
commit98b46702d2798d0db258a02f6a1854dfc5f659fd (patch)
tree13cb8a131699851391f45fd98cbf3220685ba074
parent8db3027e6a79836ea8c77e73be77492c68715da5 (diff)
downloadcpython-98b46702d2798d0db258a02f6a1854dfc5f659fd.zip
cpython-98b46702d2798d0db258a02f6a1854dfc5f659fd.tar.gz
cpython-98b46702d2798d0db258a02f6a1854dfc5f659fd.tar.bz2
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).
-rw-r--r--Lib/socket.py17
-rw-r--r--Lib/test/test_socket.py73
-rw-r--r--Misc/NEWS4
3 files changed, 86 insertions, 8 deletions
diff --git a/Lib/socket.py b/Lib/socket.py
index dbb7cca..0b19e30 100644
--- a/Lib/socket.py
+++ b/Lib/socket.py
@@ -54,6 +54,8 @@ except ImportError:
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 @@ if hasattr(_socket, "socketpair"):
return a, b
+_blocking_errnos = { EAGAIN, EWOULDBLOCK }
+
class SocketIO(io.RawIOBase):
"""Raw I/O implementation for stream sockets.
@@ -262,8 +266,11 @@ class SocketIO(io.RawIOBase):
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 @@ class SocketIO(io.RawIOBase):
"""
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.
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index 718ea5c..3ef0850 100644
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -137,8 +137,8 @@ class ThreadableTest:
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 @@ class ThreadableTest:
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 @@ class FileObjectClassTestCase(SocketConnectedTest):
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 @@ class FileObjectClassTestCase(SocketConnectedTest):
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 @@ class UnbufferedFileObjectClassTestCase(FileObjectClassTestCase):
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):
diff --git a/Misc/NEWS b/Misc/NEWS
index c1a5bc8..34cea64 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -52,6 +52,10 @@ Core and Builtins
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.