Index: Lib/socket.py =================================================================== --- Lib/socket.py (revision 74327) +++ Lib/socket.py (working copy) @@ -90,6 +90,8 @@ except ImportError: EBADF = 9 +from errno import EINTR, EAGAIN + __all__ = ["getfqdn", "create_connection"] __all__.extend(os._get_exports_list(_socket)) @@ -289,6 +291,8 @@ buffer = "".join(self._wbuf) self._wbuf = [] self._wbuf_len = 0 + # TODO(gps): we cannot recover from EINTR when using + # sendall, we should loop using send instead. self._sock.sendall(buffer) def fileno(self): @@ -329,7 +333,12 @@ # 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: + self._rbuf = buf # Interrupted, unconsume it. + raise if not data: break buf.write(data) @@ -353,7 +362,12 @@ # 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: + self._rbuf = buf # Interrupted, unconsume it. + raise if not data: break n = len(data) @@ -397,7 +411,15 @@ data = None recv = self._sock.recv while data != "\n": - data = recv(1) + try: + data = recv(1) + except error, e: + if e[0] == EINTR: + del buffers[0] # This data is still in buf. + buf.seek(0, 2) # end + buf.write(''.join(buffers)) + self._rbuf = buf # Interrupted, unconsume it. + raise if not data: break buffers.append(data) @@ -406,7 +428,12 @@ 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: + self._rbuf = buf # Interrupted, unconsume it. + raise if not data: break nl = data.find('\n') @@ -430,7 +457,12 @@ 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: + self._rbuf = buf # Interrupted, unconsume it. + raise if not data: break left = size - buf_len Index: Lib/test/test_socket.py =================================================================== --- Lib/test/test_socket.py (revision 74327) +++ Lib/test/test_socket.py (working copy) @@ -858,6 +858,74 @@ def _testClosedAttr(self): self.assertTrue(not self.cli_file.closed) + +class FileObjectInterruptedTestCase(unittest.TestCase): + """Test that the file object correctly handles being interrupted by a signal.""" + + class MockSocket(object): + def __init__(self, recv_funcs=()): + # Build a generator that returns functions 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, **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(), "This is the first line\n") + self.assertRaises(socket.error, fo.readline) + self.assertEquals(fo.readline(), "And the second line is here\n") + + def _test_read(self, **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.assertRaises(socket.error, fo.read) + self.assertEquals(fo.read(), "This is the first line\n" + "And the second line is here\n") + + def test_default(self): + self._test_readline() + self._test_read() + + def test_set_buffer(self): + self._test_readline(bufsize=1024) + self._test_read(bufsize=1024) + + def _test_readline_no_buffer(self, **kwargs): + mock_sock = self.MockSocket(recv_funcs=[ + lambda : "a", + lambda : "\n", + lambda : "B", + self._raise_eintr, + lambda : "b", + lambda : "", + ]) + fo = socket._fileobject(mock_sock, **kwargs) + self.assertEquals(fo.readline(), "a\n") + self.assertRaises(socket.error, fo.readline) + self.assertEquals(fo.readline(), "Bb") + + def test_no_buffer(self): + self._test_readline_no_buffer(bufsize=0) + self._test_read(bufsize=0) + + class UnbufferedFileObjectClassTestCase(FileObjectClassTestCase): """Repeat the tests from FileObjectClassTestCase with bufsize==0. @@ -1253,6 +1321,7 @@ tests.extend([ NonBlockingTCPTests, FileObjectClassTestCase, + FileObjectInterruptedTestCase, UnbufferedFileObjectClassTestCase, LineBufferedFileObjectClassTestCase, SmallBufferedFileObjectClassTestCase,