mirror of https://github.com/python/cpython
gh-62948: IOBase finalizer logs close() errors (#105104)
This commit is contained in:
parent
85e5d03163
commit
58a2e09816
|
@ -87,6 +87,15 @@ New Modules
|
||||||
Improved Modules
|
Improved Modules
|
||||||
================
|
================
|
||||||
|
|
||||||
|
io
|
||||||
|
--
|
||||||
|
|
||||||
|
The :class:`io.IOBase` finalizer now logs the ``close()`` method errors with
|
||||||
|
:data:`sys.unraisablehook`. Previously, errors were ignored silently by default,
|
||||||
|
and only logged in :ref:`Python Development Mode <devmode>` or on :ref:`Python
|
||||||
|
built on debug mode <debug-build>`.
|
||||||
|
(Contributed by Victor Stinner in :gh:`62948`.)
|
||||||
|
|
||||||
pathlib
|
pathlib
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
20
Lib/_pyio.py
20
Lib/_pyio.py
|
@ -33,11 +33,8 @@ DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
|
||||||
# Rebind for compatibility
|
# Rebind for compatibility
|
||||||
BlockingIOError = BlockingIOError
|
BlockingIOError = BlockingIOError
|
||||||
|
|
||||||
# Does io.IOBase finalizer log the exception if the close() method fails?
|
|
||||||
# The exception is ignored silently by default in release build.
|
|
||||||
_IOBASE_EMITS_UNRAISABLE = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode)
|
|
||||||
# Does open() check its 'errors' argument?
|
# Does open() check its 'errors' argument?
|
||||||
_CHECK_ERRORS = _IOBASE_EMITS_UNRAISABLE
|
_CHECK_ERRORS = (hasattr(sys, "gettotalrefcount") or sys.flags.dev_mode)
|
||||||
|
|
||||||
|
|
||||||
def text_encoding(encoding, stacklevel=2):
|
def text_encoding(encoding, stacklevel=2):
|
||||||
|
@ -416,18 +413,9 @@ class IOBase(metaclass=abc.ABCMeta):
|
||||||
if closed:
|
if closed:
|
||||||
return
|
return
|
||||||
|
|
||||||
if _IOBASE_EMITS_UNRAISABLE:
|
# If close() fails, the caller logs the exception with
|
||||||
self.close()
|
# sys.unraisablehook. close() must be called at the end at __del__().
|
||||||
else:
|
self.close()
|
||||||
# The try/except block is in case this is called at program
|
|
||||||
# exit time, when it's possible that globals have already been
|
|
||||||
# deleted, and then the close() call might fail. Since
|
|
||||||
# there's nothing we can do about such failures and they annoy
|
|
||||||
# the end users, we suppress the traceback.
|
|
||||||
try:
|
|
||||||
self.close()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
### Inquiries ###
|
### Inquiries ###
|
||||||
|
|
||||||
|
|
|
@ -66,10 +66,6 @@ else:
|
||||||
class EmptyStruct(ctypes.Structure):
|
class EmptyStruct(ctypes.Structure):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Does io.IOBase finalizer log the exception if the close() method fails?
|
|
||||||
# The exception is ignored silently by default in release build.
|
|
||||||
IOBASE_EMITS_UNRAISABLE = (support.Py_DEBUG or sys.flags.dev_mode)
|
|
||||||
|
|
||||||
|
|
||||||
def _default_chunk_size():
|
def _default_chunk_size():
|
||||||
"""Get the default TextIOWrapper chunk size"""
|
"""Get the default TextIOWrapper chunk size"""
|
||||||
|
@ -1218,10 +1214,7 @@ class CommonBufferedTests:
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
self.tp(rawio).xyzzy
|
self.tp(rawio).xyzzy
|
||||||
|
|
||||||
if not IOBASE_EMITS_UNRAISABLE:
|
self.assertEqual(cm.unraisable.exc_type, OSError)
|
||||||
self.assertIsNone(cm.unraisable)
|
|
||||||
elif cm.unraisable is not None:
|
|
||||||
self.assertEqual(cm.unraisable.exc_type, OSError)
|
|
||||||
|
|
||||||
def test_repr(self):
|
def test_repr(self):
|
||||||
raw = self.MockRawIO()
|
raw = self.MockRawIO()
|
||||||
|
@ -3022,10 +3015,7 @@ class TextIOWrapperTest(unittest.TestCase):
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
self.TextIOWrapper(rawio, encoding="utf-8").xyzzy
|
self.TextIOWrapper(rawio, encoding="utf-8").xyzzy
|
||||||
|
|
||||||
if not IOBASE_EMITS_UNRAISABLE:
|
self.assertEqual(cm.unraisable.exc_type, OSError)
|
||||||
self.assertIsNone(cm.unraisable)
|
|
||||||
elif cm.unraisable is not None:
|
|
||||||
self.assertEqual(cm.unraisable.exc_type, OSError)
|
|
||||||
|
|
||||||
# Systematic tests of the text I/O API
|
# Systematic tests of the text I/O API
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
The :class:`io.IOBase` finalizer now logs the ``close()`` method errors with
|
||||||
|
:data:`sys.unraisablehook`. Previously, errors were ignored silently by default,
|
||||||
|
and only logged in :ref:`Python Development Mode <devmode>` or on
|
||||||
|
:ref:`Python built on debug mode <debug-build>`. Patch by Victor Stinner.
|
|
@ -319,20 +319,8 @@ iobase_finalize(PyObject *self)
|
||||||
if (PyObject_SetAttr(self, &_Py_ID(_finalizing), Py_True))
|
if (PyObject_SetAttr(self, &_Py_ID(_finalizing), Py_True))
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
res = PyObject_CallMethodNoArgs((PyObject *)self, &_Py_ID(close));
|
res = PyObject_CallMethodNoArgs((PyObject *)self, &_Py_ID(close));
|
||||||
/* Silencing I/O errors is bad, but printing spurious tracebacks is
|
|
||||||
equally as bad, and potentially more frequent (because of
|
|
||||||
shutdown issues). */
|
|
||||||
if (res == NULL) {
|
if (res == NULL) {
|
||||||
#ifndef Py_DEBUG
|
|
||||||
if (_Py_GetConfig()->dev_mode) {
|
|
||||||
PyErr_WriteUnraisable(self);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
PyErr_Clear();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
PyErr_WriteUnraisable(self);
|
PyErr_WriteUnraisable(self);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Py_DECREF(res);
|
Py_DECREF(res);
|
||||||
|
|
Loading…
Reference in New Issue