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.
This commit is contained in:
Victor Stinner 2024-02-14 23:35:06 +01:00 committed by GitHub
parent 326119d373
commit 3e7b7df5cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 55 additions and 6 deletions

View File

@ -416,6 +416,24 @@ The following exceptions are the exceptions that are usually raised.
handling in C, most floating point operations are not checked. 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 <interpreter shutdown>`.
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 .. exception:: RecursionError
This exception is derived from :exc:`RuntimeError`. It is raised when the This exception is derived from :exc:`RuntimeError`. It is raised when the

View File

@ -1202,6 +1202,8 @@ always available.
Return :const:`True` if the main Python interpreter is Return :const:`True` if the main Python interpreter is
:term:`shutting down <interpreter shutdown>`. Return :const:`False` otherwise. :term:`shutting down <interpreter shutdown>`. Return :const:`False` otherwise.
See also the :exc:`PythonFinalizationError` exception.
.. versionadded:: 3.5 .. versionadded:: 3.5
.. data:: last_exc .. data:: last_exc

View File

@ -160,6 +160,21 @@ Other Language Changes
(Contributed by Levi Sabah, Zackery Spytz and Hugo van Kemenade in (Contributed by Levi Sabah, Zackery Spytz and Hugo van Kemenade in
:gh:`73965`.) :gh:`73965`.)
* Add :exc:`PythonFinalizationError` exception. This exception derived from
:exc:`RuntimeError` is raised when an operation is blocked during
the :term:`Python finalization <interpreter shutdown>`.
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 New Modules
=========== ===========

View File

@ -122,4 +122,6 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalErrorFunc(
PyAPI_FUNC(void) PyErr_FormatUnraisable(const char *, ...); PyAPI_FUNC(void) PyErr_FormatUnraisable(const char *, ...);
PyAPI_DATA(PyObject *) PyExc_PythonFinalizationError;
#define Py_FatalError(message) _Py_FatalErrorFunc(__func__, (message)) #define Py_FatalError(message) _Py_FatalErrorFunc(__func__, (message))

View File

@ -40,6 +40,7 @@ BaseException
├── ReferenceError ├── ReferenceError
├── RuntimeError ├── RuntimeError
│ ├── NotImplementedError │ ├── NotImplementedError
│ ├── PythonFinalizationError
│ └── RecursionError │ └── RecursionError
├── StopAsyncIteration ├── StopAsyncIteration
├── StopIteration ├── StopIteration

View File

@ -564,6 +564,7 @@ class CompatPickleTests(unittest.TestCase):
if exc in (BlockingIOError, if exc in (BlockingIOError,
ResourceWarning, ResourceWarning,
StopAsyncIteration, StopAsyncIteration,
PythonFinalizationError,
RecursionError, RecursionError,
EncodingWarning, EncodingWarning,
BaseExceptionGroup, BaseExceptionGroup,

View File

@ -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 <interpreter shutdown>`. Patch by Victor Stinner.

View File

@ -1032,7 +1032,7 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
if ((preexec_fn != Py_None) && interp->finalizing) { if ((preexec_fn != Py_None) && interp->finalizing) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_PythonFinalizationError,
"preexec_fn not supported at interpreter shutdown"); "preexec_fn not supported at interpreter shutdown");
return NULL; return NULL;
} }

View File

@ -1304,7 +1304,7 @@ do_start_new_thread(thread_module_state* state,
return -1; return -1;
} }
if (interp->finalizing) { if (interp->finalizing) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_PythonFinalizationError,
"can't create new thread at interpreter shutdown"); "can't create new thread at interpreter shutdown");
return -1; return -1;
} }

View File

@ -139,7 +139,7 @@ overlapped_dealloc(OverlappedObject *self)
{ {
/* The operation is still pending -- give a warning. This /* The operation is still pending -- give a warning. This
will probably only happen on Windows XP. */ will probably only happen on Windows XP. */
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_PythonFinalizationError,
"I/O operations still in flight while destroying " "I/O operations still in flight while destroying "
"Overlapped object, the process may crash"); "Overlapped object, the process may crash");
PyErr_WriteUnraisable(NULL); PyErr_WriteUnraisable(NULL);

View File

@ -7841,7 +7841,7 @@ os_fork1_impl(PyObject *module)
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->finalizing) { if (interp->finalizing) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_PythonFinalizationError,
"can't fork at interpreter shutdown"); "can't fork at interpreter shutdown");
return NULL; return NULL;
} }
@ -7885,7 +7885,7 @@ os_fork_impl(PyObject *module)
pid_t pid; pid_t pid;
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->finalizing) { if (interp->finalizing) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_PythonFinalizationError,
"can't fork at interpreter shutdown"); "can't fork at interpreter shutdown");
return NULL; return NULL;
} }
@ -8718,7 +8718,7 @@ os_forkpty_impl(PyObject *module)
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->finalizing) { if (interp->finalizing) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_PythonFinalizationError,
"can't fork at interpreter shutdown"); "can't fork at interpreter shutdown");
return NULL; return NULL;
} }

View File

@ -2177,6 +2177,10 @@ SimpleExtendsException(PyExc_Exception, RuntimeError,
SimpleExtendsException(PyExc_RuntimeError, RecursionError, SimpleExtendsException(PyExc_RuntimeError, RecursionError,
"Recursion limit exceeded."); "Recursion limit exceeded.");
// PythonFinalizationError extends RuntimeError
SimpleExtendsException(PyExc_RuntimeError, PythonFinalizationError,
"Operation blocked during Python finalization.");
/* /*
* NotImplementedError extends RuntimeError * NotImplementedError extends RuntimeError
*/ */
@ -3641,6 +3645,7 @@ static struct static_exception static_exceptions[] = {
ITEM(KeyError), // base: LookupError(Exception) ITEM(KeyError), // base: LookupError(Exception)
ITEM(ModuleNotFoundError), // base: ImportError(Exception) ITEM(ModuleNotFoundError), // base: ImportError(Exception)
ITEM(NotImplementedError), // base: RuntimeError(Exception) ITEM(NotImplementedError), // base: RuntimeError(Exception)
ITEM(PythonFinalizationError), // base: RuntimeError(Exception)
ITEM(RecursionError), // base: RuntimeError(Exception) ITEM(RecursionError), // base: RuntimeError(Exception)
ITEM(UnboundLocalError), // base: NameError(Exception) ITEM(UnboundLocalError), // base: NameError(Exception)
ITEM(UnicodeError), // base: ValueError(Exception) ITEM(UnicodeError), // base: ValueError(Exception)

View File

@ -189,6 +189,7 @@ Objects/exceptions.c - _PyExc_ProcessLookupError -
Objects/exceptions.c - _PyExc_TimeoutError - Objects/exceptions.c - _PyExc_TimeoutError -
Objects/exceptions.c - _PyExc_EOFError - Objects/exceptions.c - _PyExc_EOFError -
Objects/exceptions.c - _PyExc_RuntimeError - Objects/exceptions.c - _PyExc_RuntimeError -
Objects/exceptions.c - _PyExc_PythonFinalizationError -
Objects/exceptions.c - _PyExc_RecursionError - Objects/exceptions.c - _PyExc_RecursionError -
Objects/exceptions.c - _PyExc_NotImplementedError - Objects/exceptions.c - _PyExc_NotImplementedError -
Objects/exceptions.c - _PyExc_NameError - Objects/exceptions.c - _PyExc_NameError -
@ -254,6 +255,7 @@ Objects/exceptions.c - PyExc_ProcessLookupError -
Objects/exceptions.c - PyExc_TimeoutError - Objects/exceptions.c - PyExc_TimeoutError -
Objects/exceptions.c - PyExc_EOFError - Objects/exceptions.c - PyExc_EOFError -
Objects/exceptions.c - PyExc_RuntimeError - Objects/exceptions.c - PyExc_RuntimeError -
Objects/exceptions.c - PyExc_PythonFinalizationError -
Objects/exceptions.c - PyExc_RecursionError - Objects/exceptions.c - PyExc_RecursionError -
Objects/exceptions.c - PyExc_NotImplementedError - Objects/exceptions.c - PyExc_NotImplementedError -
Objects/exceptions.c - PyExc_NameError - Objects/exceptions.c - PyExc_NameError -

Can't render this file because it has a wrong number of fields in line 4.