bpo-39877: Refactor take_gil() function (GH-18885)
* Remove ceval parameter of take_gil(): get it from tstate.
* Move exit_thread_if_finalizing() call inside take_gil(). Replace
exit_thread_if_finalizing() with tstate_must_exit(): the caller is
now responsible to call PyThread_exit_thread().
* Move is_tstate_valid() assertion inside take_gil(). Remove
is_tstate_valid(): inline code into take_gil().
* Move gil_created() assertion inside take_gil().
diff --git a/Python/ceval.c b/Python/ceval.c
index 208fdab..72a8304 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -198,16 +198,6 @@
}
-#ifndef NDEBUG
-static int is_tstate_valid(PyThreadState *tstate)
-{
- assert(!_PyMem_IsPtrFreed(tstate));
- assert(!_PyMem_IsPtrFreed(tstate->interp));
- return 1;
-}
-#endif
-
-
int
PyEval_ThreadsInitialized(void)
{
@@ -230,13 +220,15 @@
PyThread_init_thread();
create_gil(gil);
- take_gil(ceval, tstate);
+
+ take_gil(tstate);
struct _pending_calls *pending = &ceval->pending;
pending->lock = PyThread_allocate_lock();
if (pending->lock == NULL) {
return _PyStatus_NO_MEMORY();
}
+
return _PyStatus_OK();
}
@@ -269,30 +261,6 @@
}
}
-/* This function is designed to exit daemon threads immediately rather than
- taking the GIL if Py_Finalize() has been called.
-
- The caller must *not* hold the GIL, since this function does not release
- the GIL before exiting the thread.
-
- When this function is called by a daemon thread after Py_Finalize() has been
- called, the GIL does no longer exist.
-
- tstate must be non-NULL. */
-static inline void
-exit_thread_if_finalizing(PyThreadState *tstate)
-{
- /* bpo-39877: Access _PyRuntime directly rather than using
- tstate->interp->runtime to support calls from Python daemon threads.
- After Py_Finalize() has been called, tstate can be a dangling pointer:
- point to PyThreadState freed memory. */
- _PyRuntimeState *runtime = &_PyRuntime;
- PyThreadState *finalizing = _PyRuntimeState_GetFinalizing(runtime);
- if (finalizing != NULL && finalizing != tstate) {
- PyThread_exit_thread();
- }
-}
-
void
_PyEval_Fini(void)
{
@@ -329,11 +297,7 @@
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
ensure_tstate_not_null(__func__, tstate);
- exit_thread_if_finalizing(tstate);
- assert(is_tstate_valid(tstate));
-
- struct _ceval_runtime_state *ceval = &runtime->ceval;
- take_gil(ceval, tstate);
+ take_gil(tstate);
}
void
@@ -353,17 +317,10 @@
{
ensure_tstate_not_null(__func__, tstate);
- exit_thread_if_finalizing(tstate);
- assert(is_tstate_valid(tstate));
+ take_gil(tstate);
- _PyRuntimeState *runtime = tstate->interp->runtime;
- struct _ceval_runtime_state *ceval = &runtime->ceval;
-
- /* Check that _PyEval_InitThreads() was called to create the lock */
- assert(gil_created(&ceval->gil));
-
- take_gil(ceval, tstate);
- if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
+ struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
+ if (_PyThreadState_Swap(gilstate, tstate) != NULL) {
Py_FatalError("non-NULL old thread state");
}
}
@@ -396,7 +353,8 @@
recreate_gil(&ceval->gil);
PyThreadState *tstate = _PyRuntimeState_GetThreadState(runtime);
ensure_tstate_not_null(__func__, tstate);
- take_gil(ceval, tstate);
+
+ take_gil(tstate);
struct _pending_calls *pending = &ceval->pending;
pending->lock = PyThread_allocate_lock();
@@ -436,16 +394,10 @@
{
ensure_tstate_not_null(__func__, tstate);
- exit_thread_if_finalizing(tstate);
- assert(is_tstate_valid(tstate));
+ take_gil(tstate);
- _PyRuntimeState *runtime = tstate->interp->runtime;
- struct _ceval_runtime_state *ceval = &runtime->ceval;
- assert(gil_created(&ceval->gil));
-
- take_gil(ceval, tstate);
-
- _PyThreadState_Swap(&runtime->gilstate, tstate);
+ struct _gilstate_runtime_state *gilstate = &tstate->interp->runtime->gilstate;
+ _PyThreadState_Swap(gilstate, tstate);
}
@@ -805,7 +757,6 @@
PyThreadState * const tstate = _PyRuntimeState_GetThreadState(runtime);
ensure_tstate_not_null(__func__, tstate);
- assert(is_tstate_valid(tstate));
/* when tracing we set things up so that
@@ -1294,10 +1245,7 @@
/* Other threads may run now */
- /* Check if we should make a quick exit. */
- exit_thread_if_finalizing(tstate);
-
- take_gil(ceval, tstate);
+ take_gil(tstate);
if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
Py_FatalError("orphan tstate");
diff --git a/Python/ceval_gil.h b/Python/ceval_gil.h
index 99d576d..03f04b9 100644
--- a/Python/ceval_gil.h
+++ b/Python/ceval_gil.h
@@ -180,17 +180,55 @@
#endif
}
+
+/* Check if a Python thread must exit immediately, rather than taking the GIL
+ if Py_Finalize() has been called.
+
+ When this function is called by a daemon thread after Py_Finalize() has been
+ called, the GIL does no longer exist.
+
+ tstate must be non-NULL. */
+static inline int
+tstate_must_exit(PyThreadState *tstate)
+{
+ /* bpo-39877: Access _PyRuntime directly rather than using
+ tstate->interp->runtime to support calls from Python daemon threads.
+ After Py_Finalize() has been called, tstate can be a dangling pointer:
+ point to PyThreadState freed memory. */
+ _PyRuntimeState *runtime = &_PyRuntime;
+ PyThreadState *finalizing = _PyRuntimeState_GetFinalizing(runtime);
+ return (finalizing != NULL && finalizing != tstate);
+}
+
+
/* Take the GIL.
The function saves errno at entry and restores its value at exit.
tstate must be non-NULL. */
static void
-take_gil(struct _ceval_runtime_state *ceval, PyThreadState *tstate)
+take_gil(PyThreadState *tstate)
{
int err = errno;
+ assert(tstate != NULL);
+
+ /* Check if we should make a quick exit. */
+ if (tstate_must_exit(tstate)) {
+ PyThread_exit_thread();
+ }
+
+ /* Ensure that tstate is valid: sanity check for PyEval_AcquireThread() and
+ PyEval_RestoreThread(). Detect if tstate memory was freed. */
+ assert(!_PyMem_IsPtrFreed(tstate));
+ assert(!_PyMem_IsPtrFreed(tstate->interp));
+
+ struct _ceval_runtime_state *ceval = &tstate->interp->runtime->ceval;
struct _gil_runtime_state *gil = &ceval->gil;
+
+ /* Check that _PyEval_InitThreads() was called to create the lock */
+ assert(gil_created(gil));
+
MUTEX_LOCK(gil->mutex);
if (!_Py_atomic_load_relaxed(&gil->locked)) {
@@ -215,6 +253,7 @@
SET_GIL_DROP_REQUEST(ceval);
}
}
+
_ready:
#ifdef FORCE_SWITCHING
/* This mutex must be taken before modifying gil->last_holder: