diff options
author | Gregory P. Smith <greg@mad-scientist.com> | 2009-08-13 18:54:50 (GMT) |
---|---|---|
committer | Gregory P. Smith <greg@mad-scientist.com> | 2009-08-13 18:54:50 (GMT) |
commit | c4ad0345cf7789dc432ff57ab644db230d8baf1c (patch) | |
tree | 984552a9edaf0637ce702e8ffbf07029cac53e8d /Lib | |
parent | aa66a968d4842c7dca0063c27520162d96fd7fe7 (diff) | |
download | cpython-c4ad0345cf7789dc432ff57ab644db230d8baf1c.zip cpython-c4ad0345cf7789dc432ff57ab644db230d8baf1c.tar.gz cpython-c4ad0345cf7789dc432ff57ab644db230d8baf1c.tar.bz2 |
Fix issue1628205: Socket file objects returned by socket.socket.makefile() now
properly handles EINTR within the read, readline, write & flush methods.
The socket.sendall() method now properly handles interrupted system calls.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/socket.py | 69 | ||||
-rw-r--r-- | Lib/test/test_socket.py | 72 |
2 files changed, 128 insertions, 13 deletions
diff --git a/Lib/socket.py b/Lib/socket.py index dd0f327..a1e0386 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -86,9 +86,11 @@ except ImportError: from StringIO import StringIO try: - from errno import EBADF + import errno except ImportError: - EBADF = 9 + errno = None +EBADF = getattr(errno, 'EBADF', 9) +EINTR = getattr(errno, 'EINTR', 4) __all__ = ["getfqdn", "create_connection"] __all__.extend(os._get_exports_list(_socket)) @@ -286,10 +288,22 @@ class _fileobject(object): def flush(self): if self._wbuf: - buffer = "".join(self._wbuf) + data = "".join(self._wbuf) self._wbuf = [] self._wbuf_len = 0 - self._sock.sendall(buffer) + buffer_size = max(self._rbufsize, self.default_bufsize) + data_size = len(data) + write_offset = 0 + try: + while write_offset < data_size: + self._sock.sendall(buffer(data, write_offset, buffer_size)) + write_offset += buffer_size + finally: + if write_offset < data_size: + remainder = data[write_offset:] + del data # explicit free + self._wbuf.append(remainder) + self._wbuf_len = len(remainder) def fileno(self): return self._sock.fileno() @@ -329,7 +343,12 @@ class _fileobject(object): # Read until EOF self._rbuf = StringIO() # reset _rbuf. we consume it via buf. while True: - data = self._sock.recv(rbufsize) + try: + data = self._sock.recv(rbufsize) + except error, e: + if e[0] == EINTR: + continue + raise if not data: break buf.write(data) @@ -353,7 +372,12 @@ class _fileobject(object): # than that. The returned data string is short lived # as we copy it into a StringIO and free it. This avoids # fragmentation issues on many platforms. - data = self._sock.recv(left) + try: + data = self._sock.recv(left) + except error, e: + if e[0] == EINTR: + continue + raise if not data: break n = len(data) @@ -396,17 +420,31 @@ class _fileobject(object): self._rbuf = StringIO() # reset _rbuf. we consume it via buf. data = None recv = self._sock.recv - while data != "\n": - data = recv(1) - if not data: - break - buffers.append(data) + while True: + try: + while data != "\n": + data = recv(1) + if not data: + break + buffers.append(data) + except error, e: + # The try..except to catch EINTR was moved outside the + # recv loop to avoid the per byte overhead. + if e[0] == EINTR: + continue + raise + break return "".join(buffers) buf.seek(0, 2) # seek end self._rbuf = StringIO() # reset _rbuf. we consume it via buf. while True: - data = self._sock.recv(self._rbufsize) + try: + data = self._sock.recv(self._rbufsize) + except error, e: + if e[0] == EINTR: + continue + raise if not data: break nl = data.find('\n') @@ -430,7 +468,12 @@ class _fileobject(object): return rv self._rbuf = StringIO() # reset _rbuf. we consume it via buf. while True: - data = self._sock.recv(self._rbufsize) + try: + data = self._sock.recv(self._rbufsize) + except error, e: + if e[0] == EINTR: + continue + raise if not data: break left = size - buf_len diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index a2265de..4b26824 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -858,6 +858,77 @@ class FileObjectClassTestCase(SocketConnectedTest): def _testClosedAttr(self): self.assertTrue(not self.cli_file.closed) + +class FileObjectInterruptedTestCase(unittest.TestCase): + """Test that the file object correctly handles EINTR internally.""" + + class MockSocket(object): + def __init__(self, recv_funcs=()): + # A generator that returns callables that we'll call for each + # call to recv(). + self._recv_step = iter(recv_funcs) + + def recv(self, size): + return self._recv_step.next()() + + @staticmethod + def _raise_eintr(): + raise socket.error(errno.EINTR) + + def _test_readline(self, size=-1, **kwargs): + mock_sock = self.MockSocket(recv_funcs=[ + lambda : "This is the first line\nAnd the sec", + self._raise_eintr, + lambda : "ond line is here\n", + lambda : "", + ]) + fo = socket._fileobject(mock_sock, **kwargs) + self.assertEquals(fo.readline(size), "This is the first line\n") + self.assertEquals(fo.readline(size), "And the second line is here\n") + + def _test_read(self, size=-1, **kwargs): + mock_sock = self.MockSocket(recv_funcs=[ + lambda : "This is the first line\nAnd the sec", + self._raise_eintr, + lambda : "ond line is here\n", + lambda : "", + ]) + fo = socket._fileobject(mock_sock, **kwargs) + self.assertEquals(fo.read(size), "This is the first line\n" + "And the second line is here\n") + + def test_default(self): + self._test_readline() + self._test_readline(size=100) + self._test_read() + self._test_read(size=100) + + def test_with_1k_buffer(self): + self._test_readline(bufsize=1024) + self._test_readline(size=100, bufsize=1024) + self._test_read(bufsize=1024) + self._test_read(size=100, bufsize=1024) + + def _test_readline_no_buffer(self, size=-1): + mock_sock = self.MockSocket(recv_funcs=[ + lambda : "aa", + lambda : "\n", + lambda : "BB", + self._raise_eintr, + lambda : "bb", + lambda : "", + ]) + fo = socket._fileobject(mock_sock, bufsize=0) + self.assertEquals(fo.readline(size), "aa\n") + self.assertEquals(fo.readline(size), "BBbb") + + def test_no_buffer(self): + self._test_readline_no_buffer() + self._test_readline_no_buffer(size=4) + self._test_read(bufsize=0) + self._test_read(size=100, bufsize=0) + + class UnbufferedFileObjectClassTestCase(FileObjectClassTestCase): """Repeat the tests from FileObjectClassTestCase with bufsize==0. @@ -1253,6 +1324,7 @@ def test_main(): tests.extend([ NonBlockingTCPTests, FileObjectClassTestCase, + FileObjectInterruptedTestCase, UnbufferedFileObjectClassTestCase, LineBufferedFileObjectClassTestCase, SmallBufferedFileObjectClassTestCase, |