From aafdca895b5384455de662f89955f240daaa8caf Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Mon, 4 Jan 2010 04:50:36 +0000 Subject: [PATCH] Merged revisions 74426 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk ........ r74426 | gregory.p.smith | 2009-08-13 11:54:50 -0700 (Thu, 13 Aug 2009) | 4 lines 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. ........ --- Lib/socket.py | 14 +++-- Lib/test/test_socket.py | 113 ++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 4 ++ 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py index 9133411c312..a82e48dceff 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -49,9 +49,11 @@ from _socket import * import os, sys, io 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)) @@ -212,7 +214,13 @@ class SocketIO(io.RawIOBase): def readinto(self, b): self._checkClosed() self._checkReadable() - return self._sock.recv_into(b) + while True: + try: + return self._sock.recv_into(b) + except error as e: + if e.args[0] == EINTR: + continue + raise def write(self, b): self._checkClosed() diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 09e2cc89de1..4326e6506c2 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -4,6 +4,7 @@ import unittest from test import support import errno +import io import socket import select import _thread as thread @@ -906,6 +907,117 @@ class FileObjectClassTestCase(SocketConnectedTest): pass +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_into(self, buffer): + data = next(self._recv_step)() + assert len(buffer) >= len(data) + buffer[:len(data)] = data + return len(data) + + def _decref_socketios(self): + pass + + def _textiowrap_for_test(self, buffering=-1): + raw = socket.SocketIO(self, "r") + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + return raw + buffer = io.BufferedReader(raw, buffering) + text = io.TextIOWrapper(buffer, None, None) + text.mode = "rb" + return text + + @staticmethod + def _raise_eintr(): + raise socket.error(errno.EINTR) + + def _textiowrap_mock_socket(self, mock, buffering=-1): + raw = socket.SocketIO(mock, "r") + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + return raw + buffer = io.BufferedReader(raw, buffering) + text = io.TextIOWrapper(buffer, None, None) + text.mode = "rb" + return text + + def _test_readline(self, size=-1, buffering=-1): + mock_sock = self.MockSocket(recv_funcs=[ + lambda : b"This is the first line\nAnd the sec", + self._raise_eintr, + lambda : b"ond line is here\n", + lambda : b"", + lambda : b"", # XXX(gps): io library does an extra EOF read + ]) + fo = mock_sock._textiowrap_for_test(buffering=buffering) + 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, buffering=-1): + mock_sock = self.MockSocket(recv_funcs=[ + lambda : b"This is the first line\nAnd the sec", + self._raise_eintr, + lambda : b"ond line is here\n", + lambda : b"", + lambda : b"", # XXX(gps): io library does an extra EOF read + ]) + expecting = (b"This is the first line\n" + b"And the second line is here\n") + fo = mock_sock._textiowrap_for_test(buffering=buffering) + if buffering == 0: + data = b'' + else: + data = '' + expecting = expecting.decode('utf8') + while len(data) != len(expecting): + part = fo.read(size) + if not part: + break + data += part + self.assertEquals(data, expecting) + + 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(buffering=1024) + self._test_readline(size=100, buffering=1024) + self._test_read(buffering=1024) + self._test_read(size=100, buffering=1024) + + def _test_readline_no_buffer(self, size=-1): + mock_sock = self.MockSocket(recv_funcs=[ + lambda : b"a", + lambda : b"\n", + lambda : b"B", + self._raise_eintr, + lambda : b"b", + lambda : b"", + ]) + fo = mock_sock._textiowrap_for_test(buffering=0) + self.assertEquals(fo.readline(size), b"a\n") + self.assertEquals(fo.readline(size), b"Bb") + + def test_no_buffer(self): + self._test_readline_no_buffer() + self._test_readline_no_buffer(size=4) + self._test_read(buffering=0) + self._test_read(size=100, buffering=0) + + class UnbufferedFileObjectClassTestCase(FileObjectClassTestCase): """Repeat the tests from FileObjectClassTestCase with bufsize==0. @@ -1310,6 +1422,7 @@ def test_main(): tests.extend([ NonBlockingTCPTests, FileObjectClassTestCase, + FileObjectInterruptedTestCase, UnbufferedFileObjectClassTestCase, LineBufferedFileObjectClassTestCase, SmallBufferedFileObjectClassTestCase, diff --git a/Misc/NEWS b/Misc/NEWS index 1f29728aefb..c78c960751d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -194,6 +194,10 @@ C-API Library ------- +- Issue #1628205: 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. + - Issue #7471: Improve the performance of GzipFile's buffering mechanism, and make it implement the `io.BufferedIOBase` ABC to allow for further speedups by wrapping it in an `io.BufferedReader`. Patch by Nir Aides.