From e317b620e9d925049baff0ab29d24bb812c74308 Mon Sep 17 00:00:00 2001 From: Matt Davis <6775756+nitzmahone@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:04:32 -0700 Subject: [PATCH] hard disable Py_LIMITED_API for free-threaded builds (#125) * all Linux x86_64 tests pass on 3.13t_x86_64 as of 3.13.0rc2 * update some broken test assumptions around limited API support and eager global GC * add non-artifact CI job for 3.13t --- .github/workflows/ci.yaml | 6 +++++- src/cffi/recompiler.py | 8 ++++---- src/cffi/setuptools_ext.py | 8 ++++++++ testing/cffi0/test_zintegration.py | 4 +++- testing/cffi1/test_ffi_obj.py | 7 ++++--- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c051bb9d..614f1601 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -83,6 +83,9 @@ jobs: omit: ${{ env.skip_ci_redundant_jobs }} - spec: cp313-manylinux_x86_64 + + - spec: cp313t-manylinux_x86_64 + skip_artifact_upload: 'true' - spec: cp38-manylinux_i686 omit: ${{ env.skip_ci_redundant_jobs }} @@ -290,6 +293,7 @@ jobs: CIBW_MUSLLINUX_I686_IMAGE: ${{ matrix.musllinux_img || 'musllinux_1_1' }} CIBW_MUSLLINUX_AARCH64_IMAGE: ${{ matrix.musllinux_img || 'musllinux_1_1' }} CIBW_PRERELEASE_PYTHONS: 'True' + CIBW_FREE_THREADED_SUPPORT: 'True' CIBW_TEST_REQUIRES: pytest setuptools # 3.12+ no longer includes distutils, just always ensure setuptools is present CIBW_TEST_COMMAND: PYTHONUNBUFFERED=1 python -m pytest ${{ matrix.test_args || '{project}' }} # default to test all run: | @@ -311,7 +315,7 @@ jobs: name: ${{ steps.build.outputs.artifact_name }} path: dist/*.whl if-no-files-found: error - if: ${{ env.skip_artifact_upload != 'true' }} + if: ${{ (matrix.skip_artifact_upload != 'true') && (env.skip_artifact_upload != 'true') }} make_macos_matrix: runs-on: ubuntu-22.04 diff --git a/src/cffi/recompiler.py b/src/cffi/recompiler.py index 738d21e1..7734a348 100644 --- a/src/cffi/recompiler.py +++ b/src/cffi/recompiler.py @@ -1,4 +1,4 @@ -import os, sys, io +import io, os, sys, sysconfig from . import ffiplatform, model from .error import VerificationError from .cffi_opcode import * @@ -7,9 +7,9 @@ VERSION_EMBEDDED = 0x2701 VERSION_CHAR16CHAR32 = 0x2801 -USE_LIMITED_API = (sys.platform != 'win32' or sys.version_info < (3, 0) or - sys.version_info >= (3, 5)) - +USE_LIMITED_API = ((sys.platform != 'win32' or sys.version_info < (3, 0) or + sys.version_info >= (3, 5)) and + not sysconfig.get_config_var("Py_GIL_DISABLED")) # free-threaded doesn't yet support limited API class GlobalExpr: def __init__(self, name, address, type_op, size=0, check_value=0): diff --git a/src/cffi/setuptools_ext.py b/src/cffi/setuptools_ext.py index d1e850c5..5cdd246f 100644 --- a/src/cffi/setuptools_ext.py +++ b/src/cffi/setuptools_ext.py @@ -1,5 +1,6 @@ import os import sys +import sysconfig try: basestring @@ -87,6 +88,7 @@ def _set_py_limited_api(Extension, kwds): Recently (2020) we started shipping only >= 3.5 wheels, though. So we'll give it another try and set py_limited_api on Windows >= 3.5. """ + from cffi._shimmed_dist_utils import log from cffi import recompiler if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount') @@ -102,6 +104,12 @@ def _set_py_limited_api(Extension, kwds): # warning. kwds['py_limited_api'] = True + if sysconfig.get_config_var("Py_GIL_DISABLED"): + if kwds.get('py_limited_api'): + log.info("Ignoring py_limited_api=True for free-threaded build.") + + kwds['py_limited_api'] = False + if kwds.get('py_limited_api') is False: # avoid setting Py_LIMITED_API if py_limited_api=False # which _cffi_include.h does unless _CFFI_NO_LIMITED_API is defined diff --git a/testing/cffi0/test_zintegration.py b/testing/cffi0/test_zintegration.py index 9fbbd91c..7612ba00 100644 --- a/testing/cffi0/test_zintegration.py +++ b/testing/cffi0/test_zintegration.py @@ -1,5 +1,6 @@ import py, os, sys, shutil import subprocess +import sysconfig import textwrap from testing.udir import udir import pytest @@ -179,7 +180,8 @@ def test_set_py_limited_api(self): except ImportError as e: pytest.skip(str(e)) orig_version = setuptools.__version__ - expecting_limited_api = not hasattr(sys, 'gettotalrefcount') + # free-threaded Python does not yet support limited API + expecting_limited_api = not hasattr(sys, 'gettotalrefcount') and not sysconfig.get_config_var("Py_GIL_DISABLED") try: setuptools.__version__ = '26.0.0' from setuptools import Extension diff --git a/testing/cffi1/test_ffi_obj.py b/testing/cffi1/test_ffi_obj.py index 9b1532b8..7a7fc4f2 100644 --- a/testing/cffi1/test_ffi_obj.py +++ b/testing/cffi1/test_ffi_obj.py @@ -35,8 +35,9 @@ def test_ffi_cache_type(): def test_ffi_type_not_immortal(): import weakref, gc ffi = _cffi1_backend.FFI() - t1 = ffi.typeof("int **") - t2 = ffi.typeof("int *") + # this test can fail on free-threaded builds lazier GC if the type was used by another test + t1 = ffi.typeof("unsigned short int **") + t2 = ffi.typeof("unsigned short int *") w1 = weakref.ref(t1) w2 = weakref.ref(t2) del t1, ffi @@ -44,7 +45,7 @@ def test_ffi_type_not_immortal(): assert w1() is None assert w2() is t2 ffi = _cffi1_backend.FFI() - assert ffi.typeof(ffi.new("int **")[0]) is t2 + assert ffi.typeof(ffi.new("unsigned short int **")[0]) is t2 # ffi = _cffi1_backend.FFI() t1 = ffi.typeof("int ***")