diff --git a/Doc/library/test.rst b/Doc/library/test.rst index b7a2595708d..0a98c882465 100644 --- a/Doc/library/test.rst +++ b/Doc/library/test.rst @@ -1086,6 +1086,18 @@ 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. + Usage:: with support.catch_unraisable_exception() as cm: diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index d6ed2215f38..174e0456dc7 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -3040,6 +3040,18 @@ 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. + Usage: with support.catch_unraisable_exception() as cm: @@ -3058,6 +3070,8 @@ class catch_unraisable_exception: self._old_hook = None def _hook(self, unraisable): + # Storing unraisable.object can resurrect an object which is being + # finalized. Storing unraisable.exc_value creates a reference cycle. self.unraisable = unraisable def __enter__(self): @@ -3066,6 +3080,10 @@ class catch_unraisable_exception: return self def __exit__(self, *exc_info): - # Clear the unraisable exception to explicitly break a reference cycle - del self.unraisable + # 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/Misc/NEWS.d/next/Tests/2019-06-13-12-19-56.bpo-37261.NuKFVo.rst b/Misc/NEWS.d/next/Tests/2019-06-13-12-19-56.bpo-37261.NuKFVo.rst new file mode 100644 index 00000000000..27ce78a70f2 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2019-06-13-12-19-56.bpo-37261.NuKFVo.rst @@ -0,0 +1,3 @@ +Fix :func:`test.support.catch_unraisable_exception`: its __exit__() method +now ignores unraisable exception raised when clearing its ``unraisable`` +attribute.