From 08bf50d2a2c1a38797ef4901e59a225506c10315 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 30 Jul 2026 10:34:36 -0600 Subject: [PATCH 1/2] Use PyGilState_Ensure to avoid accessing CPython internals --- src/c/misc_thread_common.h | 55 ++++++++------------------------------ 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/src/c/misc_thread_common.h b/src/c/misc_thread_common.h index 766dcb2a..ca12d4d6 100644 --- a/src/c/misc_thread_common.h +++ b/src/c/misc_thread_common.h @@ -224,9 +224,14 @@ thread_canary_register(PyThreadState *tstate) the reference) and in 'tls->local_thread_canary' (which doesn't). */ assert(Py_REFCNT(canary) == 1); tls->local_thread_canary = canary; - tstate->gilstate_counter++; - /* ^^^ this means 'tstate' will never be automatically freed by - PyGILState_Release() */ + { + /* this extra PyGILState_Ensure() is deliberately never paired + with a PyGILState_Release(), so 'tstate' is never + automatically freed by PyGILState_Release() */ + PyGILState_STATE state = PyGILState_Ensure(); + assert(state == PyGILState_LOCKED); + (void)state; + } //fprintf(stderr, "CANARY: ready, tstate=%p, tls=%p, canary=%p\n", tstate, tls, canary); return; @@ -313,52 +318,16 @@ static void restore_errno_only(void) #endif -/* MESS. We can't use PyThreadState_GET(), because that calls - PyThreadState_Get() which fails an assert if the result is NULL. - - * in Python 2.7 and <= 3.4, the variable _PyThreadState_Current - is directly available, so use that. - - * in Python 3.5, the variable is available too, but it might be - the case that the headers don't define it (this changed in 3.5.1). - In case we're compiling with 3.5.x with x >= 1, we need to - manually define this variable. - - * in Python >= 3.6 there is _PyThreadState_UncheckedGet(). - It was added in 3.5.2 but should never be used in 3.5.x - because it is not available in 3.5.0 or 3.5.1. -*/ -static PyThreadState *get_current_ts(void) -{ -#if PY_VERSION_HEX >= 0x030D0000 - return PyThreadState_GetUnchecked(); -#else - return _PyThreadState_UncheckedGet(); -#endif -} - static PyGILState_STATE gil_ensure(void) { - /* Called at the start of a callback. Replacement for - PyGILState_Ensure(). - */ + /* Called at the start of a callback. */ PyGILState_STATE result; PyThreadState *ts = PyGILState_GetThisThreadState(); //fprintf(stderr, "%p: gil_ensure(), tstate=%p, tls=%p\n", get_cffi_tls(), ts, get_cffi_tls()); if (ts != NULL) { - ts->gilstate_counter++; - if (ts != get_current_ts()) { - /* common case: 'ts' is our non-current thread state and - we have to make it current and acquire the GIL */ - PyEval_RestoreThread(ts); - //fprintf(stderr, "%p: gil_ensure(), tstate=%p MADE CURRENT\n", get_cffi_tls(), ts); - return PyGILState_UNLOCKED; - } - else { - //fprintf(stderr, "%p: gil_ensure(), tstate=%p ALREADY CURRENT\n", get_cffi_tls(), ts); - return PyGILState_LOCKED; - } + /* common case: this thread already has a thread state */ + return PyGILState_Ensure(); } else { /* no thread state here so far. */ @@ -368,8 +337,6 @@ static PyGILState_STATE gil_ensure(void) ts = PyGILState_GetThisThreadState(); //fprintf(stderr, "%p: gil_ensure(), made a new tstate=%p\n", get_cffi_tls(), ts); assert(ts != NULL); - assert(ts == get_current_ts()); - assert(ts->gilstate_counter >= 1); /* Use the ThreadCanary mechanism to keep 'ts' alive until the thread really shuts down */ From 2152dc3095ee656880978abd92ddff97de7fc3f0 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Thu, 30 Jul 2026 10:48:24 -0600 Subject: [PATCH 2/2] Avoid more direct PyThreadState accesses --- src/c/call_python.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/c/call_python.c b/src/c/call_python.c index db9e1253..2503270a 100644 --- a/src/c/call_python.c +++ b/src/c/call_python.c @@ -1,6 +1,6 @@ static PyObject *_current_interp_key(void) { - PyInterpreterState *interp = PyThreadState_GET()->interp; + PyInterpreterState *interp = PyInterpreterState_Get(); return PyInterpreterState_GetDict(interp); /* shared reference */ } @@ -17,13 +17,17 @@ static PyObject *_get_interpstate_dict(void) int err; PyInterpreterState *interp; - tstate = PyThreadState_GET(); +#if PY_VERSION_HEX >= 0x030D0000 + tstate = PyThreadState_GetUnchecked(); +#else + tstate = _PyThreadState_UncheckedGet(); +#endif if (tstate == NULL) { /* no thread state! */ return NULL; } - interp = tstate->interp; + interp = PyThreadState_GetInterpreter(tstate); interpdict = PyInterpreterState_GetDict(interp); /* shared reference */ if (interpdict == NULL) { /* subinterpreter was cleared already, or is being cleared right now,