From 2f33cd8dcb2255f8642cb1629c49c74c530091a4 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 12 Feb 2024 17:18:27 +0100 Subject: [PATCH 1/2] 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 3191315049ad5a4..7994eb13bcce833 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 ad8857fc2807f70..351c44b1915159f 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 b14fb4e5392a2c3..1e0764144a28553 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 479b908fb7058ac..32c5884cd21341c 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 217ee15d4c8af54..65f54859e2a21d0 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 5e187e5189d1179..19f977971570b76 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 000000000000000..828d47d0e18d6b6 --- /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 aa1a300e4378dd9..bcbbe70680b8e76 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 d7840eaf45e8d69..da6a8bc7b120fed 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 83a4ccd4802ae01..8f9b8520bb3f349 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 ef6d65623bf0388..958b5a5e6e24066 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 3df3a9b3b1a2530..63c461d34fb4ff2 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 5c5016f71371640..45119664af4362a 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 - From 30bd9c94f1aa48cc37de971ad76f82be9418c6dd Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Mon, 12 Feb 2024 16:11:36 -0800 Subject: [PATCH 2/2] minor grammar fixes to exceptions.rst --- Doc/library/exceptions.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 7994eb13bcce833..88417b40e4aa7f9 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -419,13 +419,13 @@ The following exceptions are the exceptions that are usually raised. .. exception:: PythonFinalizationError This exception is derived from :exc:`RuntimeError`. It is raised when - an operations is blocked during the :term:`Python finalization `. + 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: - * create a new Python thread; + * Creating a new Python thread. * :func:`os.fork`. See also the :func:`sys.is_finalizing` function.