Commit c4ad0345 authored by Gregory P. Smith's avatar Gregory P. Smith

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.
parent aa66a968
...@@ -86,9 +86,11 @@ except ImportError: ...@@ -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): ...@@ -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): ...@@ -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:
data = self._sock.recv(rbufsize) try:
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): ...@@ -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.
data = self._sock.recv(left) try:
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): ...@@ -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 data != "\n": while True:
data = recv(1) try:
if not data: while data != "\n":
break data = recv(1)
buffers.append(data) 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) 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:
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: if not data:
break break
nl = data.find('\n') nl = data.find('\n')
...@@ -430,7 +468,12 @@ class _fileobject(object): ...@@ -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:
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: if not data:
break break
left = size - buf_len left = size - buf_len
......
...@@ -858,6 +858,77 @@ class FileObjectClassTestCase(SocketConnectedTest): ...@@ -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(): ...@@ -1253,6 +1324,7 @@ def test_main():
tests.extend([ tests.extend([
NonBlockingTCPTests, NonBlockingTCPTests,
FileObjectClassTestCase, FileObjectClassTestCase,
FileObjectInterruptedTestCase,
UnbufferedFileObjectClassTestCase, UnbufferedFileObjectClassTestCase,
LineBufferedFileObjectClassTestCase, LineBufferedFileObjectClassTestCase,
SmallBufferedFileObjectClassTestCase, SmallBufferedFileObjectClassTestCase,
......
...@@ -362,6 +362,10 @@ Library ...@@ -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].
......
...@@ -2736,8 +2736,21 @@ sock_sendall(PySocketSockObject *s, PyObject *args) ...@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment