Commit b46b9d59 authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #9617: Signals received during a low-level write operation aren't

ignored by the buffered IO layer anymore.
parent 522180a6
...@@ -27,6 +27,8 @@ import random ...@@ -27,6 +27,8 @@ import random
import unittest import unittest
import weakref import weakref
import abc import abc
import signal
import errno
from itertools import cycle, count from itertools import cycle, count
from collections import deque from collections import deque
from test import support from test import support
...@@ -2463,6 +2465,75 @@ class CMiscIOTest(MiscIOTest): ...@@ -2463,6 +2465,75 @@ class CMiscIOTest(MiscIOTest):
class PyMiscIOTest(MiscIOTest): class PyMiscIOTest(MiscIOTest):
io = pyio io = pyio
@unittest.skipIf(os.name == 'nt', 'POSIX signals required for this test.')
class SignalsTest(unittest.TestCase):
def setUp(self):
self.oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
def tearDown(self):
signal.signal(signal.SIGALRM, self.oldalrm)
def alarm_interrupt(self, sig, frame):
1/0
@unittest.skipUnless(threading, 'Threading required for this test.')
def check_interrupted_write(self, item, bytes, **fdopen_kwargs):
"""Check that a partial write, when it gets interrupted, properly
invokes the signal handler."""
read_results = []
def _read():
s = os.read(r, 1)
read_results.append(s)
t = threading.Thread(target=_read)
t.daemon = True
r, w = os.pipe()
try:
wio = self.io.open(w, **fdopen_kwargs)
t.start()
signal.alarm(1)
# Fill the pipe enough that the write will be blocking.
# It will be interrupted by the timer armed above. Since the
# other thread has read one byte, the low-level write will
# return with a successful (partial) result rather than an EINTR.
# The buffered IO layer must check for pending signal
# handlers, which in this case will invoke alarm_interrupt().
self.assertRaises(ZeroDivisionError,
wio.write, item * (1024 * 1024))
t.join()
# We got one byte, get another one and check that it isn't a
# repeat of the first one.
read_results.append(os.read(r, 1))
self.assertEqual(read_results, [bytes[0:1], bytes[1:2]])
finally:
os.close(w)
os.close(r)
# This is deliberate. If we didn't close the file descriptor
# before closing wio, wio would try to flush its internal
# buffer, and block again.
try:
wio.close()
except IOError as e:
if e.errno != errno.EBADF:
raise
def test_interrupted_write_unbuffered(self):
self.check_interrupted_write(b"xy", b"xy", mode="wb", buffering=0)
def test_interrupted_write_buffered(self):
self.check_interrupted_write(b"xy", b"xy", mode="wb")
def test_interrupted_write_text(self):
self.check_interrupted_write("xy", b"xy", mode="w", encoding="ascii")
class CSignalsTest(SignalsTest):
io = io
class PySignalsTest(SignalsTest):
io = pyio
def test_main(): def test_main():
tests = (CIOTest, PyIOTest, tests = (CIOTest, PyIOTest,
CBufferedReaderTest, PyBufferedReaderTest, CBufferedReaderTest, PyBufferedReaderTest,
...@@ -2472,7 +2543,9 @@ def test_main(): ...@@ -2472,7 +2543,9 @@ def test_main():
StatefulIncrementalDecoderTest, StatefulIncrementalDecoderTest,
CIncrementalNewlineDecoderTest, PyIncrementalNewlineDecoderTest, CIncrementalNewlineDecoderTest, PyIncrementalNewlineDecoderTest,
CTextIOWrapperTest, PyTextIOWrapperTest, CTextIOWrapperTest, PyTextIOWrapperTest,
CMiscIOTest, PyMiscIOTest,) CMiscIOTest, PyMiscIOTest,
CSignalsTest, PySignalsTest,
)
# Put the namespaces of the IO module we are testing and some useful mock # Put the namespaces of the IO module we are testing and some useful mock
# classes in the __dict__ of each test. # classes in the __dict__ of each test.
......
...@@ -117,6 +117,9 @@ Extensions ...@@ -117,6 +117,9 @@ Extensions
Library Library
------- -------
- Issue #9617: Signals received during a low-level write operation aren't
ignored by the buffered IO layer anymore.
- Issue #843590: Make "macintosh" an alias to the "mac_roman" encoding. - Issue #843590: Make "macintosh" an alias to the "mac_roman" encoding.
- Create os.fsdecode(): decode from the filesystem encoding with - Create os.fsdecode(): decode from the filesystem encoding with
......
...@@ -1665,6 +1665,11 @@ _bufferedwriter_flush_unlocked(buffered *self, int restore_pos) ...@@ -1665,6 +1665,11 @@ _bufferedwriter_flush_unlocked(buffered *self, int restore_pos)
self->write_pos += n; self->write_pos += n;
self->raw_pos = self->write_pos; self->raw_pos = self->write_pos;
written += Py_SAFE_DOWNCAST(n, Py_off_t, Py_ssize_t); written += Py_SAFE_DOWNCAST(n, Py_off_t, Py_ssize_t);
/* Partial writes can return successfully when interrupted by a
signal (see write(2)). We must run signal handlers before
blocking another time, possibly indefinitely. */
if (PyErr_CheckSignals() < 0)
goto error;
} }
if (restore_pos) { if (restore_pos) {
...@@ -1802,6 +1807,11 @@ bufferedwriter_write(buffered *self, PyObject *args) ...@@ -1802,6 +1807,11 @@ bufferedwriter_write(buffered *self, PyObject *args)
} }
written += n; written += n;
remaining -= n; remaining -= n;
/* Partial writes can return successfully when interrupted by a
signal (see write(2)). We must run signal handlers before
blocking another time, possibly indefinitely. */
if (PyErr_CheckSignals() < 0)
goto error;
} }
if (self->readable) if (self->readable)
_bufferedreader_reset_buf(self); _bufferedreader_reset_buf(self);
......
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