bpo-36475: Finalize PyEval_AcquireLock() and PyEval_AcquireThread() properly (GH-12667)
PyEval_AcquireLock() and PyEval_AcquireThread() now terminate the current thread if called while the interpreter is finalizing, making them consistent with PyEval_RestoreThread(), Py_END_ALLOW_THREADS, and PyGILState_Ensure().
This commit is contained in:
parent
254b309c80
commit
f781d202a2
|
@ -1080,6 +1080,18 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
|
||||||
*tstate*, which should not be *NULL*. The lock must have been created earlier.
|
*tstate*, which should not be *NULL*. The lock must have been created earlier.
|
||||||
If this thread already has the lock, deadlock ensues.
|
If this thread already has the lock, deadlock ensues.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Calling this function from a thread when the runtime is finalizing
|
||||||
|
will terminate the thread, even if the thread was not created by Python.
|
||||||
|
You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to
|
||||||
|
check if the interpreter is in process of being finalized before calling
|
||||||
|
this function to avoid unwanted termination.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8
|
||||||
|
Updated to be consistent with :c:func:`PyEval_RestoreThread`,
|
||||||
|
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`,
|
||||||
|
and terminate the current thread if called while the interpreter is finalizing.
|
||||||
|
|
||||||
:c:func:`PyEval_RestoreThread` is a higher-level function which is always
|
:c:func:`PyEval_RestoreThread` is a higher-level function which is always
|
||||||
available (even when threads have not been initialized).
|
available (even when threads have not been initialized).
|
||||||
|
|
||||||
|
@ -1106,6 +1118,18 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
|
||||||
:c:func:`PyEval_RestoreThread` or :c:func:`PyEval_AcquireThread`
|
:c:func:`PyEval_RestoreThread` or :c:func:`PyEval_AcquireThread`
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Calling this function from a thread when the runtime is finalizing
|
||||||
|
will terminate the thread, even if the thread was not created by Python.
|
||||||
|
You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to
|
||||||
|
check if the interpreter is in process of being finalized before calling
|
||||||
|
this function to avoid unwanted termination.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.8
|
||||||
|
Updated to be consistent with :c:func:`PyEval_RestoreThread`,
|
||||||
|
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`,
|
||||||
|
and terminate the current thread if called while the interpreter is finalizing.
|
||||||
|
|
||||||
|
|
||||||
.. c:function:: void PyEval_ReleaseLock()
|
.. c:function:: void PyEval_ReleaseLock()
|
||||||
|
|
||||||
|
|
|
@ -758,6 +758,13 @@ Changes in Python behavior
|
||||||
always use the ``sys.platform.startswith('aix')``.
|
always use the ``sys.platform.startswith('aix')``.
|
||||||
(Contributed by M. Felt in :issue:`36588`.)
|
(Contributed by M. Felt in :issue:`36588`.)
|
||||||
|
|
||||||
|
* :c:func:`PyEval_AcquireLock` and :c:func:`PyEval_AcquireThread` now
|
||||||
|
terminate the current thread if called while the interpreter is
|
||||||
|
finalizing, making them consistent with :c:func:`PyEval_RestoreThread`,
|
||||||
|
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`. If this
|
||||||
|
behaviour is not desired, guard the call by checking :c:func:`_Py_IsFinalizing`
|
||||||
|
or :c:func:`sys.is_finalizing`.
|
||||||
|
|
||||||
Changes in the Python API
|
Changes in the Python API
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
:c:func:`PyEval_AcquireLock` and :c:func:`PyEval_AcquireThread` now
|
||||||
|
terminate the current thread if called while the interpreter is
|
||||||
|
finalizing, making them consistent with :c:func:`PyEval_RestoreThread`,
|
||||||
|
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`.
|
|
@ -76,6 +76,7 @@ static PyObject * special_lookup(PyObject *, _Py_Identifier *);
|
||||||
static int check_args_iterable(PyObject *func, PyObject *vararg);
|
static int check_args_iterable(PyObject *func, PyObject *vararg);
|
||||||
static void format_kwargs_error(PyObject *func, PyObject *kwargs);
|
static void format_kwargs_error(PyObject *func, PyObject *kwargs);
|
||||||
static void format_awaitable_error(PyTypeObject *, int);
|
static void format_awaitable_error(PyTypeObject *, int);
|
||||||
|
static inline void exit_thread_if_finalizing(PyThreadState *);
|
||||||
|
|
||||||
#define NAME_ERROR_MSG \
|
#define NAME_ERROR_MSG \
|
||||||
"name '%.200s' is not defined"
|
"name '%.200s' is not defined"
|
||||||
|
@ -203,6 +204,17 @@ _PyEval_FiniThreads(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
exit_thread_if_finalizing(PyThreadState *tstate)
|
||||||
|
{
|
||||||
|
/* _Py_Finalizing is protected by the GIL */
|
||||||
|
if (_Py_IsFinalizing() && !_Py_CURRENTLY_FINALIZING(tstate)) {
|
||||||
|
drop_gil(tstate);
|
||||||
|
PyThread_exit_thread();
|
||||||
|
Py_UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PyEval_AcquireLock(void)
|
PyEval_AcquireLock(void)
|
||||||
{
|
{
|
||||||
|
@ -210,6 +222,7 @@ PyEval_AcquireLock(void)
|
||||||
if (tstate == NULL)
|
if (tstate == NULL)
|
||||||
Py_FatalError("PyEval_AcquireLock: current thread state is NULL");
|
Py_FatalError("PyEval_AcquireLock: current thread state is NULL");
|
||||||
take_gil(tstate);
|
take_gil(tstate);
|
||||||
|
exit_thread_if_finalizing(tstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -230,6 +243,7 @@ PyEval_AcquireThread(PyThreadState *tstate)
|
||||||
/* Check someone has called PyEval_InitThreads() to create the lock */
|
/* Check someone has called PyEval_InitThreads() to create the lock */
|
||||||
assert(gil_created());
|
assert(gil_created());
|
||||||
take_gil(tstate);
|
take_gil(tstate);
|
||||||
|
exit_thread_if_finalizing(tstate);
|
||||||
if (PyThreadState_Swap(tstate) != NULL)
|
if (PyThreadState_Swap(tstate) != NULL)
|
||||||
Py_FatalError(
|
Py_FatalError(
|
||||||
"PyEval_AcquireThread: non-NULL old thread state");
|
"PyEval_AcquireThread: non-NULL old thread state");
|
||||||
|
@ -298,12 +312,7 @@ PyEval_RestoreThread(PyThreadState *tstate)
|
||||||
|
|
||||||
int err = errno;
|
int err = errno;
|
||||||
take_gil(tstate);
|
take_gil(tstate);
|
||||||
/* _Py_Finalizing is protected by the GIL */
|
exit_thread_if_finalizing(tstate);
|
||||||
if (_Py_IsFinalizing() && !_Py_CURRENTLY_FINALIZING(tstate)) {
|
|
||||||
drop_gil(tstate);
|
|
||||||
PyThread_exit_thread();
|
|
||||||
Py_UNREACHABLE();
|
|
||||||
}
|
|
||||||
errno = err;
|
errno = err;
|
||||||
|
|
||||||
PyThreadState_Swap(tstate);
|
PyThreadState_Swap(tstate);
|
||||||
|
@ -1083,12 +1092,7 @@ main_loop:
|
||||||
take_gil(tstate);
|
take_gil(tstate);
|
||||||
|
|
||||||
/* Check if we should make a quick exit. */
|
/* Check if we should make a quick exit. */
|
||||||
if (_Py_IsFinalizing() &&
|
exit_thread_if_finalizing(tstate);
|
||||||
!_Py_CURRENTLY_FINALIZING(tstate))
|
|
||||||
{
|
|
||||||
drop_gil(tstate);
|
|
||||||
PyThread_exit_thread();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PyThreadState_Swap(tstate) != NULL)
|
if (PyThreadState_Swap(tstate) != NULL)
|
||||||
Py_FatalError("ceval: orphan tstate");
|
Py_FatalError("ceval: orphan tstate");
|
||||||
|
|
Loading…
Reference in New Issue