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:
parent
8db3027e6a
commit
98b46702d2
|
@ -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.
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue