Skip to content

Commit f781d20

Browse files
nanjekyejoannahvstinner
authored andcommitted
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().
1 parent 254b309 commit f781d20

File tree

4 files changed

+51
-12
lines changed

4 files changed

+51
-12
lines changed

Doc/c-api/init.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,18 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
10801080
*tstate*, which should not be *NULL*. The lock must have been created earlier.
10811081
If this thread already has the lock, deadlock ensues.
10821082
1083+
.. note::
1084+
Calling this function from a thread when the runtime is finalizing
1085+
will terminate the thread, even if the thread was not created by Python.
1086+
You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to
1087+
check if the interpreter is in process of being finalized before calling
1088+
this function to avoid unwanted termination.
1089+
1090+
.. versionchanged:: 3.8
1091+
Updated to be consistent with :c:func:`PyEval_RestoreThread`,
1092+
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`,
1093+
and terminate the current thread if called while the interpreter is finalizing.
1094+
10831095
:c:func:`PyEval_RestoreThread` is a higher-level function which is always
10841096
available (even when threads have not been initialized).
10851097
@@ -1106,6 +1118,18 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
11061118
:c:func:`PyEval_RestoreThread` or :c:func:`PyEval_AcquireThread`
11071119
instead.
11081120
1121+
.. note::
1122+
Calling this function from a thread when the runtime is finalizing
1123+
will terminate the thread, even if the thread was not created by Python.
1124+
You can use :c:func:`_Py_IsFinalizing` or :func:`sys.is_finalizing` to
1125+
check if the interpreter is in process of being finalized before calling
1126+
this function to avoid unwanted termination.
1127+
1128+
.. versionchanged:: 3.8
1129+
Updated to be consistent with :c:func:`PyEval_RestoreThread`,
1130+
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`,
1131+
and terminate the current thread if called while the interpreter is finalizing.
1132+
11091133
11101134
.. c:function:: void PyEval_ReleaseLock()
11111135

Doc/whatsnew/3.8.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,13 @@ Changes in Python behavior
758758
always use the ``sys.platform.startswith('aix')``.
759759
(Contributed by M. Felt in :issue:`36588`.)
760760

761+
* :c:func:`PyEval_AcquireLock` and :c:func:`PyEval_AcquireThread` now
762+
terminate the current thread if called while the interpreter is
763+
finalizing, making them consistent with :c:func:`PyEval_RestoreThread`,
764+
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`. If this
765+
behaviour is not desired, guard the call by checking :c:func:`_Py_IsFinalizing`
766+
or :c:func:`sys.is_finalizing`.
767+
761768
Changes in the Python API
762769
-------------------------
763770

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:c:func:`PyEval_AcquireLock` and :c:func:`PyEval_AcquireThread` now
2+
terminate the current thread if called while the interpreter is
3+
finalizing, making them consistent with :c:func:`PyEval_RestoreThread`,
4+
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`.

Python/ceval.c

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ static PyObject * special_lookup(PyObject *, _Py_Identifier *);
7676
static int check_args_iterable(PyObject *func, PyObject *vararg);
7777
static void format_kwargs_error(PyObject *func, PyObject *kwargs);
7878
static void format_awaitable_error(PyTypeObject *, int);
79+
static inline void exit_thread_if_finalizing(PyThreadState *);
7980

8081
#define NAME_ERROR_MSG \
8182
"name '%.200s' is not defined"
@@ -203,13 +204,25 @@ _PyEval_FiniThreads(void)
203204
}
204205
}
205206

207+
static inline void
208+
exit_thread_if_finalizing(PyThreadState *tstate)
209+
{
210+
/* _Py_Finalizing is protected by the GIL */
211+
if (_Py_IsFinalizing() && !_Py_CURRENTLY_FINALIZING(tstate)) {
212+
drop_gil(tstate);
213+
PyThread_exit_thread();
214+
Py_UNREACHABLE();
215+
}
216+
}
217+
206218
void
207219
PyEval_AcquireLock(void)
208220
{
209221
PyThreadState *tstate = _PyThreadState_GET();
210222
if (tstate == NULL)
211223
Py_FatalError("PyEval_AcquireLock: current thread state is NULL");
212224
take_gil(tstate);
225+
exit_thread_if_finalizing(tstate);
213226
}
214227

215228
void
@@ -230,6 +243,7 @@ PyEval_AcquireThread(PyThreadState *tstate)
230243
/* Check someone has called PyEval_InitThreads() to create the lock */
231244
assert(gil_created());
232245
take_gil(tstate);
246+
exit_thread_if_finalizing(tstate);
233247
if (PyThreadState_Swap(tstate) != NULL)
234248
Py_FatalError(
235249
"PyEval_AcquireThread: non-NULL old thread state");
@@ -298,12 +312,7 @@ PyEval_RestoreThread(PyThreadState *tstate)
298312

299313
int err = errno;
300314
take_gil(tstate);
301-
/* _Py_Finalizing is protected by the GIL */
302-
if (_Py_IsFinalizing() && !_Py_CURRENTLY_FINALIZING(tstate)) {
303-
drop_gil(tstate);
304-
PyThread_exit_thread();
305-
Py_UNREACHABLE();
306-
}
315+
exit_thread_if_finalizing(tstate);
307316
errno = err;
308317

309318
PyThreadState_Swap(tstate);
@@ -1083,12 +1092,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
10831092
take_gil(tstate);
10841093

10851094
/* Check if we should make a quick exit. */
1086-
if (_Py_IsFinalizing() &&
1087-
!_Py_CURRENTLY_FINALIZING(tstate))
1088-
{
1089-
drop_gil(tstate);
1090-
PyThread_exit_thread();
1091-
}
1095+
exit_thread_if_finalizing(tstate);
10921096

10931097
if (PyThreadState_Swap(tstate) != NULL)
10941098
Py_FatalError("ceval: orphan tstate");

0 commit comments

Comments
 (0)