From 3e7b7df5cbaad5617cc28f0c005010787c48e6d6 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 14 Feb 2024 23:35:06 +0100 Subject: [PATCH] gh-114570: Add PythonFinalizationError exception (#115352) Add PythonFinalizationError exception. This exception derived from RuntimeError is raised when an operation is blocked during the Python finalization. The following functions now raise PythonFinalizationError, instead of RuntimeError: * _thread.start_new_thread() * subprocess.Popen * os.fork() * os.fork1() * os.forkpty() Morever, _winapi.Overlapped finalizer now logs an unraisable PythonFinalizationError, instead of an unraisable RuntimeError. --- Doc/library/exceptions.rst | 18 ++++++++++++++++++ Doc/library/sys.rst | 2 ++ Doc/whatsnew/3.13.rst | 15 +++++++++++++++ Include/cpython/pyerrors.h | 2 ++ Lib/test/exception_hierarchy.txt | 1 + Lib/test/test_pickle.py | 1 + ...4-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst | 3 +++ Modules/_posixsubprocess.c | 2 +- Modules/_threadmodule.c | 2 +- Modules/_winapi.c | 2 +- Modules/posixmodule.c | 6 +++--- Objects/exceptions.c | 5 +++++ Tools/c-analyzer/cpython/globals-to-fix.tsv | 2 ++ 13 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 3191315049a..88417b40e4a 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -416,6 +416,24 @@ The following exceptions are the exceptions that are usually raised. handling in C, most floating point operations are not checked. +.. exception:: PythonFinalizationError + + This exception is derived from :exc:`RuntimeError`. It is raised when + an operation is blocked during interpreter shutdown also known as + :term:`Python finalization `. + + Examples of operations which can be blocked with a + :exc:`PythonFinalizationError` during the Python finalization: + + * Creating a new Python thread. + * :func:`os.fork`. + + See also the :func:`sys.is_finalizing` function. + + .. versionadded:: 3.13 + Previously, a plain :exc:`RuntimeError` was raised. + + .. exception:: RecursionError This exception is derived from :exc:`RuntimeError`. It is raised when the diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index ad8857fc280..351c44b1915 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -1202,6 +1202,8 @@ always available. Return :const:`True` if the main Python interpreter is :term:`shutting down `. Return :const:`False` otherwise. + See also the :exc:`PythonFinalizationError` exception. + .. versionadded:: 3.5 .. data:: last_exc diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index b14fb4e5392..1e0764144a2 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -160,6 +160,21 @@ Other Language Changes (Contributed by Levi Sabah, Zackery Spytz and Hugo van Kemenade in :gh:`73965`.) +* Add :exc:`PythonFinalizationError` exception. This exception derived from + :exc:`RuntimeError` is raised when an operation is blocked during + the :term:`Python finalization `. + + The following functions now raise PythonFinalizationError, instead of + :exc:`RuntimeError`: + + * :func:`_thread.start_new_thread`. + * :class:`subprocess.Popen`. + * :func:`os.fork`. + * :func:`os.forkpty`. + + (Contributed by Victor Stinner in :gh:`114570`.) + + New Modules =========== diff --git a/Include/cpython/pyerrors.h b/Include/cpython/pyerrors.h index 479b908fb70..32c5884cd21 100644 --- a/Include/cpython/pyerrors.h +++ b/Include/cpython/pyerrors.h @@ -122,4 +122,6 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalErrorFunc( PyAPI_FUNC(void) PyErr_FormatUnraisable(const char *, ...); +PyAPI_DATA(PyObject *) PyExc_PythonFinalizationError; + #define Py_FatalError(message) _Py_FatalErrorFunc(__func__, (message)) diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt index 217ee15d4c8..65f54859e2a 100644 --- a/Lib/test/exception_hierarchy.txt +++ b/Lib/test/exception_hierarchy.txt @@ -40,6 +40,7 @@ BaseException ├── ReferenceError ├── RuntimeError │ ├── NotImplementedError + │ ├── PythonFinalizationError │ └── RecursionError ├── StopAsyncIteration ├── StopIteration diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index 5e187e5189d..19f97797157 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -564,6 +564,7 @@ class CompatPickleTests(unittest.TestCase): if exc in (BlockingIOError, ResourceWarning, StopAsyncIteration, + PythonFinalizationError, RecursionError, EncodingWarning, BaseExceptionGroup, diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst b/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst new file mode 100644 index 00000000000..828d47d0e18 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-02-12-17-18-26.gh-issue-114570.BzwMlJ.rst @@ -0,0 +1,3 @@ +Add :exc:`PythonFinalizationError` exception. This exception derived from +:exc:`RuntimeError` is raised when an operation is blocked during the +:term:`Python finalization `. Patch by Victor Stinner. diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index aa1a300e437..bcbbe70680b 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -1032,7 +1032,7 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args, PyInterpreterState *interp = _PyInterpreterState_GET(); if ((preexec_fn != Py_None) && interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "preexec_fn not supported at interpreter shutdown"); return NULL; } diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index d7840eaf45e..da6a8bc7b12 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1304,7 +1304,7 @@ do_start_new_thread(thread_module_state* state, return -1; } if (interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "can't create new thread at interpreter shutdown"); return -1; } diff --git a/Modules/_winapi.c b/Modules/_winapi.c index 83a4ccd4802..8f9b8520bb3 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -139,7 +139,7 @@ overlapped_dealloc(OverlappedObject *self) { /* The operation is still pending -- give a warning. This will probably only happen on Windows XP. */ - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "I/O operations still in flight while destroying " "Overlapped object, the process may crash"); PyErr_WriteUnraisable(NULL); diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index ef6d65623bf..958b5a5e6e2 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7841,7 +7841,7 @@ os_fork1_impl(PyObject *module) PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "can't fork at interpreter shutdown"); return NULL; } @@ -7885,7 +7885,7 @@ os_fork_impl(PyObject *module) pid_t pid; PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "can't fork at interpreter shutdown"); return NULL; } @@ -8718,7 +8718,7 @@ os_forkpty_impl(PyObject *module) PyInterpreterState *interp = _PyInterpreterState_GET(); if (interp->finalizing) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_PythonFinalizationError, "can't fork at interpreter shutdown"); return NULL; } diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 3df3a9b3b1a..63c461d34fb 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2177,6 +2177,10 @@ SimpleExtendsException(PyExc_Exception, RuntimeError, SimpleExtendsException(PyExc_RuntimeError, RecursionError, "Recursion limit exceeded."); +// PythonFinalizationError extends RuntimeError +SimpleExtendsException(PyExc_RuntimeError, PythonFinalizationError, + "Operation blocked during Python finalization."); + /* * NotImplementedError extends RuntimeError */ @@ -3641,6 +3645,7 @@ static struct static_exception static_exceptions[] = { ITEM(KeyError), // base: LookupError(Exception) ITEM(ModuleNotFoundError), // base: ImportError(Exception) ITEM(NotImplementedError), // base: RuntimeError(Exception) + ITEM(PythonFinalizationError), // base: RuntimeError(Exception) ITEM(RecursionError), // base: RuntimeError(Exception) ITEM(UnboundLocalError), // base: NameError(Exception) ITEM(UnicodeError), // base: ValueError(Exception) diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 5c5016f7137..45119664af4 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -189,6 +189,7 @@ Objects/exceptions.c - _PyExc_ProcessLookupError - Objects/exceptions.c - _PyExc_TimeoutError - Objects/exceptions.c - _PyExc_EOFError - Objects/exceptions.c - _PyExc_RuntimeError - +Objects/exceptions.c - _PyExc_PythonFinalizationError - Objects/exceptions.c - _PyExc_RecursionError - Objects/exceptions.c - _PyExc_NotImplementedError - Objects/exceptions.c - _PyExc_NameError - @@ -254,6 +255,7 @@ Objects/exceptions.c - PyExc_ProcessLookupError - Objects/exceptions.c - PyExc_TimeoutError - Objects/exceptions.c - PyExc_EOFError - Objects/exceptions.c - PyExc_RuntimeError - +Objects/exceptions.c - PyExc_PythonFinalizationError - Objects/exceptions.c - PyExc_RecursionError - Objects/exceptions.c - PyExc_NotImplementedError - Objects/exceptions.c - PyExc_NameError -