Issue #23285: PEP 475 -- Retry system calls failing with EINTR.

parent 2635d916
......@@ -111,6 +111,16 @@ Please read on for a comprehensive list of user-facing changes.
PEP written by Carl Meyer
PEP 475: Retry system calls failing with EINTR
----------------------------------------------
:pep:`475` adds support for automatic retry of system calls failing with EINTR:
this means that user code doesn't have to deal with EINTR or InterruptedError
manually, and should make it more robust against asynchronous signal reception.
.. seealso::
:pep:`475` -- Retry system calls failing with EINTR
Other Language Changes
......
......@@ -1012,10 +1012,7 @@ class BufferedReader(_BufferedIOMixin):
current_size = 0
while True:
# Read until EOF or until read() would block.
try:
chunk = self.raw.read()
except InterruptedError:
continue
if chunk in empty_values:
nodata_val = chunk
break
......@@ -1034,10 +1031,7 @@ class BufferedReader(_BufferedIOMixin):
chunks = [buf[pos:]]
wanted = max(self.buffer_size, n)
while avail < n:
try:
chunk = self.raw.read(wanted)
except InterruptedError:
continue
if chunk in empty_values:
nodata_val = chunk
break
......@@ -1066,12 +1060,7 @@ class BufferedReader(_BufferedIOMixin):
have = len(self._read_buf) - self._read_pos
if have < want or have <= 0:
to_read = self.buffer_size - have
while True:
try:
current = self.raw.read(to_read)
except InterruptedError:
continue
break
if current:
self._read_buf = self._read_buf[self._read_pos:] + current
self._read_pos = 0
......@@ -1220,8 +1209,6 @@ class BufferedWriter(_BufferedIOMixin):
while self._write_buf:
try:
n = self.raw.write(self._write_buf)
except InterruptedError:
continue
except BlockingIOError:
raise RuntimeError("self.raw should implement RawIOBase: it "
"should not raise BlockingIOError")
......
......@@ -137,9 +137,6 @@ def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0):
try:
pid, status = os.waitpid(pid, 0)
except OSError as exc:
import errno
if exc.errno == errno.EINTR:
continue
if not DEBUG:
cmd = executable
raise DistutilsExecError(
......
......@@ -365,10 +365,7 @@ class Connection(_ConnectionBase):
def _send(self, buf, write=_write):
remaining = len(buf)
while True:
try:
n = write(self._handle, buf)
except InterruptedError:
continue
remaining -= n
if remaining == 0:
break
......@@ -379,10 +376,7 @@ class Connection(_ConnectionBase):
handle = self._handle
remaining = size
while remaining > 0:
try:
chunk = read(handle, remaining)
except InterruptedError:
continue
n = len(chunk)
if n == 0:
if remaining == size:
......@@ -595,13 +589,7 @@ class SocketListener(object):
self._unlink = None
def accept(self):
while True:
try:
s, self._last_accepted = self._socket.accept()
except InterruptedError:
pass
else:
break
s.setblocking(True)
return Connection(s.detach())
......
......@@ -188,8 +188,6 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
finally:
os._exit(code)
except InterruptedError:
pass
except OSError as e:
if e.errno != errno.ECONNABORTED:
raise
......@@ -230,13 +228,7 @@ def read_unsigned(fd):
data = b''
length = UNSIGNED_STRUCT.size
while len(data) < length:
while True:
try:
s = os.read(fd, length - len(data))
except InterruptedError:
pass
else:
break
if not s:
raise EOFError('unexpected EOF')
data += s
......@@ -245,13 +237,7 @@ def read_unsigned(fd):
def write_unsigned(fd, n):
msg = UNSIGNED_STRUCT.pack(n)
while msg:
while True:
try:
nbytes = os.write(fd, msg)
except InterruptedError:
pass
else:
break
if nbytes == 0:
raise RuntimeError('should not get here')
msg = msg[nbytes:]
......
import os
import sys
import signal
import errno
from . import util
......@@ -29,8 +28,6 @@ class Popen(object):
try:
pid, sts = os.waitpid(self.pid, flag)
except OSError as e:
if e.errno == errno.EINTR:
continue
# Child process not yet created. See #1731717
# e.errno == errno.ECHILD == 10
return None
......
......@@ -572,8 +572,6 @@ class SocketIO(io.RawIOBase):
except timeout:
self._timeout_occurred = True
raise
except InterruptedError:
continue
except error as e:
if e.args[0] in _blocking_errnos:
return None
......
......@@ -553,8 +553,6 @@ class ForkingMixIn:
try:
pid, _ = os.waitpid(-1, 0)
self.active_children.discard(pid)
except InterruptedError:
pass
except ChildProcessError:
# we don't have any children, we're done
self.active_children.clear()
......
......@@ -489,14 +489,6 @@ STDOUT = -2
DEVNULL = -3
def _eintr_retry_call(func, *args):
while True:
try:
return func(*args)
except InterruptedError:
continue
# XXX This function is only used by multiprocessing and the test suite,
# but it's here so that it can be imported when Python is compiled without
# threads.
......@@ -963,10 +955,10 @@ class Popen(object):
if self.stdin:
self._stdin_write(input)
elif self.stdout:
stdout = _eintr_retry_call(self.stdout.read)
stdout = self.stdout.read()
self.stdout.close()
elif self.stderr:
stderr = _eintr_retry_call(self.stderr.read)
stderr = self.stderr.read()
self.stderr.close()
self.wait()
else:
......@@ -1410,7 +1402,7 @@ class Popen(object):
# exception (limited in size)
errpipe_data = bytearray()
while True:
part = _eintr_retry_call(os.read, errpipe_read, 50000)
part = os.read(errpipe_read, 50000)
errpipe_data += part
if not part or len(errpipe_data) > 50000:
break
......@@ -1420,7 +1412,7 @@ class Popen(object):
if errpipe_data:
try:
_eintr_retry_call(os.waitpid, self.pid, 0)
os.waitpid(self.pid, 0)
except ChildProcessError:
pass
try:
......@@ -1505,7 +1497,7 @@ class Popen(object):
def _try_wait(self, wait_flags):
"""All callers to this function MUST hold self._waitpid_lock."""
try:
(pid, sts) = _eintr_retry_call(os.waitpid, self.pid, wait_flags)
(pid, sts) = os.waitpid(self.pid, wait_flags)
except ChildProcessError:
# This happens if SIGCLD is set to be ignored or waiting
# for child processes has otherwise been disabled for our
......
"""
This test suite exercises some system calls subject to interruption with EINTR,
to check that it is actually handled transparently.
It is intended to be run by the main test suite within a child process, to
ensure there is no background thread running (so that signals are delivered to
the correct thread).
Signals are generated in-process using setitimer(ITIMER_REAL), which allows
sub-second periodicity (contrarily to signal()).
"""
import io
import os
import signal
import socket
import time
import unittest
from test import support
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
class EINTRBaseTest(unittest.TestCase):
""" Base class for EINTR tests. """
# delay for initial signal delivery
signal_delay = 0.1
# signal delivery periodicity
signal_period = 0.1
# default sleep time for tests - should obviously have:
# sleep_time > signal_period
sleep_time = 0.2
@classmethod
def setUpClass(cls):
cls.orig_handler = signal.signal(signal.SIGALRM, lambda *args: None)
signal.setitimer(signal.ITIMER_REAL, cls.signal_delay,
cls.signal_period)
@classmethod
def tearDownClass(cls):
signal.setitimer(signal.ITIMER_REAL, 0, 0)
signal.signal(signal.SIGALRM, cls.orig_handler)
@classmethod
def _sleep(cls):
# default sleep time
time.sleep(cls.sleep_time)
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
class OSEINTRTest(EINTRBaseTest):
""" EINTR tests for the os module. """
def _test_wait_multiple(self, wait_func):
num = 3
for _ in range(num):
pid = os.fork()
if pid == 0:
self._sleep()
os._exit(0)
for _ in range(num):
wait_func()
def test_wait(self):
self._test_wait_multiple(os.wait)
@unittest.skipUnless(hasattr(os, 'wait3'), 'requires wait3()')
def test_wait3(self):
self._test_wait_multiple(lambda: os.wait3(0))
def _test_wait_single(self, wait_func):
pid = os.fork()
if pid == 0:
self._sleep()
os._exit(0)
else:
wait_func(pid)
def test_waitpid(self):
self._test_wait_single(lambda pid: os.waitpid(pid, 0))
@unittest.skipUnless(hasattr(os, 'wait4'), 'requires wait4()')
def test_wait4(self):
self._test_wait_single(lambda pid: os.wait4(pid, 0))
def test_read(self):
rd, wr = os.pipe()
self.addCleanup(os.close, rd)
# wr closed explicitly by parent
# the payload below are smaller than PIPE_BUF, hence the writes will be
# atomic
datas = [b"hello", b"world", b"spam"]
pid = os.fork()
if pid == 0:
os.close(rd)
for data in datas:
# let the parent block on read()
self._sleep()
os.write(wr, data)
os._exit(0)
else:
self.addCleanup(os.waitpid, pid, 0)
os.close(wr)
for data in datas:
self.assertEqual(data, os.read(rd, len(data)))
def test_write(self):
rd, wr = os.pipe()
self.addCleanup(os.close, wr)
# rd closed explicitly by parent
# we must write enough data for the write() to block
data = b"xyz" * support.PIPE_MAX_SIZE
pid = os.fork()
if pid == 0:
os.close(wr)
read_data = io.BytesIO()
# let the parent block on write()
self._sleep()
while len(read_data.getvalue()) < len(data):
chunk = os.read(rd, 2 * len(data))
read_data.write(chunk)
self.assertEqual(read_data.getvalue(), data)
os._exit(0)
else:
os.close(rd)
written = 0
while written < len(data):
written += os.write(wr, memoryview(data)[written:])
self.assertEqual(0, os.waitpid(pid, 0)[1])
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
class SocketEINTRTest(EINTRBaseTest):
""" EINTR tests for the socket module. """
@unittest.skipUnless(hasattr(socket, 'socketpair'), 'needs socketpair()')
def _test_recv(self, recv_func):
rd, wr = socket.socketpair()
self.addCleanup(rd.close)
# wr closed explicitly by parent
# single-byte payload guard us against partial recv
datas = [b"x", b"y", b"z"]
pid = os.fork()
if pid == 0:
rd.close()
for data in datas:
# let the parent block on recv()
self._sleep()
wr.sendall(data)
os._exit(0)
else:
self.addCleanup(os.waitpid, pid, 0)
wr.close()
for data in datas:
self.assertEqual(data, recv_func(rd, len(data)))
def test_recv(self):
self._test_recv(socket.socket.recv)
@unittest.skipUnless(hasattr(socket.socket, 'recvmsg'), 'needs recvmsg()')
def test_recvmsg(self):
self._test_recv(lambda sock, data: sock.recvmsg(data)[0])
def _test_send(self, send_func):
rd, wr = socket.socketpair()
self.addCleanup(wr.close)
# rd closed explicitly by parent
# we must send enough data for the send() to block
data = b"xyz" * (support.SOCK_MAX_SIZE // 3)
pid = os.fork()
if pid == 0:
wr.close()
# let the parent block on send()
self._sleep()
received_data = bytearray(len(data))
n = 0
while n < len(data):
n += rd.recv_into(memoryview(received_data)[n:])
self.assertEqual(received_data, data)
os._exit(0)
else:
rd.close()
written = 0
while written < len(data):
sent = send_func(wr, memoryview(data)[written:])
# sendall() returns None
written += len(data) if sent is None else sent
self.assertEqual(0, os.waitpid(pid, 0)[1])
def test_send(self):
self._test_send(socket.socket.send)
def test_sendall(self):
self._test_send(socket.socket.sendall)
@unittest.skipUnless(hasattr(socket.socket, 'sendmsg'), 'needs sendmsg()')
def test_sendmsg(self):
self._test_send(lambda sock, data: sock.sendmsg([data]))
def test_accept(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.addCleanup(sock.close)
sock.bind((support.HOST, 0))
_, port = sock.getsockname()
sock.listen()
pid = os.fork()
if pid == 0:
# let parent block on accept()
self._sleep()
with socket.create_connection((support.HOST, port)):
self._sleep()
os._exit(0)
else:
self.addCleanup(os.waitpid, pid, 0)
client_sock, _ = sock.accept()
client_sock.close()
@unittest.skipUnless(hasattr(os, 'mkfifo'), 'needs mkfifo()')
def _test_open(self, do_open_close_reader, do_open_close_writer):
# Use a fifo: until the child opens it for reading, the parent will
# block when trying to open it for writing.
support.unlink(support.TESTFN)
os.mkfifo(support.TESTFN)
self.addCleanup(support.unlink, support.TESTFN)
pid = os.fork()
if pid == 0:
# let the parent block
self._sleep()
do_open_close_reader(support.TESTFN)
os._exit(0)
else:
self.addCleanup(os.waitpid, pid, 0)
do_open_close_writer(support.TESTFN)
def test_open(self):
self._test_open(lambda path: open(path, 'r').close(),
lambda path: open(path, 'w').close())
def test_os_open(self):
self._test_open(lambda path: os.close(os.open(path, os.O_RDONLY)),
lambda path: os.close(os.open(path, os.O_WRONLY)))
def test_main():
support.run_unittest(OSEINTRTest, SocketEINTRTest)
if __name__ == "__main__":
test_main()
import os
import signal
import unittest
from test import script_helper, support
@unittest.skipUnless(os.name == "posix", "only supported on Unix")
class EINTRTests(unittest.TestCase):
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
def test_all(self):
# Run the tester in a sub-process, to make sure there is only one
# thread (for reliable signal delivery).
tester = support.findfile("eintr_tester.py", subdir="eintrdata")
script_helper.assert_python_ok(tester)
if __name__ == "__main__":
unittest.main()
......@@ -587,7 +587,7 @@ class SiginterruptTest(unittest.TestCase):
r, w = os.pipe()
def handler(signum, frame):
pass
1 / 0
signal.signal(signal.SIGALRM, handler)
if interrupt is not None:
......@@ -604,9 +604,8 @@ class SiginterruptTest(unittest.TestCase):
try:
# blocking call: read from a pipe without data
os.read(r, 1)
except OSError as err:
if err.errno != errno.EINTR:
raise
except ZeroDivisionError:
pass
else:
sys.exit(2)
sys.exit(3)
......
......@@ -3590,7 +3590,7 @@ class InterruptedTimeoutBase(unittest.TestCase):
def setUp(self):
super().setUp()
orig_alrm_handler = signal.signal(signal.SIGALRM,
lambda signum, frame: None)
lambda signum, frame: 1 / 0)
self.addCleanup(signal.signal, signal.SIGALRM, orig_alrm_handler)
self.addCleanup(self.setAlarm, 0)
......@@ -3627,13 +3627,11 @@ class InterruptedRecvTimeoutTest(InterruptedTimeoutBase, UDPTestBase):
self.serv.settimeout(self.timeout)
def checkInterruptedRecv(self, func, *args, **kwargs):
# Check that func(*args, **kwargs) raises OSError with an
# Check that func(*args, **kwargs) raises
# errno of EINTR when interrupted by a signal.
self.setAlarm(self.alarm_time)
with self.assertRaises(OSError) as cm:
with self.assertRaises(ZeroDivisionError) as cm:
func(*args, **kwargs)
self.assertNotIsInstance(cm.exception, socket.timeout)
self.assertEqual(cm.exception.errno, errno.EINTR)
def testInterruptedRecvTimeout(self):
self.checkInterruptedRecv(self.serv.recv, 1024)
......@@ -3689,12 +3687,10 @@ class InterruptedSendTimeoutTest(InterruptedTimeoutBase,
# Check that func(*args, **kwargs), run in a loop, raises
# OSError with an errno of EINTR when interrupted by a
# signal.
with self.assertRaises(OSError) as cm:
with self.assertRaises(ZeroDivisionError) as cm:
while True:
self.setAlarm(self.alarm_time)
func(*args, **kwargs)
self.assertNotIsInstance(cm.exception, socket.timeout)
self.assertEqual(cm.exception.errno, errno.EINTR)
# Issue #12958: The following tests have problems on OS X prior to 10.7
@support.requires_mac_ver(10, 7)
......@@ -4062,117 +4058,6 @@ 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 OSError(errno.EINTR, "interrupted")
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.assertEqual(fo.readline(size), "This is the first line\n")
self.assertEqual(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('utf-8')
while len(data) != len(expecting):
part = fo.read(size)
if not part:
break
data += part
self.assertEqual(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.assertEqual(fo.readline(size), b"a\n")
self.assertEqual(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.
......@@ -5388,7 +5273,6 @@ def test_main():
tests.extend([
NonBlockingTCPTests,
FileObjectClassTestCase,
FileObjectInterruptedTestCase,
UnbufferedFileObjectClassTestCase,
LineBufferedFileObjectClassTestCase,
SmallBufferedFileObjectClassTestCase,
......
......@@ -2421,25 +2421,6 @@ class ProcessTestCaseNoPoll(ProcessTestCase):
ProcessTestCase.tearDown(self)
class HelperFunctionTests(unittest.TestCase):
@unittest.skipIf(mswindows, "errno and EINTR make no sense on windows")
def test_eintr_retry_call(self):
record_calls = []
def fake_os_func(*args):
record_calls.append(args)
if len(record_calls) == 2:
raise OSError(errno.EINTR, "fake interrupted system call")
return tuple(reversed(args))
self.assertEqual((999, 256),
subprocess._eintr_retry_call(fake_os_func, 256, 999))
self.assertEqual([(256, 999)], record_calls)
# This time there will be an EINTR so it will loop once.
self.assertEqual((666,),
subprocess._eintr_retry_call(fake_os_func, 666))
self.assertEqual([(256, 999), (666,), (666,)], record_calls)
@unittest.skipUnless(mswindows, "Windows-specific tests")
class CommandsWithSpaces (BaseTestCase):
......@@ -2528,7 +2509,6 @@ def test_main():
Win32ProcessTestCase,
CommandTests,
ProcessTestCaseNoPoll,
HelperFunctionTests,
CommandsWithSpaces,
ContextManagerTests,
)
......
......@@ -10,6 +10,8 @@ Release date: TBA
Core and Builtins
-----------------
- Issue #23285: PEP 475 - EINTR handling.
- Issue #22735: Fix many edge cases (including crashes) involving custom mro()
implementations.
......
......@@ -218,6 +218,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
#ifdef HAVE_FSTAT
struct stat fdfstat;
#endif
int async_err = 0;
assert(PyFileIO_Check(oself));
if (self->fd >= 0) {
......@@ -360,6 +361,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
errno = 0;
if (opener == Py_None) {
do {
Py_BEGIN_ALLOW_THREADS
#ifdef MS_WINDOWS
if (widename != NULL)
......@@ -369,6 +371,8 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
self->fd = open(name, flags, 0666);
Py_END_ALLOW_THREADS
} while (self->fd < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
}
else {
PyObject *fdobj;
......@@ -397,6 +401,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
fd_is_own = 1;
if (self->fd < 0) {
if (!async_err)
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj);
goto error;
}
......@@ -550,7 +555,7 @@ fileio_readinto(fileio *self, PyObject *args)
{
Py_buffer pbuf;
Py_ssize_t n, len;
int err;
int err, async_err = 0;
if (self->fd < 0)
return err_closed();
......@@ -562,6 +567,7 @@ fileio_readinto(fileio *self, PyObject *args)
if (_PyVerify_fd(self->fd)) {
len = pbuf.len;
do {
Py_BEGIN_ALLOW_THREADS
errno = 0;
#ifdef MS_WINDOWS
......@@ -572,6 +578,8 @@ fileio_readinto(fileio *self, PyObject *args)
n = read(self->fd, pbuf.buf, len);
#endif
Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
} else
n = -1;
err = errno;
......@@ -580,6 +588,7 @@ fileio_readinto(fileio *self, PyObject *args)
if (err == EAGAIN)
Py_RETURN_NONE;
errno = err;
if (!async_err)
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
......@@ -627,6 +636,7 @@ fileio_readall(fileio *self)
Py_ssize_t bytes_read = 0;
Py_ssize_t n;
size_t bufsize;
int async_err = 0;
if (self->fd < 0)
return err_closed();
......@@ -673,6 +683,7 @@ fileio_readall(fileio *self)
return NULL;
}
}
do {
Py_BEGIN_ALLOW_THREADS
errno = 0;
n = bufsize - bytes_read;
......@@ -684,16 +695,11 @@ fileio_readall(fileio *self)
n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, n);
#endif
Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
if (n == 0)
break;
if (n < 0) {
if (errno == EINTR) {
if (PyErr_CheckSignals()) {
Py_DECREF(result);
return NULL;
}
continue;
}
if (errno == EAGAIN) {
if (bytes_read > 0)
break;
......@@ -701,6 +707,7 @@ fileio_readall(fileio *self)
Py_RETURN_NONE;
}
Py_DECREF(result);
if (!async_err)
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
......@@ -723,6 +730,7 @@ fileio_read(fileio *self, PyObject *args)
char *ptr;
Py_ssize_t n;
Py_ssize_t size = -1;
int async_err = 0;
PyObject *bytes;
if (self->fd < 0)
......@@ -747,6 +755,7 @@ fileio_read(fileio *self, PyObject *args)
ptr = PyBytes_AS_STRING(bytes);
if (_PyVerify_fd(self->fd)) {
do {
Py_BEGIN_ALLOW_THREADS
errno = 0;
#ifdef MS_WINDOWS
......@@ -755,6 +764,8 @@ fileio_read(fileio *self, PyObject *args)
n = read(self->fd, ptr, size);
#endif
Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
} else
n = -1;
......@@ -764,6 +775,7 @@ fileio_read(fileio *self, PyObject *args)
if (err == EAGAIN)
Py_RETURN_NONE;
errno = err;
if (!async_err)
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
......@@ -783,7 +795,7 @@ fileio_write(fileio *self, PyObject *args)
{
Py_buffer pbuf;
Py_ssize_t n, len;
int err;
int err, async_err = 0;
if (self->fd < 0)
return err_closed();
......@@ -794,6 +806,7 @@ fileio_write(fileio *self, PyObject *args)
return NULL;
if (_PyVerify_fd(self->fd)) {
do {
Py_BEGIN_ALLOW_THREADS
errno = 0;
len = pbuf.len;
......@@ -804,14 +817,15 @@ fileio_write(fileio *self, PyObject *args)
binary and the length is greater than 66,000 bytes (or less,
depending on heap usage). */
len = 32767;
}
else if (len > INT_MAX)
} else if (len > INT_MAX)
len = INT_MAX;
n = write(self->fd, pbuf.buf, (int)len);
#else
n = write(self->fd, pbuf.buf, len);
#endif
Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
} else
n = -1;
err = errno;
......@@ -822,6 +836,7 @@ fileio_write(fileio *self, PyObject *args)
if (err == EAGAIN)
Py_RETURN_NONE;
errno = err;
if (!async_err)
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
......
This diff is collapsed.
......@@ -2037,6 +2037,7 @@ sock_accept(PySocketSockObject *s)
PyObject *addr = NULL;
PyObject *res = NULL;
int timeout;
int async_err = 0;
#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC)
/* accept4() is available on Linux 2.6.28+ and glibc 2.10 */
static int accept4_works = -1;
......@@ -2050,7 +2051,7 @@ sock_accept(PySocketSockObject *s)
return select_error();
BEGIN_SELECT_LOOP(s)
do {
Py_BEGIN_ALLOW_THREADS
timeout = internal_select_ex(s, 0, interval);
if (!timeout) {
......@@ -2070,7 +2071,7 @@ sock_accept(PySocketSockObject *s)
#endif
}
Py_END_ALLOW_THREADS
} while (newfd < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
if (timeout == 1) {
PyErr_SetString(socket_timeout, "timed out");
return NULL;
......@@ -2078,7 +2079,7 @@ sock_accept(PySocketSockObject *s)
END_SELECT_LOOP(s)
if (newfd == INVALID_SOCKET)
return s->errorhandler();
return (!async_err) ? s->errorhandler() : NULL;
#ifdef MS_WINDOWS
if (!SetHandleInformation((HANDLE)newfd, HANDLE_FLAG_INHERIT, 0)) {
......@@ -2341,6 +2342,10 @@ sock_close(PySocketSockObject *s)
{
SOCKET_T fd;
/* We do not want to retry upon EINTR: see http://lwn.net/Articles/576478/
* and http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
* for more details.
*/
if ((fd = s->sock_fd) != -1) {
s->sock_fd = -1;
Py_BEGIN_ALLOW_THREADS
......@@ -2513,10 +2518,8 @@ sock_connect_ex(PySocketSockObject *s, PyObject *addro)
/* Signals are not errors (though they may raise exceptions). Adapted
from PyErr_SetFromErrnoWithFilenameObject(). */
#ifdef EINTR
if (res == EINTR && PyErr_CheckSignals())
return NULL;
#endif
return PyLong_FromLong((long) res);
}
......@@ -2650,6 +2653,7 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
{
Py_ssize_t outlen = -1;
int timeout;
int async_err = 0;
if (!IS_SELECTABLE(s)) {
select_error();
......@@ -2661,6 +2665,7 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
}
BEGIN_SELECT_LOOP(s)
do {
Py_BEGIN_ALLOW_THREADS
timeout = internal_select_ex(s, 0, interval);
if (!timeout) {
......@@ -2673,6 +2678,7 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
#endif
}
Py_END_ALLOW_THREADS
} while (outlen < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
if (timeout == 1) {
PyErr_SetString(socket_timeout, "timed out");
......@@ -2682,6 +2688,7 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags)
if (outlen < 0) {
/* Note: the call to errorhandler() ALWAYS indirectly returned
NULL, so ignore its return value */
if (!async_err)
s->errorhandler();
return -1;
}
......@@ -2819,6 +2826,7 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags,
int timeout;
Py_ssize_t n = -1;
socklen_t addrlen;
int async_err = 0;
*addr = NULL;
......@@ -2831,6 +2839,7 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags,
}
BEGIN_SELECT_LOOP(s)
do {
Py_BEGIN_ALLOW_THREADS
memset(&addrbuf, 0, addrlen);
timeout = internal_select_ex(s, 0, interval);
......@@ -2846,6 +2855,7 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags,
#endif
}
Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
if (timeout == 1) {
PyErr_SetString(socket_timeout, "timed out");
......@@ -2853,6 +2863,7 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags,
}
END_SELECT_LOOP(s)
if (n < 0) {
if (!async_err)
s->errorhandler();
return -1;
}
......@@ -2993,6 +3004,7 @@ sock_recvmsg_guts(PySocketSockObject *s, struct iovec *iov, int iovlen,
{
ssize_t bytes_received = -1;
int timeout;
int async_err = 0;
sock_addr_t addrbuf;
socklen_t addrbuflen;
struct msghdr msg = {0};
......@@ -3028,6 +3040,7 @@ sock_recvmsg_guts(PySocketSockObject *s, struct iovec *iov, int iovlen,
}
BEGIN_SELECT_LOOP(s)
do {
Py_BEGIN_ALLOW_THREADS;
msg.msg_name = SAS2SA(&addrbuf);
msg.msg_namelen = addrbuflen;
......@@ -3043,9 +3056,12 @@ sock_recvmsg_guts(PySocketSockObject *s, struct iovec *iov, int iovlen,
PyErr_SetString(socket_timeout, "timed out");
goto finally;
}
} while (bytes_received < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
END_SELECT_LOOP(s)
if (bytes_received < 0) {
if (!async_err)
s->errorhandler();
goto finally;
}
......@@ -3305,6 +3321,7 @@ sock_send(PySocketSockObject *s, PyObject *args)
{
char *buf;
Py_ssize_t len, n = -1;
int async_err = 0;
int flags = 0, timeout;
Py_buffer pbuf;
......@@ -3319,6 +3336,7 @@ sock_send(PySocketSockObject *s, PyObject *args)
len = pbuf.len;
BEGIN_SELECT_LOOP(s)
do {
Py_BEGIN_ALLOW_THREADS
timeout = internal_select_ex(s, 1, interval);
if (!timeout) {
......@@ -3331,6 +3349,7 @@ sock_send(PySocketSockObject *s, PyObject *args)
#endif
}
Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
if (timeout == 1) {
PyBuffer_Release(&pbuf);
PyErr_SetString(socket_timeout, "timed out");
......@@ -3340,7 +3359,7 @@ sock_send(PySocketSockObject *s, PyObject *args)
PyBuffer_Release(&pbuf);
if (n < 0)
return s->errorhandler();
return (!async_err) ? s->errorhandler() : NULL;
return PyLong_FromSsize_t(n);
}
......@@ -3359,7 +3378,8 @@ sock_sendall(PySocketSockObject *s, PyObject *args)
{
char *buf;
Py_ssize_t len, n = -1;
int flags = 0, timeout, saved_errno;
int async_err = 0;
int flags = 0, timeout;
Py_buffer pbuf;
if (!PyArg_ParseTuple(args, "y*|i:sendall", &pbuf, &flags))
......@@ -3391,29 +3411,16 @@ sock_sendall(PySocketSockObject *s, PyObject *args)
PyErr_SetString(socket_timeout, "timed out");
return NULL;
}
/* PyErr_CheckSignals() might change errno */
saved_errno = errno;
/* We must run our signal handlers before looping again.
send() can return a successful partial write when it is
interrupted, so we can't restrict ourselves to EINTR. */
if (PyErr_CheckSignals()) {
PyBuffer_Release(&pbuf);
return NULL;
}
if (n < 0) {
/* If interrupted, try again */
if (saved_errno == EINTR)
continue;
else
break;
}
if (n >= 0) {
buf += n;
len -= n;
} while (len > 0);
}
} while (len > 0 && (n >= 0 || errno == EINTR) &&
!(async_err = PyErr_CheckSignals()));
PyBuffer_Release(&pbuf);
if (n < 0)
return s->errorhandler();
if (n < 0 || async_err)
return (!async_err) ? s->errorhandler() : NULL;
Py_INCREF(Py_None);
return Py_None;
......@@ -3439,6 +3446,7 @@ sock_sendto(PySocketSockObject *s, PyObject *args)
Py_ssize_t len, arglen;
sock_addr_t addrbuf;
int addrlen, n = -1, flags, timeout;
int async_err = 0;
flags = 0;
arglen = PyTuple_Size(args);
......@@ -3473,6 +3481,7 @@ sock_sendto(PySocketSockObject *s, PyObject *args)
}
BEGIN_SELECT_LOOP(s)
do {
Py_BEGIN_ALLOW_THREADS
timeout = internal_select_ex(s, 1, interval);
if (!timeout) {
......@@ -3487,6 +3496,7 @@ sock_sendto(PySocketSockObject *s, PyObject *args)
#endif
}
Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
if (timeout == 1) {
PyBuffer_Release(&pbuf);
......@@ -3496,7 +3506,7 @@ sock_sendto(PySocketSockObject *s, PyObject *args)
END_SELECT_LOOP(s)
PyBuffer_Release(&pbuf);
if (n < 0)
return s->errorhandler();
return (!async_err) ? s->errorhandler() : NULL;
return PyLong_FromSsize_t(n);
}
......@@ -3528,6 +3538,7 @@ sock_sendmsg(PySocketSockObject *s, PyObject *args)
void *controlbuf = NULL;
size_t controllen, controllen_last;
ssize_t bytes_sent = -1;
int async_err = 0;
int addrlen, timeout, flags = 0;
PyObject *data_arg, *cmsg_arg = NULL, *addr_arg = NULL, *data_fast = NULL,
*cmsg_fast = NULL, *retval = NULL;
......@@ -3685,6 +3696,7 @@ sock_sendmsg(PySocketSockObject *s, PyObject *args)
}
BEGIN_SELECT_LOOP(s)
do {
Py_BEGIN_ALLOW_THREADS;
timeout = internal_select_ex(s, 1, interval);
if (!timeout)
......@@ -3694,9 +3706,12 @@ sock_sendmsg(PySocketSockObject *s, PyObject *args)
PyErr_SetString(socket_timeout, "timed out");
goto finally;
}
} while (bytes_sent < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
END_SELECT_LOOP(s)
if (bytes_sent < 0) {
if (!async_err)
s->errorhandler();
goto finally;
}
......
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