Merged revisions 74426 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r74426 | gregory.p.smith | 2009-08-13 14:54:50 -0400 (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.
........
This commit is contained in:
Jean-Paul Calderone 2010-05-23 15:22:08 +00:00
parent add85daa87
commit c28d554db8
4 changed files with 146 additions and 14 deletions

View File

@ -84,9 +84,11 @@ def ssl(sock, keyfile=None, certfile=None):
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))
@ -280,10 +282,22 @@ def __del__(self):
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()
@ -326,7 +340,12 @@ def read(self, size=-1):
# 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)
@ -350,7 +369,12 @@ def read(self, size=-1):
# 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)
@ -393,17 +417,31 @@ def readline(self, size=-1):
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')
@ -427,7 +465,12 @@ def readline(self, size=-1):
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

View File

@ -834,6 +834,77 @@ def testClosedAttr(self):
def _testClosedAttr(self):
self.assert_(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.
@ -1229,6 +1300,7 @@ def test_main():
tests.extend([
NonBlockingTCPTests,
FileObjectClassTestCase,
FileObjectInterruptedTestCase,
UnbufferedFileObjectClassTestCase,
LineBufferedFileObjectClassTestCase,
SmallBufferedFileObjectClassTestCase,

View File

@ -55,6 +55,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 #3924: Ignore cookies with invalid "version" field in cookielib.
- Issue #6268: Fix seek() method of codecs.open(), don't read or write the BOM

View File

@ -2723,8 +2723,21 @@ sock_sendall(PySocketSockObject *s, PyObject *args)
#else
n = send(s->sock_fd, buf, len, flags);
#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;
}
buf += n;
len -= n;
} while (len > 0);