From 22e47e38bba354f85a55c875688bc447fd618bcc Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 10 May 2026 14:36:29 +0300 Subject: [PATCH 1/5] Simplify setting PYTHON_GIL --- .github/workflows/test.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d90cc805a9b..154455e1a76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,10 +51,6 @@ jobs: include: - { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } - { python-version: "3.11", PYTHONOPTIMIZE: 2 } - # Free-threaded - - { python-version: "3.15t", disable-gil: true } - - { python-version: "3.14t", disable-gil: true } - - { python-version: "3.13t", disable-gil: true } # Intel - { os: "macos-26-intel", python-version: "3.10" } exclude: @@ -79,7 +75,7 @@ jobs: "pyproject.toml" - name: Set PYTHON_GIL - if: "${{ matrix.disable-gil }}" + if: endsWith(matrix.python-version, 't') run: | echo "PYTHON_GIL=0" >> $GITHUB_ENV From 0582f43bad6e4b3e2f51dddd6ef187108aaa8f07 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 10 May 2026 14:36:44 +0300 Subject: [PATCH 2/5] No longer test experimental 3.13t --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 154455e1a76..dc9c33743b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,6 @@ jobs: "3.15", "3.14t", "3.14", - "3.13t", "3.13", "3.12", "3.11", From 4dc442fb012f821678b9b64dfa6ecc56e9bc57bd Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 12 May 2026 20:48:26 +0300 Subject: [PATCH 3/5] Don't force PYTHON_GIL=0, instead fail if anything re-enables --- .ci/install.sh | 4 ++-- .github/workflows/test.yml | 5 ----- Tests/conftest.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index 9553eb8f442..cc104c45f9b 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -39,8 +39,8 @@ python3 -m pip install --only-binary=:all: pyarrow || true # PyQt6 doesn't support PyPy3 if [[ $GHA_PYTHON_VERSION == 3.* ]]; then sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0 - # TODO Update condition when pyqt6 supports free-threading - if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi + # pyqt6 doesn't yet support free-threading; only install if a wheel is available + python3 -m pip install --only-binary=:all: pyqt6 || true fi # webp diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc9c33743b7..362412e9474 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,11 +73,6 @@ jobs: ".ci/*.sh" "pyproject.toml" - - name: Set PYTHON_GIL - if: endsWith(matrix.python-version, 't') - run: | - echo "PYTHON_GIL=0" >> $GITHUB_ENV - - name: Build system information run: python3 .github/workflows/system-info.py diff --git a/Tests/conftest.py b/Tests/conftest.py index e00d1f019f5..8d12a0e14f3 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -1,9 +1,17 @@ from __future__ import annotations import io +import sys +import sysconfig import pytest +FREE_THREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) + +gil_enabled_at_start = True +if FREE_THREADED_BUILD: + gil_enabled_at_start = sys._is_gil_enabled() # type: ignore[attr-defined] + def pytest_report_header(config: pytest.Config) -> str: try: @@ -16,6 +24,27 @@ def pytest_report_header(config: pytest.Config) -> str: return f"pytest_report_header failed: {e}" +def pytest_terminal_summary( + terminalreporter: pytest.TerminalReporter, exitstatus: int, config: pytest.Config +) -> None: + if ( + FREE_THREADED_BUILD + and not gil_enabled_at_start + and sys._is_gil_enabled() # type: ignore[attr-defined] + ): + tr = terminalreporter + tr.ensure_newline() + tr.section("GIL re-enabled", sep="=", red=True, bold=True) + tr.line("The GIL was re-enabled at runtime during the tests.") + tr.line("This can happen with no test failures if the RuntimeWarning") + tr.line("raised by Python when this happens is filtered by a test.") + tr.line("") + tr.line("Please ensure all new C modules declare support for running") + tr.line("without the GIL. Any new tests that intentionally imports") + tr.line("code that re-enables the GIL should do so in a subprocess.") + pytest.exit("GIL re-enabled during tests", returncode=1) + + def pytest_configure(config: pytest.Config) -> None: config.addinivalue_line( "markers", From e9855d1705c5a1185256597eac9030af98e904a5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 13 May 2026 13:30:01 +0300 Subject: [PATCH 4/5] Remove unused params Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/conftest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/conftest.py b/Tests/conftest.py index 8d12a0e14f3..16d4c04d40c 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -24,9 +24,7 @@ def pytest_report_header(config: pytest.Config) -> str: return f"pytest_report_header failed: {e}" -def pytest_terminal_summary( - terminalreporter: pytest.TerminalReporter, exitstatus: int, config: pytest.Config -) -> None: +def pytest_terminal_summary(terminalreporter: pytest.TerminalReporter) -> None: if ( FREE_THREADED_BUILD and not gil_enabled_at_start From 3f74d08263af36fb131ddda070c9ce19e8cac8b7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 14 May 2026 14:05:55 +0300 Subject: [PATCH 5/5] Remove default sep="=" Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/conftest.py b/Tests/conftest.py index 16d4c04d40c..1f32bbedff3 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -32,7 +32,7 @@ def pytest_terminal_summary(terminalreporter: pytest.TerminalReporter) -> None: ): tr = terminalreporter tr.ensure_newline() - tr.section("GIL re-enabled", sep="=", red=True, bold=True) + tr.section("GIL re-enabled", red=True, bold=True) tr.line("The GIL was re-enabled at runtime during the tests.") tr.line("This can happen with no test failures if the RuntimeWarning") tr.line("raised by Python when this happens is filtered by a test.")