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.
This commit is contained in:
Gregory P. Smith 2009-08-13 18:54:50 +00:00
parent aa66a968d4
commit c4ad0345cf
4 changed files with 146 additions and 14 deletions

View file

@ -86,9 +86,11 @@ except ImportError:
from StringIO import StringIO from StringIO import StringIO
try: try:
from errno import EBADF import errno
except ImportError: except ImportError:
EBADF = 9 errno = None
EBADF = getattr(errno, 'EBADF', 9)
EINTR = getattr(errno, 'EINTR', 4)
__all__ = ["getfqdn", "create_connection"] __all__ = ["getfqdn", "create_connection"]
__all__.extend(os._get_exports_list(_socket)) __all__.extend(os._get_exports_list(_socket))
@ -286,10 +288,22 @@ class _fileobject(object):
def flush(self): def flush(self):
if self._wbuf: if self._wbuf:
buffer = "".join(self._wbuf) data = "".join(self._wbuf)
self._wbuf = [] self._wbuf = []
self._wbuf_len = 0 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): def fileno(self):
return self._sock.fileno() return self._sock.fileno()
@ -329,7 +343,12 @@ class _fileobject(object):
# Read until EOF # Read until EOF
self._rbuf = StringIO() # reset _rbuf. we consume it via buf. self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
while True: while True:
try:
data = self._sock.recv(rbufsize) data = self._sock.recv(rbufsize)
except error, e:
if e[0] == EINTR:
continue
raise
if not data: if not data:
break break
buf.write(data) buf.write(data)
@ -353,7 +372,12 @@ class _fileobject(object):
# than that. The returned data string is short lived # than that. The returned data string is short lived
# as we copy it into a StringIO and free it. This avoids # as we copy it into a StringIO and free it. This avoids
# fragmentation issues on many platforms. # fragmentation issues on many platforms.
try:
data = self._sock.recv(left) data = self._sock.recv(left)
except error, e:
if e[0] == EINTR:
continue
raise
if not data: if not data:
break break
n = len(data) n = len(data)
@ -396,17 +420,31 @@ class _fileobject(object):
self._rbuf = StringIO() # reset _rbuf. we consume it via buf. self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
data = None data = None
recv = self._sock.recv recv = self._sock.recv
while True:
try:
while data != "\n": while data != "\n":
data = recv(1) data = recv(1)
if not data: if not data:
break break
buffers.append(data) 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) return "".join(buffers)
buf.seek(0, 2) # seek end buf.seek(0, 2) # seek end
self._rbuf = StringIO() # reset _rbuf. we consume it via buf. self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
while True: while True:
try:
data = self._sock.recv(self._rbufsize) data = self._sock.recv(self._rbufsize)
except error, e:
if e[0] == EINTR:
continue
raise
if not data: if not data:
break break
nl = data.find('\n') nl = data.find('\n')
@ -430,7 +468,12 @@ class _fileobject(object):
return rv return rv
self._rbuf = StringIO() # reset _rbuf. we consume it via buf. self._rbuf = StringIO() # reset _rbuf. we consume it via buf.
while True: while True:
try:
data = self._sock.recv(self._rbufsize) data = self._sock.recv(self._rbufsize)
except error, e:
if e[0] == EINTR:
continue
raise
if not data: if not data:
break break
left = size - buf_len left = size - buf_len

View file

@ -858,6 +858,77 @@ class FileObjectClassTestCase(SocketConnectedTest):
def _testClosedAttr(self): def _testClosedAttr(self):
self.assertTrue(not self.cli_file.closed) 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): class UnbufferedFileObjectClassTestCase(FileObjectClassTestCase):
"""Repeat the tests from FileObjectClassTestCase with bufsize==0. """Repeat the tests from FileObjectClassTestCase with bufsize==0.
@ -1253,6 +1324,7 @@ def test_main():
tests.extend([ tests.extend([
NonBlockingTCPTests, NonBlockingTCPTests,
FileObjectClassTestCase, FileObjectClassTestCase,
FileObjectInterruptedTestCase,
UnbufferedFileObjectClassTestCase, UnbufferedFileObjectClassTestCase,
LineBufferedFileObjectClassTestCase, LineBufferedFileObjectClassTestCase,
SmallBufferedFileObjectClassTestCase, SmallBufferedFileObjectClassTestCase,

View file

@ -362,6 +362,10 @@ Library
- Issue #4660: If a multiprocessing.JoinableQueue.put() was preempted, it was - Issue #4660: If a multiprocessing.JoinableQueue.put() was preempted, it was
possible to get a spurious 'task_done() called too many times' error. possible to get a spurious 'task_done() called too many times' error.
- 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 #6595: The Decimal constructor now allows arbitrary Unicode - Issue #6595: The Decimal constructor now allows arbitrary Unicode
decimal digits in input, as recommended by the standard. Previously decimal digits in input, as recommended by the standard. Previously
it was restricted to accepting [0-9]. it was restricted to accepting [0-9].

View file

@ -2736,8 +2736,21 @@ sock_sendall(PySocketSockObject *s, PyObject *args)
#else #else
n = send(s->sock_fd, buf, len, flags); n = send(s->sock_fd, buf, len, flags);
#endif #endif
if (n < 0) if (n < 0) {
#ifdef EINTR
/* We must handle EINTR here as there is no way for
* the caller to know how much was sent otherwise. */
if (errno == EINTR) {
/* Run signal handlers. If an exception was
* raised, abort and leave this socket in
* an unknown state. */
if (PyErr_CheckSignals())
return NULL;
continue;
}
#endif
break; break;
}
buf += n; buf += n;
len -= n; len -= n;
} while (len > 0); } while (len > 0);