From 6238925c22239c746661dd42e3c6de3b185a5eda Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Mon, 16 Feb 2026 23:35:04 +0100 Subject: [PATCH 1/4] Fix free-threaded Python 3.14t compatibility Replace direct ob_refcnt struct access with Py_REFCNT() macro. Guard the im_self mutation optimization with #ifndef Py_GIL_DISABLED since mutating a shared method object is not thread-safe. --- src/ExtensionClass/_ExtensionClass.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ExtensionClass/_ExtensionClass.c b/src/ExtensionClass/_ExtensionClass.c index 91585e5..c3893a6 100644 --- a/src/ExtensionClass/_ExtensionClass.c +++ b/src/ExtensionClass/_ExtensionClass.c @@ -884,17 +884,20 @@ PyECMethod_New_(PyObject *callable, PyObject *inst) if (PyMethod_Check(callable)) { - if (callable->ob_refcnt == 1) +#ifndef Py_GIL_DISABLED + if (Py_REFCNT(callable) == 1) { + /* Optimization: reuse the method object when we have exclusive + ownership. Not safe in the free-threaded build because another + thread could observe the mutation of im_self. */ Py_XDECREF(((PyMethodObject*)callable)->im_self); Py_INCREF(inst); ((PyMethodObject*)callable)->im_self = inst; Py_INCREF(callable); return callable; } - else { - return PyMethod_New(PyMethod_GET_FUNCTION(callable), inst); - } +#endif + return PyMethod_New(PyMethod_GET_FUNCTION(callable), inst); } else { return PyMethod_New(callable, inst); From 8ecb1527478c425ae056662ffa7f24a7fc016ef2 Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Mon, 16 Feb 2026 23:36:32 +0100 Subject: [PATCH 2/4] Add CI and changelog for free-threaded Python 3.14t --- .github/workflows/tests.yml | 8 ++++++++ .meta.toml | 5 +++++ CHANGES.rst | 6 ++++++ tox.ini | 3 ++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7ef6527..db1343f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -99,6 +99,9 @@ jobs: exclude: - os: macos-latest python-version: "pypy-3.11" + include: + - os: ubuntu-latest + python-version: "3.14t" steps: - name: checkout @@ -255,6 +258,9 @@ jobs: exclude: - os: macos-latest python-version: "pypy-3.11" + include: + - os: ubuntu-latest + python-version: "3.14t" steps: - name: checkout @@ -564,6 +570,8 @@ jobs: # Wheels for the no-yet-supported future Python version need to go find dist/ -name "*3.15*" -type f -delete || true find dist/ -name "*cp315*" -type f -delete || true + # Free-threaded wheels are not ready for PyPI yet + find dist/ -name "*cp314t*" -type f -delete || true # For Linux, we only want the manylinux wheels find dist/ -name "*linux_x86_64*" -type f -delete || true diff --git a/.meta.toml b/.meta.toml index f3d6513..fc1618e 100644 --- a/.meta.toml +++ b/.meta.toml @@ -12,6 +12,11 @@ with-future-python = true with-macos = false with-docs = false +[tox] +additional-envlist = [ + "py314t,py314t-pure", +] + [coverage] fail-under = 99.5 diff --git a/CHANGES.rst b/CHANGES.rst index fd2139c..9ce63bb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Change log 6.3 (unreleased) ---------------- +- Fix compilation on free-threaded Python 3.14t: use ``Py_REFCNT()`` macro + instead of direct ``ob_refcnt`` struct access, guard ``im_self`` mutation + optimization with ``#ifndef Py_GIL_DISABLED``. + +- Add CI testing for free-threaded Python 3.14t (Linux). + 6.2 (2025-11-16) ---------------- diff --git a/tox.ini b/tox.ini index 96c432b..41c3b41 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,7 @@ envlist = py315,py315-pure pypy3 coverage + py314t,py314t-pure [testenv] pip_pre = py315: true @@ -60,7 +61,7 @@ deps = commands_pre = commands = check-manifest - check-python-versions --only pyproject.toml,setup.py,tox.ini,.github/workflows/tests.yml + check-python-versions --only pyproject.toml,setup.py,tox.ini python -m build --sdist --no-isolation twine check dist/* From 132ffcfcc63063d21f1ba662740f73a23c9277ec Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 11 Mar 2026 14:37:36 +0100 Subject: [PATCH 3/4] Configure with zope.meta, enable free-threaded python wheels --- .github/workflows/tests.yml | 12 +++++------- .gitignore | 1 + .meta.toml | 3 ++- .pre-commit-config.yaml | 2 +- tox.ini | 1 + 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index db1343f..6938676 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -100,8 +100,8 @@ jobs: - os: macos-latest python-version: "pypy-3.11" include: - - os: ubuntu-latest - python-version: "3.14t" + - python-version: "3.14t" + os: ubuntu-latest steps: - name: checkout @@ -259,8 +259,8 @@ jobs: - os: macos-latest python-version: "pypy-3.11" include: - - os: ubuntu-latest - python-version: "3.14t" + - python-version: "3.14t" + os: ubuntu-latest steps: - name: checkout @@ -567,11 +567,9 @@ jobs: # PyPy wheels shouldn't be uploaded, remove them if present find dist/ -name "*pypy*" -type f -delete || true find dist/ -name "*none-any*" -type f -delete || true - # Wheels for the no-yet-supported future Python version need to go + # Wheels for the not-yet-supported future Python version need to go find dist/ -name "*3.15*" -type f -delete || true find dist/ -name "*cp315*" -type f -delete || true - # Free-threaded wheels are not ready for PyPI yet - find dist/ -name "*cp314t*" -type f -delete || true # For Linux, we only want the manylinux wheels find dist/ -name "*linux_x86_64*" -type f -delete || true diff --git a/.gitignore b/.gitignore index 07212fd..c9557a8 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +28,6 @@ lib64 log/ parts/ pyvenv.cfg +share/ testing.log var/ diff --git a/.meta.toml b/.meta.toml index fc1618e..bfa3739 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,7 +2,7 @@ # https://github.com/zopefoundation/meta/tree/master/src/zope/meta/c-code [meta] template = "c-code" -commit-id = "f62d8bab" +commit-id = "2dc4f53b" [python] with-windows = true @@ -11,6 +11,7 @@ with-sphinx-doctests = false with-future-python = true with-macos = false with-docs = false +with-free-threaded-python = true [tox] additional-envlist = [ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e120a3d..c1e0027 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - id: autopep8 args: [--in-place, --aggressive, --aggressive] - repo: https://github.com/asottile/pyupgrade - rev: v3.21.0 + rev: v3.21.2 hooks: - id: pyupgrade args: [--py310-plus] diff --git a/tox.ini b/tox.ini index 41c3b41..e2c16ee 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ envlist = py313,py313-pure py314,py314-pure py315,py315-pure + py314t,py314t-pure pypy3 coverage py314t,py314t-pure From 9bbd77b7aff9cdeface5e229c88538d597a5761f Mon Sep 17 00:00:00 2001 From: "Jens W. Klein" Date: Wed, 11 Mar 2026 16:56:10 +0100 Subject: [PATCH 4/4] fix tox dup envlist --- .meta.toml | 5 ----- tox.ini | 1 - 2 files changed, 6 deletions(-) diff --git a/.meta.toml b/.meta.toml index bfa3739..7ecdd79 100644 --- a/.meta.toml +++ b/.meta.toml @@ -13,11 +13,6 @@ with-macos = false with-docs = false with-free-threaded-python = true -[tox] -additional-envlist = [ - "py314t,py314t-pure", -] - [coverage] fail-under = 99.5 diff --git a/tox.ini b/tox.ini index e2c16ee..6cd51fa 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,6 @@ envlist = py314t,py314t-pure pypy3 coverage - py314t,py314t-pure [testenv] pip_pre = py315: true