From 56a7876b8cd3b2d8148233a90e0121d74cd83852 Mon Sep 17 00:00:00 2001 From: Matt Davis <6775756+nitzmahone@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:25:05 -0700 Subject: [PATCH 1/2] Use PyGILState_Ensure to avoid accessing CPython internals (#269) (#270) * Use PyGilState_Ensure to avoid accessing CPython internals * Avoid more direct PyThreadState accesses (cherry picked from commit 8e5b6a5106552848b29dc4c8784f5c4f13163e1f) Co-authored-by: Nathan Goldbaum --- src/c/call_python.c | 10 ++++--- src/c/misc_thread_common.h | 55 ++++++++------------------------------ 2 files changed, 18 insertions(+), 47 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, 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 fd33e7700f0ebafe6f30bd5053f13327221b77a2 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 3 Aug 2026 10:26:56 -0700 Subject: [PATCH 2/2] New release 2.1.1 --- doc/source/conf.py | 2 +- doc/source/whatsnew.rst | 10 +++++++++- pyproject.toml | 2 +- src/c/_cffi_backend.c | 2 +- src/c/test_c.py | 2 +- src/cffi/__init__.py | 4 ++-- src/cffi/_embedding.h | 2 +- 7 files changed, 16 insertions(+), 8 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 47e78808..2a85c434 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -45,7 +45,7 @@ # The short X.Y version. version = '2.1' # The full version, including alpha/beta/rc tags. -release = '2.1.0' +release = '2.1.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst index 209e21cd..194148ba 100644 --- a/doc/source/whatsnew.rst +++ b/doc/source/whatsnew.rst @@ -2,8 +2,16 @@ What's New ====================== +v2.1.1 +====== + +* Minimize internal Python API usage for interpreter and thread state sampling where possible. + Avoids breaking ABI change in Python >= 3.15.0b4. (`#269`_) + +.. _`#269`: https://github.com/python-cffi/cffi/pull/269 + v2.1.0 -=========== +====== * Added the ``cffi-gen-src`` command-line tool (also invocable as ``python -m cffi.gen_src``), which generates CFFI C extension diff --git a/pyproject.toml b/pyproject.toml index 32dea617..5cac0138 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "cffi" -version = "2.1.0" +version = "2.1.1" dependencies = [ "pycparser; implementation_name != 'PyPy'", ] diff --git a/src/c/_cffi_backend.c b/src/c/_cffi_backend.c index a6ddaeda..4b8e3ed7 100644 --- a/src/c/_cffi_backend.c +++ b/src/c/_cffi_backend.c @@ -2,7 +2,7 @@ #include #include "structmember.h" #include "misc_thread_common.h" -#define CFFI_VERSION "2.1.0" +#define CFFI_VERSION "2.1.1" #ifdef MS_WIN32 #include diff --git a/src/c/test_c.py b/src/c/test_c.py index 945cfcf2..fe82c51b 100644 --- a/src/c/test_c.py +++ b/src/c/test_c.py @@ -66,7 +66,7 @@ def _capture_unraisable_hook(ur_args): # ____________________________________________________________ import sys -assert __version__ == "2.1.0", ("This test_c.py file is for testing a version" +assert __version__ == "2.1.1", ("This test_c.py file is for testing a version" " of cffi that differs from the one that we" " get from 'import _cffi_backend'") if sys.version_info < (3,): diff --git a/src/cffi/__init__.py b/src/cffi/__init__.py index da1875cc..93dcdb3a 100644 --- a/src/cffi/__init__.py +++ b/src/cffi/__init__.py @@ -5,8 +5,8 @@ from .error import CDefError, FFIError, VerificationError, VerificationMissing from .error import PkgConfigError -__version__ = "2.1.0" -__version_info__ = (2, 1, 0) +__version__ = "2.1.1" +__version_info__ = (2, 1, 1) # The verifier module file names are based on the CRC32 of a string that # contains the following version number. It may be older than __version__ diff --git a/src/cffi/_embedding.h b/src/cffi/_embedding.h index 12ff017f..ba6e79b9 100644 --- a/src/cffi/_embedding.h +++ b/src/cffi/_embedding.h @@ -222,7 +222,7 @@ static int _cffi_initialize_python(void) if (f != NULL && f != Py_None) { PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME - "\ncompiled with cffi version: 2.1.0" + "\ncompiled with cffi version: 2.1.1" "\n_cffi_backend module: ", f); modules = PyImport_GetModuleDict(); mod = PyDict_GetItemString(modules, "_cffi_backend");