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).
This commit is contained in:
Antoine Pitrou 2010-09-18 22:59:00 +00:00
parent 8db3027e6a
commit 98b46702d2
3 changed files with 86 additions and 8 deletions

View File

@ -54,6 +54,8 @@ except ImportError:
errno = None errno = None
EBADF = getattr(errno, 'EBADF', 9) EBADF = getattr(errno, 'EBADF', 9)
EINTR = getattr(errno, 'EINTR', 4) EINTR = getattr(errno, 'EINTR', 4)
EAGAIN = getattr(errno, 'EAGAIN', 11)
EWOULDBLOCK = getattr(errno, 'EWOULDBLOCK', 11)
__all__ = ["getfqdn", "create_connection"] __all__ = ["getfqdn", "create_connection"]
__all__.extend(os._get_exports_list(_socket)) __all__.extend(os._get_exports_list(_socket))
@ -220,6 +222,8 @@ if hasattr(_socket, "socketpair"):
return a, b return a, b
_blocking_errnos = { EAGAIN, EWOULDBLOCK }
class SocketIO(io.RawIOBase): class SocketIO(io.RawIOBase):
"""Raw I/O implementation for stream sockets. """Raw I/O implementation for stream sockets.
@ -262,8 +266,11 @@ class SocketIO(io.RawIOBase):
try: try:
return self._sock.recv_into(b) return self._sock.recv_into(b)
except error as e: except error as e:
if e.args[0] == EINTR: n = e.args[0]
if n == EINTR:
continue continue
if n in _blocking_errnos:
return None
raise raise
def write(self, b): def write(self, b):
@ -274,7 +281,13 @@ class SocketIO(io.RawIOBase):
""" """
self._checkClosed() self._checkClosed()
self._checkWritable() 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): def readable(self):
"""True if the SocketIO is open for reading. """True if the SocketIO is open for reading.

View File

@ -137,8 +137,8 @@ class ThreadableTest:
self.done.wait() self.done.wait()
if self.queue.qsize(): if self.queue.qsize():
msg = self.queue.get() exc = self.queue.get()
self.fail(msg) raise exc
def clientRun(self, test_func): def clientRun(self, test_func):
self.server_ready.wait() self.server_ready.wait()
@ -148,9 +148,10 @@ class ThreadableTest:
raise TypeError("test_func must be a callable function") raise TypeError("test_func must be a callable function")
try: try:
test_func() test_func()
except Exception as strerror: except BaseException as e:
self.queue.put(strerror) self.queue.put(e)
self.clientTearDown() finally:
self.clientTearDown()
def clientSetUp(self): def clientSetUp(self):
raise NotImplementedError("clientSetUp must be implemented.") raise NotImplementedError("clientSetUp must be implemented.")
@ -932,10 +933,13 @@ class FileObjectClassTestCase(SocketConnectedTest):
SocketConnectedTest.__init__(self, methodName=methodName) SocketConnectedTest.__init__(self, methodName=methodName)
def setUp(self): def setUp(self):
self.evt1, self.evt2, self.serv_finished, self.cli_finished = [
threading.Event() for i in range(4)]
SocketConnectedTest.setUp(self) SocketConnectedTest.setUp(self)
self.serv_file = self.cli_conn.makefile('rb', self.bufsize) self.serv_file = self.cli_conn.makefile('rb', self.bufsize)
def tearDown(self): def tearDown(self):
self.serv_finished.set()
self.serv_file.close() self.serv_file.close()
self.assertTrue(self.serv_file.closed) self.assertTrue(self.serv_file.closed)
self.serv_file = None self.serv_file = None
@ -943,9 +947,10 @@ class FileObjectClassTestCase(SocketConnectedTest):
def clientSetUp(self): def clientSetUp(self):
SocketConnectedTest.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): def clientTearDown(self):
self.cli_finished.set()
self.cli_file.close() self.cli_file.close()
self.assertTrue(self.cli_file.closed) self.assertTrue(self.cli_file.closed)
self.cli_file = None self.cli_file = None
@ -1196,6 +1201,62 @@ class UnbufferedFileObjectClassTestCase(FileObjectClassTestCase):
def _testMakefileCloseSocketDestroy(self): def _testMakefileCloseSocketDestroy(self):
pass 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): class LineBufferedFileObjectClassTestCase(FileObjectClassTestCase):

View File

@ -52,6 +52,10 @@ Core and Builtins
Library 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 - Issue #1730136: Fix the comparison between a tk.font.Font and an object of
another kind. another kind.