diff --git a/Lib/_pyio.py b/Lib/_pyio.py index f1611a48261..1df44ccbba7 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -1291,8 +1291,10 @@ def flush(self): return self.writer.flush() def close(self): - self.writer.close() - self.reader.close() + try: + self.writer.close() + finally: + self.reader.close() def isatty(self): return self.reader.isatty() or self.writer.isatty() diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index e5c6073ba32..6a41ae62d9c 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1649,6 +1649,53 @@ def test_close_and_closed(self): pair.close() self.assertTrue(pair.closed) + def test_reader_close_error_on_close(self): + def reader_close(): + reader_non_existing + reader = self.MockRawIO() + reader.close = reader_close + writer = self.MockRawIO() + pair = self.tp(reader, writer) + with self.assertRaises(NameError) as err: + pair.close() + self.assertIn('reader_non_existing', str(err.exception)) + self.assertTrue(pair.closed) + self.assertFalse(reader.closed) + self.assertTrue(writer.closed) + + def test_writer_close_error_on_close(self): + def writer_close(): + writer_non_existing + reader = self.MockRawIO() + writer = self.MockRawIO() + writer.close = writer_close + pair = self.tp(reader, writer) + with self.assertRaises(NameError) as err: + pair.close() + self.assertIn('writer_non_existing', str(err.exception)) + self.assertFalse(pair.closed) + self.assertTrue(reader.closed) + self.assertFalse(writer.closed) + + def test_reader_writer_close_error_on_close(self): + def reader_close(): + reader_non_existing + def writer_close(): + writer_non_existing + reader = self.MockRawIO() + reader.close = reader_close + writer = self.MockRawIO() + writer.close = writer_close + pair = self.tp(reader, writer) + with self.assertRaises(NameError) as err: + pair.close() + self.assertIn('reader_non_existing', str(err.exception)) + self.assertIsInstance(err.exception.__context__, NameError) + self.assertIn('writer_non_existing', str(err.exception.__context__)) + self.assertFalse(pair.closed) + self.assertFalse(reader.closed) + self.assertFalse(writer.closed) + def test_isatty(self): class SelectableIsAtty(MockRawIO): def __init__(self, isatty): diff --git a/Misc/NEWS b/Misc/NEWS index c1489983a3c..b85eeafe880 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -30,6 +30,9 @@ Core and Builtins Library ------- +- Issue #21802: The reader in BufferedRWPair now is closed even when closing + writer failed in BufferedRWPair.close(). + - Issue #23622: Unknown escapes in regular expressions that consist of ``'\'`` and ASCII letter now raise a deprecation warning and will be forbidden in Python 3.6. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 358a94dce27..ea9f5332a4a 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -2413,12 +2413,18 @@ bufferedrwpair_writable(rwpair *self, PyObject *args) static PyObject * bufferedrwpair_close(rwpair *self, PyObject *args) { + PyObject *exc = NULL, *val, *tb; PyObject *ret = _forward_call(self->writer, &PyId_close, args); if (ret == NULL) - return NULL; - Py_DECREF(ret); - - return _forward_call(self->reader, &PyId_close, args); + PyErr_Fetch(&exc, &val, &tb); + else + Py_DECREF(ret); + ret = _forward_call(self->reader, &PyId_close, args); + if (exc != NULL) { + _PyErr_ChainExceptions(exc, val, tb); + Py_CLEAR(ret); + } + return ret; } static PyObject *