title: Python 3.14 free-threaded support description: Research summary and implementation plan for cp314t wheels
Issue: #1187
背景
- CPython 3.14 将 GIL 变成可选配置,新的“free-threaded”构建以
cp314ttag 分发(参考 PEP 703 和 PyO3 Guide - Supporting Free-Threaded CPython). - 在 PEP 803 指定的稳定 ABI 落地前,free-threaded 版本没有 limited API,因此我们不能复用 abi3 wheel,必须单独构建
cp314t版本的 wheel,并确保这些 wheel 只在禁用 GIL 的 Python 中安装。 - PyO3 自 0.23 起支持 free-threaded 扩展;我们当前锁定在 0.25.x,因此无需额外升级即可兼容。
必备背景知识(给第一次接触的人)
1. 什么是 GIL?什么是 free-threaded?
- GIL (Global Interpreter Lock) 是 CPython 为了简化内存管理而引入的一把互斥锁。即使你开了多个线程,任意时刻也只有一个线程能执行 Python 字节码。
- free-threaded 构建在 3.13 引入、3.14 进入正式支持:编译 CPython 时加
--disable-gil,让每个线程都能同时执行 Python 代码。 - 在 free-threaded 解释器中运行
python -c "import sys; print(sys._is_gil_enabled())"会输出False,这是最快的自测方法。 - 由于内部实现差异,free-threaded CPython 和普通 CPython 不共享 ABI,所以二进制扩展(Rust/C/C++)必须单独构建。
参考链接:
2. Wheel 名字怎么看?
wheel 文件的命名规则来源于 PEP 427 和 packaging tags 规范。
示例:cocoindex-1.2.3-cp314t-cp314t-manylinux_2_28_x86_64.whl
| 片段 | 含义 |
|---|---|
cocoindex | 包名 |
1.2.3 | 版本号 |
cp314t(第 1 个) | 解释器 tag:CPython 3.14 free-threaded |
cp314t(第 2 个) | ABI tag:同样表示 cp314 free-threaded ABI |
manylinux_2_28_x86_64 | 平台,表示 Linux glibc>=2.28, x86_64 |
pip 根据这些 tag 匹配当前环境,只有同时满足解释器、ABI、平台才会安装成功。所以要支持 free-threaded,必须打出 cp314t 标签的 wheel。
更多资料:
- pypa/packaging-tag wiki
- auditwheel / delvewheel 文档(了解 manylinux)
3. abi3 是什么,为什么和 free-threaded 冲突?
abi3(PEP 384)是 CPython 提供的“稳定 ABI”,只要使用 limited API,就能一个 wheel 兼容多个 Python 版本。- 我们当前用 PyO3 的
abi3-py311特性来生成单个 wheel,兼容 3.11~3.14。 - 但是 free-threaded 的 ABI 完全不同,目前没有 limited API,PyO3 文档也写了“built with abi3 features 会被忽略”。
- 结论:要支持 free-threaded,就需要:
- 在普通 wheel 中继续使用
abi3; - 在 free-threaded wheel 中关闭
abi3,改用pyo3/free-threadedfeature。
- 在普通 wheel 中继续使用
4. PyO3 feature 应该怎么切?
PyO3 提供了多个 feature:
| Feature | 作用 |
|---|---|
abi3-py311 | 启用 limited API,目标 Python >= 3.11 |
extension-module | 让生成的 crate 可被 Python import |
auto-initialize | 在首次调用 PyO3 API 时自动初始化 Python 运行时 |
free-threaded | 切换到 free-threaded ABI,同时禁用 GIL 相关辅助结构 |
实践方案:
- workspace 只保留公共功能(
auto-initialize,chrono,uuid等)。 - 每个 PyO3 crate(
rust/cocoindex,rust/py_utils)定义 feature:[features] default = ["abi3"] abi3 = ["pyo3/abi3-py311"] free-threaded = ["pyo3/free-threaded"] - 构建普通 wheel:
cargo build -p cocoindex --features abi3. - 构建 free-threaded wheel:
cargo build -p cocoindex --no-default-features --features free-threaded.
5. CI 里怎么获取 free-threaded Python?
- GitHub Actions 官方的
actions/setup-python@v5新增了freethreaded: true和3.14t语法。例如: 注意- uses: actions/setup-python@v5 with: python-version: '3.14' allow-prereleases: true freethreaded: trueallow-prereleases: true,因为目前只有3.14.0-beta。 - 但是 manylinux 容器里不能运行
setup-python(没有 systemd)。PyO3 官方 cross 镜像/opt/python/目录已经预装了cp314t,直接指定即可:args: --release --no-default-features -F free-threaded -i /opt/python/cp314-cp314t/bin/python3 --out dist - 验证方法:
python -c "import sys; print(sys.version, sys._is_gil_enabled())",应该输出False。
6. 本地怎么装 free-threaded Python?
步骤(建议使用 pyenv,不然要手动编译):
# 安装依赖 (Debian/Ubuntu)
sudo apt-get update && sudo apt-get install -y build-essential libffi-dev libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev tk-dev libncursesw5-dev xz-utils
# 设置编译参数
export PYTHON_CONFIGURE_OPTS="--disable-gil --enable-shared"
# 安装
pyenv install 3.14-dev
pyenv shell 3.14-dev
# 验证
python -c "import sys; print(sys.version); print('GIL enabled?', sys._is_gil_enabled())"
# 清理环境变量(避免影响其他 Python 安装)
unset PYTHON_CONFIGURE_OPTS如果没有 pyenv,可以使用 Docker(示例:ghcr.io/rust-cross/manylinux_2_28-cross:x86_64)进入容器,再运行 /opt/python/cp314-cp314t/bin/python3。
仓库现状
Cargo.toml的 workspace 依赖为pyo3 = { features = ["abi3-py311", ...] },意味着默认使用 limited API 构建版本无关的 wheel。rust/cocoindex/Cargo.toml曾预留free-threadedfeature(目前被注释),可以用来切换pyo3/free-threaded功能。.github/workflows/release.yml只构建/测试 abi3 wheel;CI 没有安装 free-threaded 解释器、也没有上传cp314t工件。- 文档和贡献者指南尚未提示如何在本地构建或验证 free-threaded 版本。
参考资料
- PyO3 free-threading 提示(重点:禁用 abi3、使用
PyOnceLock等同步原语、sys._is_gil_enabled()自检) - actions/setup-python 高级用法(通过
freethreaded: true或3.14t语法获取 GIL-disabled 解释器) - devguide: 构建 free-threaded Python(本地测试时的
./configure --disable-gil说明)
实现建议
1. Cargo / PyO3 配置
- 把 workspace 级别的
pyo3依赖拆成“基础功能”+“abi3 特性”:Cargo.toml (workspace.dependencies):仅保留通用 feature(auto-initialize,chrono,uuid等)。- 在
rust/cocoindex与rust/py_utils中新增 feature 开关,例如:[features] abi3 = ["pyo3/abi3-py311"] free-threaded = ["pyo3/free-threaded"] default = ["abi3"] - 正常构建继续走
--features abi3(生成当前 abi3 wheel);free-threaded 构建使用--no-default-features --features free-threaded。
- 审核代码里和 GIL 相关的类型:
- 继续使用
Python::detach包裹长时间的 Rust 工作线程。 - 如果未来引入
GILProtected、OnceCell等,需要根据 PyO3 指南切换到Mutex + lock_py_attached或PyOnceLock,避免 free-threaded 模式下死锁。
- 继续使用
2. 构建 & 交付流程
- Linux:
PyO3/maturin-action使用的ghcr.io/rust-cross/manylinux_2_28-cross:*镜像已经预装/opt/python/cp314-cp314t/bin/python3,可以通过args: -i /opt/python/cp314-cp314t/bin/python3生成cp314t-manylinuxwheel。 - macOS / Windows:
actions/setup-python@v5指定python-version: '3.14',allow-prereleases: true,freethreaded: true(或3.14t-dev)。maturin使用-i python(workflow 为 GIL-disabled 解释器)。
- 在
.github/workflows/release.yml中新增build-free-threadedjob:- 复制现有 matrix(linux x86_64/aarch64, macOS arm64/x86_64, Windows x64)。
- 传入
--no-default-features --features free-threaded给maturin(可通过MATURIN_FEATURES或args中加-F free-threaded --no-default-features达成)。 - 工件命名类似
wheels-free-threaded-${{ matrix.platform.os }}-${{ matrix.platform.target }}。
- 发布阶段
releasejob 下载/上传所有 wheel,确保 PyPI 上既有 abi3(cp311-abi3)也有 cp314t 版本。
3. 测试策略
- 保留现有
test-abi3job。 - 新增
test-free-threadedjob:- 下载
wheels-free-threaded-linux-x86_64。 actions/setup-python安装 3.14 free-threaded 解释器。pip install --find-links=./ cocoindex。- 使用
python -c "import cocoindex, sys; assert not sys._is_gil_enabled()"验证 wheel 构建目标正确。
- 下载
- 可选:补充一个
maturin develop -F free-threaded的本地说明,帮助贡献者在 pyenv 中自测:PYTHON_CONFIGURE_OPTS="--disable-gil --enable-shared" pyenv install 3.14-dev pyenv shell 3.14-dev maturin develop --manifest-path rust/cocoindex/Cargo.toml -F free-threaded --interpreter python
4. 发布注意事项
- cp314t wheel 只能服务 Python 3.14 free-threaded;
pip会根据cp314ttag 进行匹配,因此必须保持与平台对应的 manylinux/macos/windows tag。 - 直到 PEP 803 提供稳定 ABI 前,cp314t 每个版本都要重新构建;release 脚本不能再假设“一个 wheel 通吃所有 Python 版本”。
pyproject.toml的classifiers已包含Programming Language :: Python :: 3.14;若后续官方提供Programming Language :: Python :: Implementation :: CPython :: Free Threadedclassifier,可在此处补充。
下一步
- 拆分
pyo3feature 配置,完成free-threadedfeature wiring。 - 依“构建 & 测试”章节修改
.github/workflows/release.yml,并验证本地/CI 可产出cp314t工件。 - 在贡献者指南或 README 中加入“如何本地安装 free-threaded Python 并测试 cocoindex”段落,降低后续维护成本。
给实习生的执行手册
下面是一步步的 checklist,按顺序完成即可,中途不要跳步骤。每完成一项在 PR/issue 里回报一下进度;如果遇到问题,先对照“常见坑”排查,再把尝试过的命令附在评论里。
0. 预备条件
- 本地已经配置好 Rust 1.89+、Python 3.11/3.12(推荐
pyenv)、maturin>=1.7.8。 - 熟悉基本的
git/github流程,能够在 fork 分支上提交 PR。 - 提前准备分支:
git remote -v # 确认 upstream 指向 cocoindex-io/cocoindex git checkout main git pull upstream main git checkout -b feat/free-threaded-wheels
1. 梳理 PyO3 feature
- 打开根目录
Cargo.toml,将[workspace.dependencies].pyo3的 feature 列表删到只剩公共功能:pyo3 = { version = "0.25.1", features = ["auto-initialize", "chrono", "uuid"] }- 修改完成后
rg -n "abi3" -n Cargo.toml,确保 workspace 级别不再直接声明abi3-*。
- 修改完成后
- 在
rust/cocoindex/Cargo.toml、rust/py_utils/Cargo.toml里新增 feature 块(若已有default字段就合并):[features] default = ["abi3"] abi3 = ["pyo3/abi3-py311"] free-threaded = ["pyo3/free-threaded"]- 这两个 crate 是唯一暴露给 Python 的模块,确保都具备一致配置;改完后
rg -n "free-threaded" -n rust -g"*.toml"能看到这两处声明。
- 这两个 crate 是唯一暴露给 Python 的模块,确保都具备一致配置;改完后
- 在项目根目录运行一次:
cargo check -p cocoindexCargo.lock会被刷新。- 如果
cargo check报错 feature 未定义,检查是否在Cargo.toml写错名称(比如free_threadedvsfree-threaded)。
- 如果
- 在
pyproject.toml或 README 的“本地构建”章节补充示例命令:maturin develop --manifest-path rust/cocoindex/Cargo.toml --no-default-features -F free-threaded- 同时注明需要
Python >= 3.14 (free-threaded),避免读者误以为普通 Python 可用。
- 同时注明需要
- 用
git status确认上述文件都已修改,并截图(命令+输出)上传到 issue 以备查证。
2. CI: 构建 cp314t wheel
- 在
.github/workflows/release.yml:- 复制
buildjob,命名build-free-threaded。 - matrix 同步
platform列表(linux x86_64、linux aarch64、macOS arm64、macOS x86_64、windows x64)。 needs仍然是[create-versioned-toml, generate-3p-notices]。
- 复制
- Linux 步骤中把
PyO3/maturin-action的参数改成:args: --release --no-default-features -F free-threaded -i /opt/python/cp314-cp314t/bin/python3 --out dist- 保留
manylinux/container配置。 - 通过
-i /opt/python/...指定解释器,以确保 wheel filename 包含cp314t-manylinux_2_28_x86_64。
- 保留
- 非 Linux 平台:
- 在
PyO3/maturin-action之前新增:- uses: actions/setup-python@v5 with: python-version: '3.14' allow-prereleases: true freethreaded: true args改成--release --no-default-features -F free-threaded -i python --out dist。
- 在
- 上传工件时命名成
wheels-free-threaded-${{ matrix.platform.os }}-${{ matrix.platform.target }}。 - 保存后运行
yamllint .github/workflows/release.yml(如果装了)或至少act -j build-free-threaded检查语法。
3. CI: 测试 cp314t wheel
- 新建 job
test-free-threaded,依赖build-free-threaded。 - 步骤:
- 下载
wheels-free-threaded-linux-x86_64。 actions/setup-python同样装3.14t-dev。pip install --find-links=./ cocoindex。- 运行
python -c "import cocoindex, sys; assert not sys._is_gil_enabled()"。
- 下载
- Release job 要
needs: [build-free-threaded, test-free-threaded],并在下载工件时把wheels-free-threaded-*包含进去:- uses: actions/download-artifact@v4 with: pattern: wheels-free-threaded-* test-free-threaded任务中额外执行:- run: python -m pip debug --verbose - run: python - <<'PY'
import sys, sysconfig print(‘platlib:’, sysconfig.get_path(‘platlib’)) print(‘is_gil_enabled:’, getattr(sys, ‘_is_gil_enabled’, ‘missing’)) PY
这样 reviewer 能看到 pip/解释器信息。
### 4. 本地验证
1. 安装 free-threaded Python(如果没有 pyenv 就在容器内):
```bash
PYTHON_CONFIGURE_OPTS="--disable-gil --enable-shared" pyenv install 3.14-dev
pyenv shell 3.14-dev
- (可选)
python -c "import sys; print(sys._is_gil_enabled())"确认输出False。 - 在项目根目录运行:
maturin develop --manifest-path rust/cocoindex/Cargo.toml --no-default-features -F free-threaded --interpreter python python -c "import cocoindex, sys; assert not sys._is_gil_enabled(); print('ok', sys.version)" - 把成功/失败日志贴到 issue 里,便于 reviewers 知道你测试过。
- 如果需要生成 wheel(非 develop 模式)方便本地检查,运行:
确保文件名类似maturin build --manifest-path rust/cocoindex/Cargo.toml --release --no-default-features -F free-threaded -i python ls target/wheelscocoindex-*.whl且包含cp314t. - 使用
pip uninstall -y cocoindex清理环境,避免下次测试使用旧 wheel。
5. 常见坑
- 忘记
--no-default-features,会导致 free-threaded build 仍然启用 abi3。 actions/setup-python没有加allow-prereleases: true时下载不到 3.14t,需要回头检查 workflow。- Linux build 必须用
/opt/python/cp314-cp314t/bin/python3,不要用系统 Python(否则 wheel 不是cp314t)。 - 如果
pip install时提示 “ABI mismatch”,说明 wheel 没有打出cp314ttag,回到maturin命令检查-i和 feature。 - 忘记给 release job 增加
wheels-free-threaded-*的 download 步骤,会导致发布阶段缺包。 - 如果
python -c "import cocoindex"没有sys._is_gil_enabled属性,说明 interpreter 不是 free-threaded,通常是freethreaded: true漏掉。 - macOS runner 默认拉取 3.14 普通解释器,必须设置
freethreaded: true;否则 wheel tag 会是cp314而不是cp314t。 - Windows 机器上
python命令可能指向 pre-installed 3.11,需要依赖 setup-python 输出${{ steps.py.outputs.python-path }}或直接使用python-version引用的解释器。 - 如果 CI 报
maturin develop找不到Py_LIMITED_API,说明 free-threaded 构建仍启用了 abi3,需要检查defaultfeature。
6. 提交 PR
- 确认
git status只包含预期文件,然后运行:pre-commit run --all-files - 提交并推送:
git add . git commit -m "feat: add cp314t wheel pipeline" git push origin feat/free-threaded-wheels - 在 GitHub 上发起 PR,模板里要注明:
- 修改了哪些文件/工作流;
- 本地是否完成 free-threaded wheel 的
maturin develop验证; - CI 结果截图或链接。
- reviewers 反馈如果需要改动,按照同样的流程(修改 → pre-commit → commit —amend or new commit → push)。
附录:如果你不懂 Rust / PyO3
Rust 是什么?
- 一种系统编程语言,语法类似 C++,主打“内存安全 + 零成本抽象”。
- 我们的核心逻辑(Index Engine)使用 Rust 实现,再通过 PyO3 暴露给 Python。
- 你不需要写 Rust 代码,但需要知道如何运行
cargo(Rust 的包管理和构建工具),类似 Python 里的pip + setup.py。 - 基本命令:
cargo check: 只做语法/类型检查,快;cargo build --release: 编译生成产物;cargo test: 运行测试。
什么是 crate / workspace?
- Rust 项目叫 crate(可理解为 package)。
Cargo.toml是 crate 的配置文件。 - CocoIndex 在仓库根目录有一个 workspace,包含多个子 crate(
rust/cocoindex,rust/py_utils, …)。 Cargo.lock记录了整个 workspace 的依赖版本,类似poetry.lock。
PyO3 是干嘛的?
- PyO3 是一个 Rust crate,用来写 Python 扩展模块(跟 C 扩展/pybind11 类似)。
- 它可以把 Rust 编译成
.so/.pyd,供 Pythonimport cocoindex使用。 - Feature 切换(
abi3,free-threaded)本质上是告诉 PyO3 生成哪种 ABI。
maturin 又是什么?
- maturin 是 PyO3 官方推荐的构建/打包工具,相当于
setuptools+cffi的结合体。 maturin develop:在当前 Python 环境安装一个开发版(带符号链接);maturin build:生成 wheel;maturin upload:上传到 PyPI。
不懂 Rust 但可以做什么?
- 改配置:比如 Cargo.toml、GitHub Actions、pyproject.toml。
- 跑命令:根据文档执行
cargo check,maturin build,python -c ...。 - 看输出:如果命令报错,把完整日志贴在 issue/PR,用于诊断。
入门资料:
- Rust 官方 Book(可读前 3 章了解语法)
- PyO3 User Guide(关注 “Getting Started” + “Building and Distribution”)
- maturin README