From 2f33cd8dcb2255f8642cb1629c49c74c530091a4 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 12 Feb 2024 17:18:27 +0100 Subject: [PATCH] gh-114570: Add PythonFinalizationError exception 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 3191315049ad5a..7994eb13bcce83 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 operations is blocked during the :term:`Python finalization `. + + Examples of operations which can be blocked with a + :exc:`PythonFinalizationError` during the Python finalization: + + * create 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 ad8857fc2807f7..351c44b1915159 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 b14fb4e5392a2c..1e0764144a2855 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 479b908fb7058a..32c5884cd21341 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 217ee15d4c8af5..65f54859e2a21d 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 5e187e5189d117..19f977971570b7 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -564,6 +564,7 @@ def test_exceptions(self): 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 00000000000000..828d47d0e18d6b --- /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 aa1a300e4378dd..bcbbe70680b8e7 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 d7840eaf45e8d6..da6a8bc7b120fe 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 83a4ccd4802ae0..8f9b8520bb3f34 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 ef6d65623bf038..958b5a5e6e2406 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 3df3a9b3b1a253..63c461d34fb4ff 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 5c5016f7137164..45119664af4362 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 -