diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 461a14b16fd..2c6c3a20c58 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -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 diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 98b87f74824..b0f01149994 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -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 + chunk = self.raw.read() 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 + chunk = self.raw.read(wanted) 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 + current = self.raw.read(to_read) 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") diff --git a/Lib/distutils/spawn.py b/Lib/distutils/spawn.py index 22e87e8fdb9..5dd415a283d 100644 --- a/Lib/distutils/spawn.py +++ b/Lib/distutils/spawn.py @@ -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( diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index 863fb040d81..9a8ae29a9ef 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -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 + n = write(self._handle, buf) 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 + chunk = read(handle, remaining) 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, self._last_accepted = self._socket.accept() s.setblocking(True) return Connection(s.detach()) diff --git a/Lib/multiprocessing/forkserver.py b/Lib/multiprocessing/forkserver.py index 5c0a1bd2d48..b27cba52e00 100644 --- a/Lib/multiprocessing/forkserver.py +++ b/Lib/multiprocessing/forkserver.py @@ -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 + s = os.read(fd, length - len(data)) 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 + nbytes = os.write(fd, msg) if nbytes == 0: raise RuntimeError('should not get here') msg = msg[nbytes:] diff --git a/Lib/multiprocessing/popen_fork.py b/Lib/multiprocessing/popen_fork.py index 367e72e2b1c..d2ebd7cfbe1 100644 --- a/Lib/multiprocessing/popen_fork.py +++ b/Lib/multiprocessing/popen_fork.py @@ -1,7 +1,6 @@ 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 diff --git a/Lib/socket.py b/Lib/socket.py index 5c26dd7ecf6..9c39c69eace 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -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 diff --git a/Lib/socketserver.py b/Lib/socketserver.py index c43f1be578d..0ce8e810658 100644 --- a/Lib/socketserver.py +++ b/Lib/socketserver.py @@ -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() diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 2855d013239..25ffefff6c4 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -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 diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py new file mode 100644 index 00000000000..40dca847d00 --- /dev/null +++ b/Lib/test/eintrdata/eintr_tester.py @@ -0,0 +1,260 @@ +""" +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() diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py new file mode 100644 index 00000000000..8b5f5070e66 --- /dev/null +++ b/Lib/test/test_eintr.py @@ -0,0 +1,20 @@ +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() diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 8f3dde4d4fe..774fc809a15 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -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) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 9cd9dada8fc..f7cacf05ff4 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -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, diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 538111564af..3d34859ed48 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -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, ) diff --git a/Misc/NEWS b/Misc/NEWS index a70743e9513..af1c53dcd1f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -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. diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c index 80af83a7a49..24355134000 100644 --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -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,15 +361,18 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) errno = 0; if (opener == Py_None) { - Py_BEGIN_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS #ifdef MS_WINDOWS - if (widename != NULL) - self->fd = _wopen(widename, flags, 0666); - else + if (widename != NULL) + self->fd = _wopen(widename, flags, 0666); + else #endif - self->fd = open(name, flags, 0666); + self->fd = open(name, flags, 0666); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS + } while (self->fd < 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); } else { PyObject *fdobj; @@ -397,7 +401,8 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) fd_is_own = 1; if (self->fd < 0) { - PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj); + 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,16 +567,19 @@ fileio_readinto(fileio *self, PyObject *args) if (_PyVerify_fd(self->fd)) { len = pbuf.len; - Py_BEGIN_ALLOW_THREADS - errno = 0; + do { + Py_BEGIN_ALLOW_THREADS + errno = 0; #ifdef MS_WINDOWS - if (len > INT_MAX) - len = INT_MAX; - n = read(self->fd, pbuf.buf, (int)len); + if (len > INT_MAX) + len = INT_MAX; + n = read(self->fd, pbuf.buf, (int)len); #else - n = read(self->fd, pbuf.buf, len); + n = read(self->fd, pbuf.buf, len); #endif - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS + } while (n < 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); } else n = -1; err = errno; @@ -580,7 +588,8 @@ fileio_readinto(fileio *self, PyObject *args) if (err == EAGAIN) Py_RETURN_NONE; errno = err; - PyErr_SetFromErrno(PyExc_IOError); + 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,27 +683,23 @@ fileio_readall(fileio *self) return NULL; } } - Py_BEGIN_ALLOW_THREADS - errno = 0; - n = bufsize - bytes_read; + do { + Py_BEGIN_ALLOW_THREADS + errno = 0; + n = bufsize - bytes_read; #ifdef MS_WINDOWS - if (n > INT_MAX) - n = INT_MAX; - n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, (int)n); + if (n > INT_MAX) + n = INT_MAX; + n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, (int)n); #else - n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, n); + n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, n); #endif - Py_END_ALLOW_THREADS + 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,7 +707,8 @@ fileio_readall(fileio *self) Py_RETURN_NONE; } Py_DECREF(result); - PyErr_SetFromErrno(PyExc_IOError); + if (!async_err) + PyErr_SetFromErrno(PyExc_IOError); return NULL; } bytes_read += n; @@ -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,14 +755,17 @@ fileio_read(fileio *self, PyObject *args) ptr = PyBytes_AS_STRING(bytes); if (_PyVerify_fd(self->fd)) { - Py_BEGIN_ALLOW_THREADS - errno = 0; + do { + Py_BEGIN_ALLOW_THREADS + errno = 0; #ifdef MS_WINDOWS - n = read(self->fd, ptr, (int)size); + n = read(self->fd, ptr, (int)size); #else - n = read(self->fd, ptr, size); + n = read(self->fd, ptr, size); #endif - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS + } while (n < 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); } else n = -1; @@ -764,7 +775,8 @@ fileio_read(fileio *self, PyObject *args) if (err == EAGAIN) Py_RETURN_NONE; errno = err; - PyErr_SetFromErrno(PyExc_IOError); + 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,24 +806,26 @@ fileio_write(fileio *self, PyObject *args) return NULL; if (_PyVerify_fd(self->fd)) { - Py_BEGIN_ALLOW_THREADS - errno = 0; - len = pbuf.len; + do { + Py_BEGIN_ALLOW_THREADS + errno = 0; + len = pbuf.len; #ifdef MS_WINDOWS - if (len > 32767 && isatty(self->fd)) { - /* Issue #11395: the Windows console returns an error (12: not - enough space error) on writing into stdout if stdout mode is - binary and the length is greater than 66,000 bytes (or less, - depending on heap usage). */ - len = 32767; - } - else if (len > INT_MAX) - len = INT_MAX; - n = write(self->fd, pbuf.buf, (int)len); + if (len > 32767 && isatty(self->fd)) { + /* Issue #11395: the Windows console returns an error (12: not + enough space error) on writing into stdout if stdout mode is + binary and the length is greater than 66,000 bytes (or less, + depending on heap usage). */ + len = 32767; + } else if (len > INT_MAX) + len = INT_MAX; + n = write(self->fd, pbuf.buf, (int)len); #else - n = write(self->fd, pbuf.buf, len); + n = write(self->fd, pbuf.buf, len); #endif - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS + } while (n < 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); } else n = -1; err = errno; @@ -822,7 +836,8 @@ fileio_write(fileio *self, PyObject *args) if (err == EAGAIN) Py_RETURN_NONE; errno = err; - PyErr_SetFromErrno(PyExc_IOError); + if (!async_err) + PyErr_SetFromErrno(PyExc_IOError); return NULL; } diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 94df06c27df..118a380e715 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1361,13 +1361,16 @@ static PyObject * posix_fildes_fd(int fd, int (*func)(int)) { int res; - Py_BEGIN_ALLOW_THREADS - res = (*func)(fd); - Py_END_ALLOW_THREADS - if (res < 0) - return posix_error(); - Py_INCREF(Py_None); - return Py_None; + int async_err = 0; + + do { + Py_BEGIN_ALLOW_THREADS + res = (*func)(fd); + Py_END_ALLOW_THREADS + } while (res != 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (res != 0) + return (!async_err) ? posix_error() : NULL; + Py_RETURN_NONE; } @@ -3479,11 +3482,16 @@ os_fchmod_impl(PyModuleDef *module, int fd, int mode) /*[clinic end generated code: output=3c19fbfd724a8e0f input=8ab11975ca01ee5b]*/ { int res; - Py_BEGIN_ALLOW_THREADS - res = fchmod(fd, mode); - Py_END_ALLOW_THREADS - if (res < 0) - return posix_error(); + int async_err = 0; + + do { + Py_BEGIN_ALLOW_THREADS + res = fchmod(fd, mode); + Py_END_ALLOW_THREADS + } while (res != 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (res != 0) + return (!async_err) ? posix_error() : NULL; + Py_RETURN_NONE; } #endif /* HAVE_FCHMOD */ @@ -4104,11 +4112,16 @@ os_fchown_impl(PyModuleDef *module, int fd, uid_t uid, gid_t gid) /*[clinic end generated code: output=687781cb7d8974d6 input=3af544ba1b13a0d7]*/ { int res; - Py_BEGIN_ALLOW_THREADS - res = fchown(fd, uid, gid); - Py_END_ALLOW_THREADS - if (res < 0) - return posix_error(); + int async_err = 0; + + do { + Py_BEGIN_ALLOW_THREADS + res = fchown(fd, uid, gid); + Py_END_ALLOW_THREADS + } while (res != 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (res != 0) + return (!async_err) ? posix_error() : NULL; + Py_RETURN_NONE; } #endif /* HAVE_FCHOWN */ @@ -9602,12 +9615,17 @@ os_wait3_impl(PyModuleDef *module, int options) { pid_t pid; struct rusage ru; + int async_err = 0; WAIT_TYPE status; WAIT_STATUS_INT(status) = 0; - Py_BEGIN_ALLOW_THREADS - pid = wait3(&status, options, &ru); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + pid = wait3(&status, options, &ru); + Py_END_ALLOW_THREADS + } while (pid < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (pid < 0) + return (!async_err) ? posix_error() : NULL; return wait_helper(pid, WAIT_STATUS_INT(status), &ru); } @@ -9665,15 +9683,21 @@ static PyObject * os_wait4_impl(PyModuleDef *module, pid_t pid, int options) /*[clinic end generated code: output=20dfb05289d37dc6 input=d11deed0750600ba]*/ { + pid_t res; struct rusage ru; + int async_err = 0; WAIT_TYPE status; WAIT_STATUS_INT(status) = 0; - Py_BEGIN_ALLOW_THREADS - pid = wait4(pid, &status, options, &ru); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + res = wait4(pid, &status, options, &ru); + Py_END_ALLOW_THREADS + } while (res < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (res < 0) + return (!async_err) ? posix_error() : NULL; - return wait_helper(pid, WAIT_STATUS_INT(status), &ru); + return wait_helper(res, WAIT_STATUS_INT(status), &ru); } #endif /* HAVE_WAIT4 */ @@ -9744,14 +9768,17 @@ os_waitid_impl(PyModuleDef *module, idtype_t idtype, id_t id, int options) { PyObject *result; int res; + int async_err = 0; siginfo_t si; si.si_pid = 0; - Py_BEGIN_ALLOW_THREADS - res = waitid(idtype, id, &si, options); - Py_END_ALLOW_THREADS - if (res == -1) - return posix_error(); + do { + Py_BEGIN_ALLOW_THREADS + res = waitid(idtype, id, &si, options); + Py_END_ALLOW_THREADS + } while (res < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (res < 0) + return (!async_err) ? posix_error() : NULL; if (si.si_pid == 0) Py_RETURN_NONE; @@ -9828,16 +9855,20 @@ static PyObject * os_waitpid_impl(PyModuleDef *module, pid_t pid, int options) /*[clinic end generated code: output=095a6b00af70b7ac input=0bf1666b8758fda3]*/ { + pid_t res; + int async_err = 0; WAIT_TYPE status; WAIT_STATUS_INT(status) = 0; - Py_BEGIN_ALLOW_THREADS - pid = waitpid(pid, &status, options); - Py_END_ALLOW_THREADS - if (pid == -1) - return posix_error(); + do { + Py_BEGIN_ALLOW_THREADS + res = waitpid(pid, &status, options); + Py_END_ALLOW_THREADS + } while (res < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (res < 0) + return (!async_err) ? posix_error() : NULL; - return Py_BuildValue("Ni", PyLong_FromPid(pid), WAIT_STATUS_INT(status)); + return Py_BuildValue("Ni", PyLong_FromPid(res), WAIT_STATUS_INT(status)); } #elif defined(HAVE_CWAIT) /* MS C has a variant of waitpid() that's usable for most purposes. */ @@ -9894,15 +9925,19 @@ os_waitpid_impl(PyModuleDef *module, Py_intptr_t pid, int options) /*[clinic end generated code: output=c20b95b15ad44a3a input=444c8f51cca5b862]*/ { int status; + Py_intptr_t res; + int async_err = 0; - Py_BEGIN_ALLOW_THREADS - pid = _cwait(&status, pid, options); - Py_END_ALLOW_THREADS - if (pid == -1) - return posix_error(); + do { + Py_BEGIN_ALLOW_THREADS + res = _cwait(&status, pid, options); + Py_END_ALLOW_THREADS + } while (res < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (res != 0) + return (!async_err) ? posix_error() : NULL; /* shift the status left a byte so this is more like the POSIX waitpid */ - return Py_BuildValue(_Py_PARSE_INTPTR "i", pid, status << 8); + return Py_BuildValue(_Py_PARSE_INTPTR "i", res, status << 8); } #endif @@ -9943,14 +9978,17 @@ os_wait_impl(PyModuleDef *module) /*[clinic end generated code: output=2a83a9d164e7e6a8 input=03b0182d4a4700ce]*/ { pid_t pid; + int async_err = 0; WAIT_TYPE status; WAIT_STATUS_INT(status) = 0; - Py_BEGIN_ALLOW_THREADS - pid = wait(&status); - Py_END_ALLOW_THREADS - if (pid == -1) - return posix_error(); + do { + Py_BEGIN_ALLOW_THREADS + pid = wait(&status); + Py_END_ALLOW_THREADS + } while (pid < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (pid < 0) + return (!async_err) ? posix_error() : NULL; return Py_BuildValue("Ni", PyLong_FromPid(pid), WAIT_STATUS_INT(status)); } @@ -10837,6 +10875,7 @@ os_open_impl(PyModuleDef *module, path_t *path, int flags, int mode, int dir_fd) /*[clinic end generated code: output=05b68fc4ed5e29c9 input=ad8623b29acd2934]*/ { int fd; + int async_err = 0; #ifdef O_CLOEXEC int *atomic_flag_works = &_Py_open_cloexec_works; @@ -10850,22 +10889,25 @@ os_open_impl(PyModuleDef *module, path_t *path, int flags, int mode, int dir_fd) flags |= O_CLOEXEC; #endif - Py_BEGIN_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS #ifdef MS_WINDOWS - if (path->wide) - fd = _wopen(path->wide, flags, mode); - else + if (path->wide) + fd = _wopen(path->wide, flags, mode); + else #endif #ifdef HAVE_OPENAT - if (dir_fd != DEFAULT_DIR_FD) - fd = openat(dir_fd, path->narrow, flags, mode); - else + if (dir_fd != DEFAULT_DIR_FD) + fd = openat(dir_fd, path->narrow, flags, mode); + else #endif - fd = open(path->narrow, flags, mode); - Py_END_ALLOW_THREADS + fd = open(path->narrow, flags, mode); + Py_END_ALLOW_THREADS + } while (fd < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (fd == -1) { - PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path->object); + if (!async_err) + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path->object); return -1; } @@ -10924,6 +10966,10 @@ os_close_impl(PyModuleDef *module, int fd) int res; if (!_PyVerify_fd(fd)) return posix_error(); + /* 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. + */ Py_BEGIN_ALLOW_THREADS res = close(fd); Py_END_ALLOW_THREADS @@ -11089,6 +11135,10 @@ os_dup2_impl(PyModuleDef *module, int fd, int fd2, int inheritable) if (!_PyVerify_fd_dup2(fd, fd2)) return posix_error(); + /* dup2() can fail with EINTR if the target FD is already open, because it + * then has to be closed. See os_close_impl() for why we don't handle EINTR + * upon close(), and therefore below. + */ #ifdef MS_WINDOWS Py_BEGIN_ALLOW_THREADS res = dup2(fd, fd2); @@ -11355,6 +11405,7 @@ os_read_impl(PyModuleDef *module, int fd, Py_ssize_t length) /*[clinic end generated code: output=1f3bc27260a24968 input=1df2eaa27c0bf1d3]*/ { Py_ssize_t n; + int async_err = 0; PyObject *buffer; if (length < 0) { @@ -11375,13 +11426,16 @@ os_read_impl(PyModuleDef *module, int fd, Py_ssize_t length) buffer = PyBytes_FromStringAndSize((char *)NULL, length); if (buffer == NULL) return NULL; - Py_BEGIN_ALLOW_THREADS - n = read(fd, PyBytes_AS_STRING(buffer), READ_CAST length); - Py_END_ALLOW_THREADS + + do { + Py_BEGIN_ALLOW_THREADS + n = read(fd, PyBytes_AS_STRING(buffer), READ_CAST length); + Py_END_ALLOW_THREADS + } while (n < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (n < 0) { Py_DECREF(buffer); - return posix_error(); + return (!async_err) ? posix_error() : NULL; } if (n != length) @@ -11515,6 +11569,7 @@ os_readv_impl(PyModuleDef *module, int fd, PyObject *buffers) { int cnt; Py_ssize_t n; + int async_err = 0; struct iovec *iov; Py_buffer *buf; @@ -11529,13 +11584,16 @@ os_readv_impl(PyModuleDef *module, int fd, PyObject *buffers) if (iov_setup(&iov, &buf, buffers, cnt, PyBUF_WRITABLE) < 0) return -1; - Py_BEGIN_ALLOW_THREADS - n = readv(fd, iov, cnt); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + n = readv(fd, iov, cnt); + Py_END_ALLOW_THREADS + } while (n < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); iov_cleanup(iov, buf, cnt); if (n < 0) { - posix_error(); + if (!async_err) + posix_error(); return -1; } @@ -11598,6 +11656,7 @@ os_pread_impl(PyModuleDef *module, int fd, int length, Py_off_t offset) /*[clinic end generated code: output=7b62bf6c06e20ae8 input=084948dcbaa35d4c]*/ { Py_ssize_t n; + int async_err = 0; PyObject *buffer; if (length < 0) { @@ -11611,12 +11670,16 @@ os_pread_impl(PyModuleDef *module, int fd, int length, Py_off_t offset) Py_DECREF(buffer); return posix_error(); } - Py_BEGIN_ALLOW_THREADS - n = pread(fd, PyBytes_AS_STRING(buffer), length, offset); - Py_END_ALLOW_THREADS + + do { + Py_BEGIN_ALLOW_THREADS + n = pread(fd, PyBytes_AS_STRING(buffer), length, offset); + Py_END_ALLOW_THREADS + } while (n < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (n < 0) { Py_DECREF(buffer); - return posix_error(); + return (!async_err) ? posix_error() : NULL; } if (n != length) _PyBytes_Resize(&buffer, n); @@ -11677,6 +11740,7 @@ os_write_impl(PyModuleDef *module, int fd, Py_buffer *data) /*[clinic end generated code: output=aeb96acfdd4d5112 input=3207e28963234f3c]*/ { Py_ssize_t size; + int async_err = 0; Py_ssize_t len = data->len; if (!_PyVerify_fd(fd)) { @@ -11684,17 +11748,21 @@ os_write_impl(PyModuleDef *module, int fd, Py_buffer *data) return -1; } - Py_BEGIN_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS #ifdef MS_WINDOWS - if (len > INT_MAX) - len = INT_MAX; - size = write(fd, data->buf, (int)len); + if (len > INT_MAX) + len = INT_MAX; + size = write(fd, data->buf, (int)len); #else - size = write(fd, data->buf, len); + size = write(fd, data->buf, len); #endif - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS + } while (size < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + if (size < 0) { - posix_error(); + if (!async_err) + posix_error(); return -1; } return size; @@ -11713,6 +11781,7 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) { int in, out; Py_ssize_t ret; + int async_err = 0; off_t offset; #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) @@ -11775,13 +11844,15 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) } } - Py_BEGIN_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS #ifdef __APPLE__ - ret = sendfile(in, out, offset, &sbytes, &sf, flags); + ret = sendfile(in, out, offset, &sbytes, &sf, flags); #else - ret = sendfile(in, out, offset, len, &sf, &sbytes, flags); + ret = sendfile(in, out, offset, len, &sf, &sbytes, flags); #endif - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS + } while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (sf.headers != NULL) iov_cleanup(sf.headers, hbuf, sf.hdr_cnt); @@ -11800,7 +11871,7 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict) return posix_error(); } } - return posix_error(); + return (!async_err) ? posix_error() : NULL; } goto done; @@ -11821,21 +11892,26 @@ done: return NULL; #ifdef linux if (offobj == Py_None) { - Py_BEGIN_ALLOW_THREADS - ret = sendfile(out, in, NULL, count); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + ret = sendfile(out, in, NULL, count); + Py_END_ALLOW_THREADS + } while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (ret < 0) - return posix_error(); + return (!async_err) ? posix_error() : NULL; return Py_BuildValue("n", ret); } #endif if (!Py_off_t_converter(offobj, &offset)) return NULL; - Py_BEGIN_ALLOW_THREADS - ret = sendfile(out, in, &offset, count); - Py_END_ALLOW_THREADS + + do { + Py_BEGIN_ALLOW_THREADS + ret = sendfile(out, in, &offset, count); + Py_END_ALLOW_THREADS + } while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (ret < 0) - return posix_error(); + return (!async_err) ? posix_error() : NULL; return Py_BuildValue("n", ret); #endif } @@ -11891,15 +11967,18 @@ os_fstat_impl(PyModuleDef *module, int fd) { STRUCT_STAT st; int res; + int async_err = 0; - Py_BEGIN_ALLOW_THREADS - res = FSTAT(fd, &st); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + res = FSTAT(fd, &st); + Py_END_ALLOW_THREADS + } while (res != 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (res != 0) { #ifdef MS_WINDOWS return PyErr_SetFromWindowsErr(0); #else - return posix_error(); + return (!async_err) ? posix_error() : NULL; #endif } @@ -12185,6 +12264,7 @@ os_writev_impl(PyModuleDef *module, int fd, PyObject *buffers) { int cnt; Py_ssize_t result; + int async_err = 0; struct iovec *iov; Py_buffer *buf; @@ -12199,12 +12279,14 @@ os_writev_impl(PyModuleDef *module, int fd, PyObject *buffers) return -1; } - Py_BEGIN_ALLOW_THREADS - result = writev(fd, iov, cnt); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + result = writev(fd, iov, cnt); + Py_END_ALLOW_THREADS + } while (result < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); iov_cleanup(iov, buf, cnt); - if (result < 0) + if (result < 0 && !async_err) posix_error(); return result; @@ -12275,17 +12357,20 @@ os_pwrite_impl(PyModuleDef *module, int fd, Py_buffer *buffer, Py_off_t offset) /*[clinic end generated code: output=ec9cc5b2238e96a7 input=19903f1b3dd26377]*/ { Py_ssize_t size; + int async_err = 0; if (!_PyVerify_fd(fd)) { posix_error(); return -1; } - Py_BEGIN_ALLOW_THREADS - size = pwrite(fd, buffer->buf, (size_t)buffer->len, offset); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + size = pwrite(fd, buffer->buf, (size_t)buffer->len, offset); + Py_END_ALLOW_THREADS + } while (size < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); - if (size < 0) + if (size < 0 && !async_err) posix_error(); return size; } @@ -12353,18 +12438,21 @@ os_mkfifo_impl(PyModuleDef *module, path_t *path, int mode, int dir_fd) /*[clinic end generated code: output=b3321927546893d0 input=73032e98a36e0e19]*/ { int result; + int async_err = 0; - Py_BEGIN_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS #ifdef HAVE_MKFIFOAT - if (dir_fd != DEFAULT_DIR_FD) - result = mkfifoat(dir_fd, path->narrow, mode); - else + if (dir_fd != DEFAULT_DIR_FD) + result = mkfifoat(dir_fd, path->narrow, mode); + else #endif - result = mkfifo(path->narrow, mode); - Py_END_ALLOW_THREADS - - if (result < 0) - return posix_error(); + result = mkfifo(path->narrow, mode); + Py_END_ALLOW_THREADS + } while (result != 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); + if (result != 0) + return (!async_err) ? posix_error() : NULL; Py_RETURN_NONE; } @@ -12448,18 +12536,21 @@ os_mknod_impl(PyModuleDef *module, path_t *path, int mode, dev_t device, int dir /*[clinic end generated code: output=f71d54eaf9bb6f1a input=ee44531551a4d83b]*/ { int result; + int async_err = 0; - Py_BEGIN_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS #ifdef HAVE_MKNODAT - if (dir_fd != DEFAULT_DIR_FD) - result = mknodat(dir_fd, path->narrow, mode, device); - else + if (dir_fd != DEFAULT_DIR_FD) + result = mknodat(dir_fd, path->narrow, mode, device); + else #endif - result = mknod(path->narrow, mode, device); - Py_END_ALLOW_THREADS - - if (result < 0) - return posix_error(); + result = mknod(path->narrow, mode, device); + Py_END_ALLOW_THREADS + } while (result != 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); + if (result != 0) + return (!async_err) ? posix_error() : NULL; Py_RETURN_NONE; } @@ -12662,12 +12753,16 @@ os_ftruncate_impl(PyModuleDef *module, int fd, Py_off_t length) /*[clinic end generated code: output=62326766cb9b76bf input=63b43641e52818f2]*/ { int result; + int async_err = 0; - Py_BEGIN_ALLOW_THREADS - result = ftruncate(fd, length); - Py_END_ALLOW_THREADS - if (result < 0) - return posix_error(); + do { + Py_BEGIN_ALLOW_THREADS + result = ftruncate(fd, length); + Py_END_ALLOW_THREADS + } while (result != 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); + if (result != 0) + return (!async_err) ? posix_error() : NULL; Py_RETURN_NONE; } #endif /* HAVE_FTRUNCATE */ @@ -12805,14 +12900,16 @@ os_posix_fallocate_impl(PyModuleDef *module, int fd, Py_off_t offset, Py_off_t l /*[clinic end generated code: output=0cd702d2065c79db input=d7a2ef0ab2ca52fb]*/ { int result; + int async_err = 0; - Py_BEGIN_ALLOW_THREADS - result = posix_fallocate(fd, offset, length); - Py_END_ALLOW_THREADS - if (result != 0) { - errno = result; - return posix_error(); - } + do { + Py_BEGIN_ALLOW_THREADS + result = posix_fallocate(fd, offset, length); + Py_END_ALLOW_THREADS + } while (result != 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); + if (result != 0) + return (!async_err) ? posix_error() : NULL; Py_RETURN_NONE; } #endif /* HAVE_POSIX_FALLOCATE) && !POSIX_FADVISE_AIX_BUG */ @@ -12883,14 +12980,16 @@ os_posix_fadvise_impl(PyModuleDef *module, int fd, Py_off_t offset, Py_off_t len /*[clinic end generated code: output=dad93f32c04dd4f7 input=0fbe554edc2f04b5]*/ { int result; + int async_err = 0; - Py_BEGIN_ALLOW_THREADS - result = posix_fadvise(fd, offset, length, advice); - Py_END_ALLOW_THREADS - if (result != 0) { - errno = result; - return posix_error(); - } + do { + Py_BEGIN_ALLOW_THREADS + result = posix_fadvise(fd, offset, length, advice); + Py_END_ALLOW_THREADS + } while (result != 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); + if (result != 0) + return (!async_err) ? posix_error() : NULL; Py_RETURN_NONE; } #endif /* HAVE_POSIX_FADVISE && !POSIX_FADVISE_AIX_BUG */ @@ -13745,13 +13844,17 @@ os_fstatvfs_impl(PyModuleDef *module, int fd) /*[clinic end generated code: output=0e32bf07f946ec0d input=d8122243ac50975e]*/ { int result; + int async_err = 0; struct statvfs st; - Py_BEGIN_ALLOW_THREADS - result = fstatvfs(fd, &st); - Py_END_ALLOW_THREADS + do { + Py_BEGIN_ALLOW_THREADS + result = fstatvfs(fd, &st); + Py_END_ALLOW_THREADS + } while (result != 0 && errno == EINTR && + !(async_err = PyErr_CheckSignals())); if (result != 0) - return posix_error(); + return (!async_err) ? posix_error() : NULL; return _pystatvfs_fromstructstatvfs(st); } diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 94cf7738e48..5913e65a8eb 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -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,27 +2051,27 @@ sock_accept(PySocketSockObject *s) return select_error(); BEGIN_SELECT_LOOP(s) - - Py_BEGIN_ALLOW_THREADS - timeout = internal_select_ex(s, 0, interval); - if (!timeout) { + do { + Py_BEGIN_ALLOW_THREADS + timeout = internal_select_ex(s, 0, interval); + if (!timeout) { #if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) - if (accept4_works != 0) { - newfd = accept4(s->sock_fd, SAS2SA(&addrbuf), &addrlen, - SOCK_CLOEXEC); - if (newfd == INVALID_SOCKET && accept4_works == -1) { - /* On Linux older than 2.6.28, accept4() fails with ENOSYS */ - accept4_works = (errno != ENOSYS); + if (accept4_works != 0) { + newfd = accept4(s->sock_fd, SAS2SA(&addrbuf), &addrlen, + SOCK_CLOEXEC); + if (newfd == INVALID_SOCKET && accept4_works == -1) { + /* On Linux older than 2.6.28, accept4() fails with ENOSYS */ + accept4_works = (errno != ENOSYS); + } } - } - if (accept4_works == 0) - newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen); + if (accept4_works == 0) + newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen); #else - newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen); + newfd = accept(s->sock_fd, SAS2SA(&addrbuf), &addrlen); #endif - } - Py_END_ALLOW_THREADS - + } + 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,18 +2665,20 @@ sock_recv_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags) } BEGIN_SELECT_LOOP(s) - Py_BEGIN_ALLOW_THREADS - timeout = internal_select_ex(s, 0, interval); - if (!timeout) { + do { + Py_BEGIN_ALLOW_THREADS + timeout = internal_select_ex(s, 0, interval); + if (!timeout) { #ifdef MS_WINDOWS - if (len > INT_MAX) - len = INT_MAX; - outlen = recv(s->sock_fd, cbuf, (int)len, flags); + if (len > INT_MAX) + len = INT_MAX; + outlen = recv(s->sock_fd, cbuf, (int)len, flags); #else - outlen = recv(s->sock_fd, cbuf, len, flags); + outlen = recv(s->sock_fd, cbuf, len, flags); #endif - } - Py_END_ALLOW_THREADS + } + Py_END_ALLOW_THREADS + } while (outlen < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (timeout == 1) { PyErr_SetString(socket_timeout, "timed out"); @@ -2682,7 +2688,8 @@ 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 */ - s->errorhandler(); + if (!async_err) + s->errorhandler(); return -1; } return outlen; @@ -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,21 +2839,23 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags, } BEGIN_SELECT_LOOP(s) - Py_BEGIN_ALLOW_THREADS - memset(&addrbuf, 0, addrlen); - timeout = internal_select_ex(s, 0, interval); - if (!timeout) { + do { + Py_BEGIN_ALLOW_THREADS + memset(&addrbuf, 0, addrlen); + timeout = internal_select_ex(s, 0, interval); + if (!timeout) { #ifdef MS_WINDOWS - if (len > INT_MAX) - len = INT_MAX; - n = recvfrom(s->sock_fd, cbuf, (int)len, flags, - (void *) &addrbuf, &addrlen); + if (len > INT_MAX) + len = INT_MAX; + n = recvfrom(s->sock_fd, cbuf, (int)len, flags, + (void *) &addrbuf, &addrlen); #else - n = recvfrom(s->sock_fd, cbuf, len, flags, - SAS2SA(&addrbuf), &addrlen); + n = recvfrom(s->sock_fd, cbuf, len, flags, + SAS2SA(&addrbuf), &addrlen); #endif - } - Py_END_ALLOW_THREADS + } + Py_END_ALLOW_THREADS + } while (n < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); if (timeout == 1) { PyErr_SetString(socket_timeout, "timed out"); @@ -2853,7 +2863,8 @@ sock_recvfrom_guts(PySocketSockObject *s, char* cbuf, Py_ssize_t len, int flags, } END_SELECT_LOOP(s) if (n < 0) { - s->errorhandler(); + 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,25 +3040,29 @@ sock_recvmsg_guts(PySocketSockObject *s, struct iovec *iov, int iovlen, } BEGIN_SELECT_LOOP(s) - Py_BEGIN_ALLOW_THREADS; - msg.msg_name = SAS2SA(&addrbuf); - msg.msg_namelen = addrbuflen; - msg.msg_iov = iov; - msg.msg_iovlen = iovlen; - msg.msg_control = controlbuf; - msg.msg_controllen = controllen; - timeout = internal_select_ex(s, 0, interval); - if (!timeout) - bytes_received = recvmsg(s->sock_fd, &msg, flags); - Py_END_ALLOW_THREADS; - if (timeout == 1) { - PyErr_SetString(socket_timeout, "timed out"); - goto finally; - } + do { + Py_BEGIN_ALLOW_THREADS; + msg.msg_name = SAS2SA(&addrbuf); + msg.msg_namelen = addrbuflen; + msg.msg_iov = iov; + msg.msg_iovlen = iovlen; + msg.msg_control = controlbuf; + msg.msg_controllen = controllen; + timeout = internal_select_ex(s, 0, interval); + if (!timeout) + bytes_received = recvmsg(s->sock_fd, &msg, flags); + Py_END_ALLOW_THREADS; + if (timeout == 1) { + 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) { - s->errorhandler(); + 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,18 +3336,20 @@ sock_send(PySocketSockObject *s, PyObject *args) len = pbuf.len; BEGIN_SELECT_LOOP(s) - Py_BEGIN_ALLOW_THREADS - timeout = internal_select_ex(s, 1, interval); - if (!timeout) { + do { + Py_BEGIN_ALLOW_THREADS + timeout = internal_select_ex(s, 1, interval); + if (!timeout) { #ifdef MS_WINDOWS - if (len > INT_MAX) - len = INT_MAX; - n = send(s->sock_fd, buf, (int)len, flags); + if (len > INT_MAX) + len = INT_MAX; + n = send(s->sock_fd, buf, (int)len, flags); #else - n = send(s->sock_fd, buf, len, flags); + n = send(s->sock_fd, buf, len, flags); #endif - } - Py_END_ALLOW_THREADS + } + 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) { + buf += n; + len -= n; } - if (n < 0) { - /* If interrupted, try again */ - if (saved_errno == EINTR) - continue; - else - break; - } - 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,20 +3481,22 @@ sock_sendto(PySocketSockObject *s, PyObject *args) } BEGIN_SELECT_LOOP(s) - Py_BEGIN_ALLOW_THREADS - timeout = internal_select_ex(s, 1, interval); - if (!timeout) { + do { + Py_BEGIN_ALLOW_THREADS + timeout = internal_select_ex(s, 1, interval); + if (!timeout) { #ifdef MS_WINDOWS - if (len > INT_MAX) - len = INT_MAX; - n = sendto(s->sock_fd, buf, (int)len, flags, - SAS2SA(&addrbuf), addrlen); + if (len > INT_MAX) + len = INT_MAX; + n = sendto(s->sock_fd, buf, (int)len, flags, + SAS2SA(&addrbuf), addrlen); #else - n = sendto(s->sock_fd, buf, len, flags, - SAS2SA(&addrbuf), addrlen); + n = sendto(s->sock_fd, buf, len, flags, + SAS2SA(&addrbuf), addrlen); #endif - } - Py_END_ALLOW_THREADS + } + 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,19 +3696,23 @@ sock_sendmsg(PySocketSockObject *s, PyObject *args) } BEGIN_SELECT_LOOP(s) - Py_BEGIN_ALLOW_THREADS; - timeout = internal_select_ex(s, 1, interval); - if (!timeout) - bytes_sent = sendmsg(s->sock_fd, &msg, flags); - Py_END_ALLOW_THREADS; - if (timeout == 1) { - PyErr_SetString(socket_timeout, "timed out"); - goto finally; - } + do { + Py_BEGIN_ALLOW_THREADS; + timeout = internal_select_ex(s, 1, interval); + if (!timeout) + bytes_sent = sendmsg(s->sock_fd, &msg, flags); + Py_END_ALLOW_THREADS; + if (timeout == 1) { + 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) { - s->errorhandler(); + if (!async_err) + s->errorhandler(); goto finally; } retval = PyLong_FromSsize_t(bytes_sent);