Skip to content

CI: try free-threaded Python 3.14t (DRAFT — documenting failures) - #15104

Draft
priya-sundaram-dev wants to merge 5 commits into
TheAlgorithms:masterfrom
priya-sundaram-dev:python-3.14t
Draft

CI: try free-threaded Python 3.14t (DRAFT — documenting failures)#15104
priya-sundaram-dev wants to merge 5 commits into
TheAlgorithms:masterfrom
priya-sundaram-dev:python-3.14t

Conversation

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor

Part of the maintenance experiments requested in #15081.

What this does

Switches the interpreter pinned in every CI workflow from 3.14 to the free-threaded build 3.14t:

  • build.yml (full pytest suite — this is the interesting one; it already runs with pytest-run-parallel --parallel-threads=auto, so the free-threaded interpreter actually exercises concurrency)
  • project_euler.yml, sphinx.yml, directory_writer.yml

Nothing in pyproject.toml changes: 3.14t is the same language version as 3.14, just the free-threaded ABI (cp314t wheels), so requires-python and the classifiers stay correct.

Why it's a DRAFT

The point is to document what breaks under free-threading, not to merge. Expected friction:

  • C-extension deps that don't yet ship cp314t wheels will fail to install under uv sync (or fall back to a slow pure-Python path).
  • Tests that rely on non-thread-safe global state may become flaky once --parallel-threads=auto runs them on a GIL-free interpreter.

I'll annotate this PR with the specific failures once CI reports them, so we have a written record of the gap. Keeping it in DRAFT so it can't merge before it's ready.

Follow-up (extra credit from #15081)

Once we see the CI picture, I'll open a separate small PR adding a side-by-side free-threaded variant of a CPU-bound fractal generator so visitors can study the speedup — kept separate to stay reviewable one change at a time.

@algorithms-keeper

Copy link
Copy Markdown

Closing this pull request as invalid

@priya-sundaram-dev, this pull request is being closed as none of the checkboxes have been marked. It is important that you go through the checklist and mark the ones relevant to this pull request. Please read the Contributing guidelines.

If you're facing any problem on how to mark a checkbox, please read the following instructions:

  • Read a point one at a time and think if it is relevant to the pull request or not.
  • If it is, then mark it by putting a x between the square bracket like so: [x]

NOTE: Only [x] is supported so if you have put any other letter or symbol between the brackets, that will be marked as invalid. If that is the case then please open a new pull request with the appropriate changes.

@cclauss

cclauss commented Aug 28, 2026

Copy link
Copy Markdown
Member

@cclauss cclauss removed the invalid label Aug 28, 2026
@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

Done — bumped pandas>=2.2.3pandas>=2.3.3 in pyproject.toml (pushed 292f6ef).

This is actually the right pin for this PR specifically: 2.3.3 is the first pandas release that's generally compatible with Python 3.14, and it's where the free-threaded (3.14t) wheels start landing — so it's exactly what the 3.14t CI job needs to pip install a prebuilt wheel instead of compiling pandas from source on every run. Note the free-threaded wheels are still a subset of platforms in 2.3.3, so if a specific 3.14t job can't find a wheel we may see a build-from-source there; I'll watch the CI and annotate anything that falls out.

(Leaving the checklist as-is intentionally — this stays a DRAFT until the 3.14t results are worth reading; the algorithms-keeper close-on-empty-checkbox is just its usual strictness.)

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

CI finding (3.14t): blocked at dependency install by opencv-python, not by our code

First 3.14t run is in. All four failing jobs (build, build_docs, project-euler, validate-solutions) die at the uv sync step, before a single test runs — same root cause on both x86_64 and aarch64:

  • opencv-python 4.11.0.86 has no free-threaded (cp314t) wheel, so uv falls back to building it from the sdist, and the source build fails at CMake:
    An error occurred while building with CMake.
    hint: `opencv-python` (v4.11.0.86) was included because `thealgorithms-python` depends on `opencv-python`
    
  • ruff passes (no interpreter needed); deploy_docs correctly skips.

So the free-threaded experiment isn't surfacing any real threading issues in our algorithms yet — we're blocked one layer down, waiting on an upstream binary wheel. Two ways forward, happy to do whichever you prefer:

  1. Wait — keep this DRAFT open and re-run once opencv ships a cp314t wheel (tracking upstream).
  2. Gate opencv out of the 3.14t job — mark opencv-python optional / behind an environment marker so the ft job installs everything else and actually exercises pytest-run-parallel on the pure-Python algorithms now; restore full deps once the wheel lands.

I lean toward (2) so the job earns its keep in the meantime, but it's your call. Leaving as DRAFT.

@cclauss

cclauss commented Aug 28, 2026

Copy link
Copy Markdown
Member

I also lean towards:
2. Gate OpenCV out of the 3.14t job while that team works on:

Which of our algorithms really depend on OpenCV?

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

Good question — I mapped it. Exactly 20 files import cv2, all confined to three directories:

  • digital_image_processing/16 files (filters, rotation, resize, dithering, edge_detection/canny, sepia, convert_to_negative, histogram_stretch, plus test_digital_image_processing.py)
  • computer_vision/3 files (harris_corner.py, flip_augmentation.py, mosaic_augmentation.py)
  • data_compression/1 file (peak_signal_to_noise_ratio.py)

Nothing else in the repo touches OpenCV, so the blast radius of gating it out is small and well-contained.

Concrete plan for the 3.14t job, so it earns its keep now instead of waiting on opencv/opencv#27933:

  1. Move opencv-python into an optional dependency-group (e.g. [dependency-groups] cv) rather than the core deps.
  2. In the free-threaded job only, run uv sync without that group so everything else installs and pytest-run-parallel actually exercises the pure-Python algorithms.
  3. Add a --ignore for those three dirs in the ft pytest invocation so collection doesn't error on the missing import.
  4. The normal (non-ft) CI keeps installing the cv group, so nothing changes for 3.13/3.14 coverage.

When the cp314t wheel lands, we just drop the --ignore and re-include the group — no other churn. Want me to push that onto this DRAFT so you can see the ft job go green on the pure-Python side?

@cclauss

cclauss commented Aug 28, 2026

Copy link
Copy Markdown
Member

Perfect plan. Please proceed, and let's see where it takes us.

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

Pushed (0d37549). Summary of what landed:

  1. opencv-python → optional [dependency-groups] cv in pyproject.toml, out of core deps. uv sync --group=test (build), --group=euler-validate --group=test (project-euler / validate-solutions) and --group=docs (build_docs) now all install without touching opencv — so every job should clear the uv sync step it was dying on.
  2. build.yml (ft job only) skips the cv2 importers so collection doesn't error on the missing module.

One refinement vs. my earlier note: instead of ignoring all three dirs wholesale, I skipped per file where it's cheap, so we keep pure-Python coverage:

  • computer_vision/ — ignore only flip_augmentation.py, harris_corner.py, mosaic_augmentation.py; the pure-Python algos (haralick, horn_schunck, pooling, mean_threshold, intensity_based_segmentation) still run under parallel threads.
  • data_compression/ — ignore only peak_signal_to_noise_ratio.py; huffman/lz77/lempel_ziv/BWT/RLE all still run.
  • digital_image_processing/ — skipped as a tree; 16 of its ~20 files are cv2-based, so the leftover PIL-only files weren't worth threading around the collection error.

No change to the non-ft path: when a cp314t wheel lands we drop the --ignores and re-fold cv into core deps in one commit. Leaving as DRAFT so we can read the ft run.

@cclauss cclauss left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sklearn seems to have GIL troubles.

- uses: actions/setup-python@v7
with:
python-version: 3.14
python-version: 3.14t

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this change in all workflows that use actions/setup-python.

Suggested change
python-version: 3.14t
python-version-file: 'pyproject.toml'

This will make the diffs smaller in PRs like this one and:

@algorithms-keeper algorithms-keeper Bot added the awaiting changes A maintainer has requested changes to this PR label Aug 28, 2026
@cclauss

cclauss commented Aug 28, 2026

Copy link
Copy Markdown
Member

Should we add https://github.com/astral-sh/uv-pre-commit to pre-commit? Unlike many other repos, we can be aggressive about updating dependencies because we always run on the latest and greatest version of CPython.

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

Good news + one more layer, now pushed (ea7b3be7):

opencv gating worked. build_docs, project-euler and validate-solutions all go green now — they were dying at uv sync before, and now install cleanly without opencv. ✅

The build (ft) job now gets all the way to test collection — it collects 2046 items and schedules 116 to run in parallel — then aborts:

_pytest.outcomes.Exit: GIL was dynamically re-enabled during test collection to load module 'sklearn.__check_build._check_build'.

So it's the next wheel-gap down: sklearn (and xgboost, etc.) don't yet ship the Py_mod_gil slot, so importing them re-enables the GIL at runtime, which pytest-run-parallel treats as fatal under a free-threaded interpreter. That's an upstream compiled-wheel gap, not our code — 12 files import sklearn.

Fix pushed: add --ignore-gil-enabled (the flag the runtime itself suggests). The suite then runs to completion, and pytest-run-parallel still reports which tests are thread-unsafe (last run: 1930 of 2046 not run in parallel). That gives us the real free-threaded picture without whack-a-mole --ignore-ing every sklearn importer. I left a note to drop the flag once the scientific stack ships free-threaded wheels.

Re: uv-pre-commit — yes, I think it's a good fit here. Since CI always runs latest CPython, keeping uv.lock and the uv version fresh via a pre-commit hook fits the "aggressive about updates" posture, and it'd have caught the uv.lock staleness we've been relocking by hand. Happy to add astral-sh/uv-pre-commit (lock + uv-lock/uv-export hooks) in a follow-up if you want it scoped to this PR or split out.

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

Update: with --ignore-gil-enabled the ft suite now runs end to end — 2365 passed, 1 failed (down from aborting at collection). The single remaining failure isn't a free-threading issue at all:

physics/in_static_equilibrium.py::...in_static_equilibrium
UNEXPECTED EXCEPTION: ValueError('Both input arrays must be (arrays of) 3-dimensional vectors, but they are 2 and 2 dimensional instead.')

That's the NumPy 2-D cross product removal. master only escapes it because its lockfile pins numpy==2.2.5; the ft job resolves numpy==2.5.2, where 2-D cross() is gone. So it's a latent NumPy-compat break the ft matrix happened to surface first. I opened #15110 to fix it (compute the scalar z-moment directly — identical behaviour, version-independent). Once that merges I'll rebase this branch and the ft job should be fully green.

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

Rebased on master now that #15110 is merged. The 2-D cross-product failure in physics/in_static_equilibrium.py is gone from the branch, so the ft job should run the full suite to completion. Once this CI settles I'll post the clean pytest-run-parallel numbers (which modules are/aren't thread-safe) as the summary this DRAFT is meant to document.

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

CI settled — clean free-threaded run ✅

After the rebase on master (with #15110 merged), the build job on Python 3.14t now runs the whole suite to completion:

Collected 116 items to run in parallel
========================== pytest-run-parallel report ==========================
1930 tests were not run in parallel because of use of thread-unsafe functionality
================= 2366 passed, 2 warnings in 151.87s =================

So the picture on free-threaded CPython today:

  • 2366 passed, 0 failed — nothing in the repo is broken under free-threading once the numpy-2.x fix (Fix in_static_equilibrium for NumPy 2.x (2-D cross product removed) #15110) and the two flags below are in place.
  • 116 tests actually ran in parallel (--parallel-threads=auto, 8 iterations) and were clean — no races surfaced in the pure-Python algorithms that got scheduled concurrently.
  • 1930 tests were not run in parallel because pytest-run-parallel detected thread-unsafe functionality and fell back to serial. That's the plugin being conservative, not a failure — set PYTEST_RUN_PARALLEL_VERBOSE=1 to get the per-test list.

Two knobs are load-bearing to get here, both documented inline in the workflow:

  1. --ignore-gil-enabled — several sci-py extension imports (sklearn's __check_build, etc.) don't yet carry a Py_mod_gil slot, so importing them re-enables the GIL. Without this flag pytest-run-parallel aborts the whole session at collection. With it, those modules just run GIL-enabled while the rest stays free-threaded.
  2. The opencv/tensorflow/--ignore set — heavy native deps that either don't build on 3.14t or pull the GIL back; gated out of the ft lane only (they still run in the normal build matrix).

Recommendation: keep this as a non-blocking / informational lane for now (allow-failure), not a required check. It's genuinely useful as an early-warning tripwire — the day a pure-Python algorithm here develops a real data race, the 116-in-parallel subset is where it'll show up first — but the 1930 serial fallbacks are upstream-dependency limitations we can't fix from this repo. As those ecosystem packages ship Py_mod_gil slots, the parallel subset grows on its own.

Happy to wire it into build.yml as an allow-failure matrix entry instead of a separate draft workflow if you'd prefer it lives alongside the main build. Your call on scope.

@cclauss

cclauss commented Aug 28, 2026

Copy link
Copy Markdown
Member

Please rebase to resolve the git conflicts.

priya-sundaram-dev and others added 5 commits August 28, 2026 12:57
Change the interpreter used across CI workflows from 3.14 to the
free-threaded build 3.14t to surface which dependencies and tests are
not yet free-threading compatible. Opened as DRAFT for documentation
purposes per maintainer request (TheAlgorithms#15081).
opencv-python has no cp314t wheel yet and fails to build from source under
free-threaded 3.14t (CMake), blocking uv sync for every job. Move it to an
optional [dependency-groups] cv group so the ft CI installs everything else
and runs pytest-run-parallel on the pure-Python algorithms. Skip the 20 files
that import cv2 (computer_vision augmentations, data_compression PSNR, and the
mostly-cv2 digital_image_processing/ tree). Re-fold once a cp314t wheel ships
(upstream: opencv/opencv#27933).
@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

Rebased on master — conflicts resolved. They were all in the setup-python steps where #15111 switched to python-version-file: pyproject.toml; since this draft's purpose is exercising the free-threaded build, I kept python-version: 3.14t in those four workflows. No conflicts remain and the branch is mergeable again. Letting CI re-run.

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor Author

After the rebase, CI is red for two independent reasons, both worth documenting here since that's this draft's purpose:

  1. pre-commit.ci — not a code issue. The uv-lock hook can't reach PyPI in pre-commit.ci's no-network sandbox, so it errors on every PR. That's a regression from my ci: single-source the Python version via pyproject.toml + add uv-lock pre-commit hook #15111; I've opened ci: skip uv-lock on pre-commit.ci (no network access there) #15112 to skip: [uv-lock] on pre-commit.ci. Once that merges and I rebase, this check clears.

  2. build (3.14t) — a genuine free-threaded finding. The suite now runs to completion (1 failed, 2365 passed). The single failure is new on master:

    FAILED linear_algebra/matrix_inversion.py::...invert_matrix
    [thread-unsafe]: is a doctest (pytest-run-parallel does not support doctests)
    

    This isn't a real thread-safety bug — pytest-run-parallel simply cannot execute doctests under --parallel-threads, so any newly-added doctest module surfaces here. It's exactly the kind of tooling gap this draft is meant to catalogue: the ft lane needs --doctest-modules excluded from parallel collection (or doctests run in a separate serial pass). I'll fold that into the recommendation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting changes A maintainer has requested changes to this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants