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. ...@@ -111,6 +111,16 @@ Please read on for a comprehensive list of user-facing changes.
PEP written by Carl Meyer 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 Other Language Changes
......
...@@ -1012,10 +1012,7 @@ class BufferedReader(_BufferedIOMixin): ...@@ -1012,10 +1012,7 @@ class BufferedReader(_BufferedIOMixin):
current_size = 0 current_size = 0
while True: while True:
# Read until EOF or until read() would block. # Read until EOF or until read() would block.
try: chunk = self.raw.read()
chunk = self.raw.read()
except InterruptedError:
continue
if chunk in empty_values: if chunk in empty_values:
nodata_val = chunk nodata_val = chunk
break break
...@@ -1034,10 +1031,7 @@ class BufferedReader(_BufferedIOMixin): ...@@ -1034,10 +1031,7 @@ class BufferedReader(_BufferedIOMixin):
chunks = [buf[pos:]] chunks = [buf[pos:]]
wanted = max(self.buffer_size, n) wanted = max(self.buffer_size, n)
while avail < n: while avail < n:
try: chunk = self.raw.read(wanted)
chunk = self.raw.read(wanted)
except InterruptedError:
continue
if chunk in empty_values: if chunk in empty_values:
nodata_val = chunk nodata_val = chunk
break break
...@@ -1066,12 +1060,7 @@ class BufferedReader(_BufferedIOMixin): ...@@ -1066,12 +1060,7 @@ class BufferedReader(_BufferedIOMixin):
have = len(self._read_buf) - self._read_pos have = len(self._read_buf) - self._read_pos
if have < want or have <= 0: if have < want or have <= 0:
to_read = self.buffer_size - have to_read = self.buffer_size - have
while True: current = self.raw.read(to_read)
try:
current = self.raw.read(to_read)
except InterruptedError:
continue
break
if current: if current:
self._read_buf = self._read_buf[self._read_pos:] + current self._read_buf = self._read_buf[self._read_pos:] + current
self._read_pos = 0 self._read_pos = 0
...@@ -1220,8 +1209,6 @@ class BufferedWriter(_BufferedIOMixin): ...@@ -1220,8 +1209,6 @@ class BufferedWriter(_BufferedIOMixin):
while self._write_buf: while self._write_buf:
try: try:
n = self.raw.write(self._write_buf) n = self.raw.write(self._write_buf)
except InterruptedError:
continue
except BlockingIOError: except BlockingIOError:
raise RuntimeError("self.raw should implement RawIOBase: it " raise RuntimeError("self.raw should implement RawIOBase: it "
"should not raise BlockingIOError") "should not raise BlockingIOError")
......
...@@ -137,9 +137,6 @@ def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0): ...@@ -137,9 +137,6 @@ def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0):
try: try:
pid, status = os.waitpid(pid, 0) pid, status = os.waitpid(pid, 0)
except OSError as exc: except OSError as exc:
import errno
if exc.errno == errno.EINTR:
continue
if not DEBUG: if not DEBUG:
cmd = executable cmd = executable
raise DistutilsExecError( raise DistutilsExecError(
......
...@@ -365,10 +365,7 @@ class Connection(_ConnectionBase): ...@@ -365,10 +365,7 @@ class Connection(_ConnectionBase):
def _send(self, buf, write=_write): def _send(self, buf, write=_write):
remaining = len(buf) remaining = len(buf)
while True: while True:
try: n = write(self._handle, buf)
n = write(self._handle, buf)
except InterruptedError:
continue
remaining -= n remaining -= n
if remaining == 0: if remaining == 0:
break break
...@@ -379,10 +376,7 @@ class Connection(_ConnectionBase): ...@@ -379,10 +376,7 @@ class Connection(_ConnectionBase):
handle = self._handle handle = self._handle
remaining = size remaining = size
while remaining > 0: while remaining > 0:
try: chunk = read(handle, remaining)
chunk = read(handle, remaining)
except InterruptedError:
continue
n = len(chunk) n = len(chunk)
if n == 0: if n == 0:
if remaining == size: if remaining == size:
...@@ -595,13 +589,7 @@ class SocketListener(object): ...@@ -595,13 +589,7 @@ class SocketListener(object):
self._unlink = None self._unlink = None
def accept(self): def accept(self):
while True: s, self._last_accepted = self._socket.accept()
try:
s, self._last_accepted = self._socket.accept()
except InterruptedError:
pass
else:
break
s.setblocking(True) s.setblocking(True)
return Connection(s.detach()) return Connection(s.detach())
......
...@@ -188,8 +188,6 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None): ...@@ -188,8 +188,6 @@ def main(listener_fd, alive_r, preload, main_path=None, sys_path=None):
finally: finally:
os._exit(code) os._exit(code)
except InterruptedError:
pass
except OSError as e: except OSError as e:
if e.errno != errno.ECONNABORTED: if e.errno != errno.ECONNABORTED:
raise raise
...@@ -230,13 +228,7 @@ def read_unsigned(fd): ...@@ -230,13 +228,7 @@ def read_unsigned(fd):
data = b'' data = b''
length = UNSIGNED_STRUCT.size length = UNSIGNED_STRUCT.size
while len(data) < length: while len(data) < length:
while True: s = os.read(fd, length - len(data))
try:
s = os.read(fd, length - len(data))
except InterruptedError:
pass
else:
break
if not s: if not s:
raise EOFError('unexpected EOF') raise EOFError('unexpected EOF')
data += s data += s
...@@ -245,13 +237,7 @@ def read_unsigned(fd): ...@@ -245,13 +237,7 @@ def read_unsigned(fd):
def write_unsigned(fd, n): def write_unsigned(fd, n):
msg = UNSIGNED_STRUCT.pack(n) msg = UNSIGNED_STRUCT.pack(n)
while msg: while msg:
while True: nbytes = os.write(fd, msg)
try:
nbytes = os.write(fd, msg)
except InterruptedError:
pass
else:
break
if nbytes == 0: if nbytes == 0:
raise RuntimeError('should not get here') raise RuntimeError('should not get here')
msg = msg[nbytes:] msg = msg[nbytes:]
......
import os import os
import sys import sys
import signal import signal
import errno
from . import util from . import util
...@@ -29,8 +28,6 @@ class Popen(object): ...@@ -29,8 +28,6 @@ class Popen(object):
try: try:
pid, sts = os.waitpid(self.pid, flag) pid, sts = os.waitpid(self.pid, flag)
except OSError as e: except OSError as e:
if e.errno == errno.EINTR:
continue
# Child process not yet created. See #1731717 # Child process not yet created. See #1731717
# e.errno == errno.ECHILD == 10 # e.errno == errno.ECHILD == 10
return None return None
......
...@@ -572,8 +572,6 @@ class SocketIO(io.RawIOBase): ...@@ -572,8 +572,6 @@ class SocketIO(io.RawIOBase):
except timeout: except timeout:
self._timeout_occurred = True self._timeout_occurred = True
raise raise
except InterruptedError:
continue
except error as e: except error as e:
if e.args[0] in _blocking_errnos: if e.args[0] in _blocking_errnos:
return None return None
......
...@@ -553,8 +553,6 @@ class ForkingMixIn: ...@@ -553,8 +553,6 @@ class ForkingMixIn:
try: try:
pid, _ = os.waitpid(-1, 0) pid, _ = os.waitpid(-1, 0)
self.active_children.discard(pid) self.active_children.discard(pid)
except InterruptedError:
pass
except ChildProcessError: except ChildProcessError:
# we don't have any children, we're done # we don't have any children, we're done
self.active_children.clear() self.active_children.clear()
......
...@@ -489,14 +489,6 @@ STDOUT = -2 ...@@ -489,14 +489,6 @@ STDOUT = -2
DEVNULL = -3 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, # 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 # but it's here so that it can be imported when Python is compiled without
# threads. # threads.
...@@ -963,10 +955,10 @@ class Popen(object): ...@@ -963,10 +955,10 @@ class Popen(object):
if self.stdin: if self.stdin:
self._stdin_write(input) self._stdin_write(input)
elif self.stdout: elif self.stdout:
stdout = _eintr_retry_call(self.stdout.read) stdout = self.stdout.read()
self.stdout.close() self.stdout.close()
elif self.stderr: elif self.stderr:
stderr = _eintr_retry_call(self.stderr.read) stderr = self.stderr.read()
self.stderr.close() self.stderr.close()
self.wait() self.wait()
else: else:
...@@ -1410,7 +1402,7 @@ class Popen(object): ...@@ -1410,7 +1402,7 @@ class Popen(object):
# exception (limited in size) # exception (limited in size)
errpipe_data = bytearray() errpipe_data = bytearray()
while True: while True:
part = _eintr_retry_call(os.read, errpipe_read, 50000) part = os.read(errpipe_read, 50000)
errpipe_data += part errpipe_data += part
if not part or len(errpipe_data) > 50000: if not part or len(errpipe_data) > 50000:
break break
...@@ -1420,7 +1412,7 @@ class Popen(object): ...@@ -1420,7 +1412,7 @@ class Popen(object):
if errpipe_data: if errpipe_data:
try: try:
_eintr_retry_call(os.waitpid, self.pid, 0) os.waitpid(self.pid, 0)
except ChildProcessError: except ChildProcessError:
pass pass
try: try:
...@@ -1505,7 +1497,7 @@ class Popen(object): ...@@ -1505,7 +1497,7 @@ class Popen(object):
def _try_wait(self, wait_flags): def _try_wait(self, wait_flags):
"""All callers to this function MUST hold self._waitpid_lock.""" """All callers to this function MUST hold self._waitpid_lock."""
try: try:
(pid, sts) = _eintr_retry_call(os.waitpid, self.pid, wait_flags) (pid, sts) = os.waitpid(self.pid, wait_flags)
except ChildProcessError: except ChildProcessError:
# This happens if SIGCLD is set to be ignored or waiting # This happens if SIGCLD is set to be ignored or waiting
# for child processes has otherwise been disabled for our # 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): ...@@ -587,7 +587,7 @@ class SiginterruptTest(unittest.TestCase):
r, w = os.pipe() r, w = os.pipe()
def handler(signum, frame): def handler(signum, frame):
pass 1 / 0
signal.signal(signal.SIGALRM, handler) signal.signal(signal.SIGALRM, handler)
if interrupt is not None: if interrupt is not None:
...@@ -604,9 +604,8 @@ class SiginterruptTest(unittest.TestCase): ...@@ -604,9 +604,8 @@ class SiginterruptTest(unittest.TestCase):
try: try:
# blocking call: read from a pipe without data # blocking call: read from a pipe without data
os.read(r, 1) os.read(r, 1)
except OSError as err: except ZeroDivisionError:
if err.errno != errno.EINTR: pass
raise
else: else:
sys.exit(2) sys.exit(2)
sys.exit(3) sys.exit(3)
......
...@@ -3590,7 +3590,7 @@ class InterruptedTimeoutBase(unittest.TestCase): ...@@ -3590,7 +3590,7 @@ class InterruptedTimeoutBase(unittest.TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
orig_alrm_handler = signal.signal(signal.SIGALRM, 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(signal.signal, signal.SIGALRM, orig_alrm_handler)
self.addCleanup(self.setAlarm, 0) self.addCleanup(self.setAlarm, 0)
...@@ -3627,13 +3627,11 @@ class InterruptedRecvTimeoutTest(InterruptedTimeoutBase, UDPTestBase): ...@@ -3627,13 +3627,11 @@ class InterruptedRecvTimeoutTest(InterruptedTimeoutBase, UDPTestBase):
self.serv.settimeout(self.timeout) self.serv.settimeout(self.timeout)
def checkInterruptedRecv(self, func, *args, **kwargs): 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. # errno of EINTR when interrupted by a signal.
self.setAlarm(self.alarm_time) self.setAlarm(self.alarm_time)
with self.assertRaises(OSError) as cm: with self.assertRaises(ZeroDivisionError) as cm:
func(*args, **kwargs) func(*args, **kwargs)
self.assertNotIsInstance(cm.exception, socket.timeout)
self.assertEqual(cm.exception.errno, errno.EINTR)
def testInterruptedRecvTimeout(self): def testInterruptedRecvTimeout(self):
self.checkInterruptedRecv(self.serv.recv, 1024) self.checkInterruptedRecv(self.serv.recv, 1024)
...@@ -3689,12 +3687,10 @@ class InterruptedSendTimeoutTest(InterruptedTimeoutBase, ...@@ -3689,12 +3687,10 @@ class InterruptedSendTimeoutTest(InterruptedTimeoutBase,
# Check that func(*args, **kwargs), run in a loop, raises # Check that func(*args, **kwargs), run in a loop, raises
# OSError with an errno of EINTR when interrupted by a # OSError with an errno of EINTR when interrupted by a
# signal. # signal.
with self.assertRaises(OSError) as cm: with self.assertRaises(ZeroDivisionError) as cm:
while True: while True:
self.setAlarm(self.alarm_time) self.setAlarm(self.alarm_time)
func(*args, **kwargs) 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 # Issue #12958: The following tests have problems on OS X prior to 10.7
@support.requires_mac_ver(10, 7) @support.requires_mac_ver(10, 7)
...@@ -4062,117 +4058,6 @@ class FileObjectClassTestCase(SocketConnectedTest): ...@@ -4062,117 +4058,6 @@ class FileObjectClassTestCase(SocketConnectedTest):
pass 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): class UnbufferedFileObjectClassTestCase(FileObjectClassTestCase):
"""Repeat the tests from FileObjectClassTestCase with bufsize==0. """Repeat the tests from FileObjectClassTestCase with bufsize==0.
...@@ -5388,7 +5273,6 @@ def test_main(): ...@@ -5388,7 +5273,6 @@ def test_main():
tests.extend([ tests.extend([
NonBlockingTCPTests, NonBlockingTCPTests,
FileObjectClassTestCase, FileObjectClassTestCase,
FileObjectInterruptedTestCase,
UnbufferedFileObjectClassTestCase, UnbufferedFileObjectClassTestCase,
LineBufferedFileObjectClassTestCase, LineBufferedFileObjectClassTestCase,
SmallBufferedFileObjectClassTestCase, SmallBufferedFileObjectClassTestCase,
......
...@@ -2421,25 +2421,6 @@ class ProcessTestCaseNoPoll(ProcessTestCase): ...@@ -2421,25 +2421,6 @@ class ProcessTestCaseNoPoll(ProcessTestCase):
ProcessTestCase.tearDown(self) 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") @unittest.skipUnless(mswindows, "Windows-specific tests")
class CommandsWithSpaces (BaseTestCase): class CommandsWithSpaces (BaseTestCase):
...@@ -2528,7 +2509,6 @@ def test_main(): ...@@ -2528,7 +2509,6 @@ def test_main():
Win32ProcessTestCase, Win32ProcessTestCase,
CommandTests, CommandTests,
ProcessTestCaseNoPoll, ProcessTestCaseNoPoll,
HelperFunctionTests,
CommandsWithSpaces, CommandsWithSpaces,
ContextManagerTests, ContextManagerTests,
) )
......
...@@ -10,6 +10,8 @@ Release date: TBA ...@@ -10,6 +10,8 @@ Release date: TBA
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #23285: PEP 475 - EINTR handling.
- Issue #22735: Fix many edge cases (including crashes) involving custom mro() - Issue #22735: Fix many edge cases (including crashes) involving custom mro()
implementations. implementations.
......
...@@ -218,6 +218,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) ...@@ -218,6 +218,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
#ifdef HAVE_FSTAT #ifdef HAVE_FSTAT
struct stat fdfstat; struct stat fdfstat;
#endif #endif
int async_err = 0;
assert(PyFileIO_Check(oself)); assert(PyFileIO_Check(oself));
if (self->fd >= 0) { if (self->fd >= 0) {
...@@ -360,15 +361,18 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) ...@@ -360,15 +361,18 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
errno = 0; errno = 0;
if (opener == Py_None) { if (opener == Py_None) {
Py_BEGIN_ALLOW_THREADS do {
Py_BEGIN_ALLOW_THREADS
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (widename != NULL) if (widename != NULL)
self->fd = _wopen(widename, flags, 0666); self->fd = _wopen(widename, flags, 0666);
else else
#endif #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 { else {
PyObject *fdobj; PyObject *fdobj;
...@@ -397,7 +401,8 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) ...@@ -397,7 +401,8 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
fd_is_own = 1; fd_is_own = 1;
if (self->fd < 0) { if (self->fd < 0) {
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj); if (!async_err)
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj);
goto error; goto error;
} }
...@@ -550,7 +555,7 @@ fileio_readinto(fileio *self, PyObject *args) ...@@ -550,7 +555,7 @@ fileio_readinto(fileio *self, PyObject *args)
{ {
Py_buffer pbuf; Py_buffer pbuf;
Py_ssize_t n, len; Py_ssize_t n, len;
int err; int err, async_err = 0;
if (self->fd < 0) if (self->fd < 0)
return err_closed(); return err_closed();
...@@ -562,16 +567,19 @@ fileio_readinto(fileio *self, PyObject *args) ...@@ -562,16 +567,19 @@ fileio_readinto(fileio *self, PyObject *args)
if (_PyVerify_fd(self->fd)) { if (_PyVerify_fd(self->fd)) {
len = pbuf.len; len = pbuf.len;
Py_BEGIN_ALLOW_THREADS do {
errno = 0; Py_BEGIN_ALLOW_THREADS
errno = 0;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (len > INT_MAX) if (len > INT_MAX)
len = INT_MAX; len = INT_MAX;
n = read(self->fd, pbuf.buf, (int)len); n = read(self->fd, pbuf.buf, (int)len);
#else #else
n = read(self->fd, pbuf.buf, len); n = read(self->fd, pbuf.buf, len);
#endif #endif
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
} else } else
n = -1; n = -1;
err = errno; err = errno;
...@@ -580,7 +588,8 @@ fileio_readinto(fileio *self, PyObject *args) ...@@ -580,7 +588,8 @@ fileio_readinto(fileio *self, PyObject *args)
if (err == EAGAIN) if (err == EAGAIN)
Py_RETURN_NONE; Py_RETURN_NONE;
errno = err; errno = err;
PyErr_SetFromErrno(PyExc_IOError); if (!async_err)
PyErr_SetFromErrno(PyExc_IOError);
return NULL; return NULL;
} }
...@@ -627,6 +636,7 @@ fileio_readall(fileio *self) ...@@ -627,6 +636,7 @@ fileio_readall(fileio *self)
Py_ssize_t bytes_read = 0; Py_ssize_t bytes_read = 0;
Py_ssize_t n; Py_ssize_t n;
size_t bufsize; size_t bufsize;
int async_err = 0;
if (self->fd < 0) if (self->fd < 0)
return err_closed(); return err_closed();
...@@ -673,27 +683,23 @@ fileio_readall(fileio *self) ...@@ -673,27 +683,23 @@ fileio_readall(fileio *self)
return NULL; return NULL;
} }
} }
Py_BEGIN_ALLOW_THREADS do {
errno = 0; Py_BEGIN_ALLOW_THREADS
n = bufsize - bytes_read; errno = 0;
n = bufsize - bytes_read;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (n > INT_MAX) if (n > INT_MAX)
n = INT_MAX; n = INT_MAX;
n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, (int)n); n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, (int)n);
#else #else
n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, n); n = read(self->fd, PyBytes_AS_STRING(result) + bytes_read, n);
#endif #endif
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
if (n == 0) if (n == 0)
break; break;
if (n < 0) { if (n < 0) {
if (errno == EINTR) {
if (PyErr_CheckSignals()) {
Py_DECREF(result);
return NULL;
}
continue;
}
if (errno == EAGAIN) { if (errno == EAGAIN) {
if (bytes_read > 0) if (bytes_read > 0)
break; break;
...@@ -701,7 +707,8 @@ fileio_readall(fileio *self) ...@@ -701,7 +707,8 @@ fileio_readall(fileio *self)
Py_RETURN_NONE; Py_RETURN_NONE;
} }
Py_DECREF(result); Py_DECREF(result);
PyErr_SetFromErrno(PyExc_IOError); if (!async_err)
PyErr_SetFromErrno(PyExc_IOError);
return NULL; return NULL;
} }
bytes_read += n; bytes_read += n;
...@@ -723,6 +730,7 @@ fileio_read(fileio *self, PyObject *args) ...@@ -723,6 +730,7 @@ fileio_read(fileio *self, PyObject *args)
char *ptr; char *ptr;
Py_ssize_t n; Py_ssize_t n;
Py_ssize_t size = -1; Py_ssize_t size = -1;
int async_err = 0;
PyObject *bytes; PyObject *bytes;
if (self->fd < 0) if (self->fd < 0)
...@@ -747,14 +755,17 @@ fileio_read(fileio *self, PyObject *args) ...@@ -747,14 +755,17 @@ fileio_read(fileio *self, PyObject *args)
ptr = PyBytes_AS_STRING(bytes); ptr = PyBytes_AS_STRING(bytes);
if (_PyVerify_fd(self->fd)) { if (_PyVerify_fd(self->fd)) {
Py_BEGIN_ALLOW_THREADS do {
errno = 0; Py_BEGIN_ALLOW_THREADS
errno = 0;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
n = read(self->fd, ptr, (int)size); n = read(self->fd, ptr, (int)size);
#else #else
n = read(self->fd, ptr, size); n = read(self->fd, ptr, size);
#endif #endif
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
} else } else
n = -1; n = -1;
...@@ -764,7 +775,8 @@ fileio_read(fileio *self, PyObject *args) ...@@ -764,7 +775,8 @@ fileio_read(fileio *self, PyObject *args)
if (err == EAGAIN) if (err == EAGAIN)
Py_RETURN_NONE; Py_RETURN_NONE;
errno = err; errno = err;
PyErr_SetFromErrno(PyExc_IOError); if (!async_err)
PyErr_SetFromErrno(PyExc_IOError);
return NULL; return NULL;
} }
...@@ -783,7 +795,7 @@ fileio_write(fileio *self, PyObject *args) ...@@ -783,7 +795,7 @@ fileio_write(fileio *self, PyObject *args)
{ {
Py_buffer pbuf; Py_buffer pbuf;
Py_ssize_t n, len; Py_ssize_t n, len;
int err; int err, async_err = 0;
if (self->fd < 0) if (self->fd < 0)
return err_closed(); return err_closed();
...@@ -794,24 +806,26 @@ fileio_write(fileio *self, PyObject *args) ...@@ -794,24 +806,26 @@ fileio_write(fileio *self, PyObject *args)
return NULL; return NULL;
if (_PyVerify_fd(self->fd)) { if (_PyVerify_fd(self->fd)) {
Py_BEGIN_ALLOW_THREADS do {
errno = 0; Py_BEGIN_ALLOW_THREADS
len = pbuf.len; errno = 0;
len = pbuf.len;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
if (len > 32767 && isatty(self->fd)) { if (len > 32767 && isatty(self->fd)) {
/* Issue #11395: the Windows console returns an error (12: not /* Issue #11395: the Windows console returns an error (12: not
enough space error) on writing into stdout if stdout mode is enough space error) on writing into stdout if stdout mode is
binary and the length is greater than 66,000 bytes (or less, binary and the length is greater than 66,000 bytes (or less,
depending on heap usage). */ depending on heap usage). */
len = 32767; len = 32767;
} } else if (len > INT_MAX)
else if (len > INT_MAX) len = INT_MAX;
len = INT_MAX; n = write(self->fd, pbuf.buf, (int)len);
n = write(self->fd, pbuf.buf, (int)len);
#else #else
n = write(self->fd, pbuf.buf, len); n = write(self->fd, pbuf.buf, len);
#endif #endif
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
} while (n < 0 && errno == EINTR &&
!(async_err = PyErr_CheckSignals()));
} else } else
n = -1; n = -1;
err = errno; err = errno;
...@@ -822,7 +836,8 @@ fileio_write(fileio *self, PyObject *args) ...@@ -822,7 +836,8 @@ fileio_write(fileio *self, PyObject *args)
if (err == EAGAIN) if (err == EAGAIN)
Py_RETURN_NONE; Py_RETURN_NONE;
errno = err; errno = err;
PyErr_SetFromErrno(PyExc_IOError); if (!async_err)
PyErr_SetFromErrno(PyExc_IOError);
return NULL; return NULL;
} }
......
This diff is collapsed.
This diff is collapsed.
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