From 3b976d19c8c09e83eec63a5b62daf4d55bfd6aeb Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 14 Jun 2019 09:59:54 -0700 Subject: [PATCH] bpo-37261: Document sys.unraisablehook corner cases (GH-14059) Document reference cycle and resurrected objects issues in sys.unraisablehook() and threading.excepthook() documentation. Fix test.support.catch_unraisable_exception(): __exit__() no longer ignores unraisable exceptions. Fix test_io test_writer_close_error_on_close(): use a second catch_unraisable_exception() to catch the BufferedWriter unraisable exception. (cherry picked from commit 212646cae6b7c4ddc8d98c8b9b6d39a5f259e864) Co-authored-by: Victor Stinner --- Doc/library/sys.rst | 14 +++++++++++--- Doc/library/test.rst | 10 +++------- Doc/library/threading.rst | 8 ++++++++ Lib/test/support/__init__.py | 15 +++------------ Lib/test/test_io.py | 6 +++++- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 5bde687071..817c3f1e56 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1514,13 +1514,21 @@ always available. * *err_msg*: Error message, can be ``None``. * *object*: Object causing the exception, can be ``None``. - :func:`sys.unraisablehook` can be overridden to control how unraisable - exceptions are handled. - The default hook formats *err_msg* and *object* as: ``f'{err_msg}: {object!r}'``; use "Exception ignored in" error message if *err_msg* is ``None``. + :func:`sys.unraisablehook` can be overridden to control how unraisable + exceptions are handled. + + Storing *exc_value* using a custom hook can create a reference cycle. It + should be cleared explicitly to break the reference cycle when the + exception is no longer needed. + + Storing *object* using a custom hook can resurrect it if it is set to an + object which is being finalized. Avoid storing *object* after the custom + hook completes to avoid resurrecting objects. + See also :func:`excepthook` which handles uncaught exceptions. .. versionadded:: 3.8 diff --git a/Doc/library/test.rst b/Doc/library/test.rst index 0a98c88246..920c018084 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1086,17 +1086,13 @@ The :mod:`test.support` module defines the following functions: Context manager catching unraisable exception using :func:`sys.unraisablehook`. - If the *object* attribute of the unraisable hook is set and the object is - being finalized, the object is resurrected because the context manager - stores a strong reference to it (``cm.unraisable.object``). - Storing the exception value (``cm.unraisable.exc_value``) creates a reference cycle. The reference cycle is broken explicitly when the context manager exits. - Exiting the context manager clears the stored unraisable exception. It can - trigger a new unraisable exception (ex: the resurrected object is finalized - again and raises the same exception): it is silently ignored in this case. + Storing the object (``cm.unraisable.object``) can resurrect it if it is set + to an object which is being finalized. Exiting the context manager clears + the stored object. Usage:: diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 2907b65f5b..9ffd5cd581 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -58,6 +58,14 @@ This module defines the following functions: :func:`threading.excepthook` can be overridden to control how uncaught exceptions raised by :func:`Thread.run` are handled. + Storing *exc_value* using a custom hook can create a reference cycle. It + should be cleared explicitly to break the reference cycle when the + exception is no longer needed. + + Storing *object* using a custom hook can resurrect it if it is set to an + object which is being finalized. Avoid storing *object* after the custom + hook completes to avoid resurrecting objects. + .. seealso:: :func:`sys.excepthook` handles uncaught exceptions. diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 174e0456dc..7c0efc783e 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -3040,17 +3040,13 @@ class catch_unraisable_exception: """ Context manager catching unraisable exception using sys.unraisablehook. - If the *object* attribute of the unraisable hook is set and the object is - being finalized, the object is resurrected because the context manager - stores a strong reference to it (cm.unraisable.object). - Storing the exception value (cm.unraisable.exc_value) creates a reference cycle. The reference cycle is broken explicitly when the context manager exits. - Exiting the context manager clears the stored unraisable exception. It can - trigger a new unraisable exception (ex: the resurrected object is finalized - again and raises the same exception): it is silently ignored in this case. + Storing the object (cm.unraisable.object) can resurrect it if it is set to + an object which is being finalized. Exiting the context manager clears the + stored object. Usage: @@ -3080,10 +3076,5 @@ class catch_unraisable_exception: return self def __exit__(self, *exc_info): - # Clear the unraisable exception to explicitly break a reference cycle. - # It can call _hook() again: ignore the new unraisable exception in - # this case. - self.unraisable = None - sys.unraisablehook = self._old_hook del self.unraisable diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 55686d7439..fc474c9905 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2072,8 +2072,12 @@ class BufferedRWPairTest(unittest.TestCase): writer.close = lambda: None writer = None + # Ignore BufferedWriter (of the BufferedRWPair) unraisable exception with support.catch_unraisable_exception(): - pair = None + # Ignore BufferedRWPair unraisable exception + with support.catch_unraisable_exception(): + pair = None + support.gc_collect() support.gc_collect() def test_reader_writer_close_error_on_close(self): -- 2.40.0