feat: 新增 Python 3.14t 自由线程镜像 (#6434)

* fix(resource): select free-threaded extension ABI

* chore(deps): require moviepilot-rust 0.2.9

* perf: add free-threaded runtime comparison

* feat: add free-threaded runtime profile

* chore(deps): require moviepilot-rust 0.3.0

* test: isolate system endpoint import graph

* fix(docker): preserve runtime profile during recovery

* feat(runtime): expose active GIL state

* feat(plugin): log GIL fallback attribution

* test: refresh runtime observability dependency baseline

* fix(plugin): validate the active uv runtime profile

* test(runtime): expand free-threaded benchmark evidence

* docs(runtime): define v3t governance gates

* test(runtime): separate rust benchmark modes

* docs: sync free-threaded architecture baseline

* perf: add PostgreSQL driver comparison

* docs(runtime): record final free-threaded evidence

* fix(runtime): converge dual-profile dependency verification

* fix(runtime): scope Python 3.14 warning filter

* fix(runtime): match actual oss2 syntax warning

* docs(runtime): refresh free-threaded benchmark evidence

* test(architecture): refresh runtime dependency baseline

* ci: skip unused Trivy Java database

* build: exclude local verification artifacts

* docs(runtime): refresh PostgreSQL driver benchmarks

* docs(runtime): record plugin restore acceptance

* docs(runtime): record amd64 candidate acceptance

* ci: pin beta image publisher action

* test(architecture): merge runtime dependency baseline

* feat(runtime): expose Python GIL status

* docs(runtime): document Python runtime status fields
This commit is contained in:
InfinityPacer
2026-08-24 17:48:14 +08:00
committed by GitHub
parent 88dce4ca8e
commit 326b5cf3ad
63 changed files with 5294 additions and 253 deletions
+3 -2
View File
@@ -653,8 +653,8 @@ flowchart LR
| 指标 | 当前值 |
|---|---:|
| Python 模块 | 810 |
| 内部导入边 | 6,560 |
| Python 模块 | 811 |
| 内部导入边 | 6,572 |
| 非平凡 SCC | 1(仅隔离的 TMDB 移植包) |
| Module Contract V2 spec | 212(其中 211 个进入 `run_module` 观察面) |
| Event Contract | 53 |
@@ -705,6 +705,7 @@ flowchart LR
| [`docs/rules/10-data-and-persistent.md`](rules/10-data-and-persistent.md) | 数据模型、迁移与缓存规范 |
| [`docs/subscribe-lifecycle.md`](subscribe-lifecycle.md) | 订阅生命周期详解 |
| [`docs/mcp-api.md`](mcp-api.md) | MCP 工具端点说明 |
| [`docs/v3t-runtime-governance.md`](v3t-runtime-governance.md) | V3/V3t 运行依赖、故障恢复、GIL 可观测性与兼容退场门禁 |
| [`docs/refactor/backend-architecture-governance.md`](refactor/backend-architecture-governance.md) | 分阶段架构治理、边界门禁与迁移验收 |
| [`docs/refactor/backend-module-refactor-compatibility.md`](refactor/backend-module-refactor-compatibility.md) | 模块迁移与插件兼容层实施矩阵 |
| [`docs/refactor/backend-architecture-next-stage.md`](refactor/backend-architecture-next-stage.md) | 对标优秀 Python 后端后的二阶段任务、验收与回滚方案 |
+5 -5
View File
@@ -11,7 +11,7 @@ curl -fsSL https://raw.githubusercontent.com/jxxghp/MoviePilot/v3/scripts/bootst
脚本会自动:
- 检测操作系统
- 自动检查并尽量安装 `git``curl``uv 0.12.5``Python 3.12+`
- 自动检查并尽量安装 `git``curl``uv 0.12.5``Python 3.14+`
- 克隆 `MoviePilot`
- 安装后端依赖
- 按当前仓库 `version.py` 中的 `FRONTEND_VERSION` 下载对应前端 release 的 `dist.zip`
@@ -24,8 +24,8 @@ curl -fsSL https://raw.githubusercontent.com/jxxghp/MoviePilot/v3/scripts/bootst
说明:
- 如果系统里已经有可用的 `Python 3.12+`,脚本会优先直接复用本地解释器
- 如果系统里没有可用解释器,脚本会通过固定版本的 uv 安装 Python 3.12
- 如果系统里已经有可用的 `Python 3.14+`,脚本会优先直接复用本地解释器
- 如果系统里没有可用解释器,脚本会通过固定版本的 uv 安装 Python 3.14
- Linux 下安装系统依赖时通常需要 `sudo`
- 复用已有仓库时,脚本现在只会因为已跟踪源码改动而阻止自动更新,不会再被 `.DS_Store` 之类未跟踪文件卡住
@@ -156,7 +156,7 @@ moviepilot commands
```shell
moviepilot install deps
moviepilot install deps --python python3.12
moviepilot install deps --python python3.14
moviepilot install deps --venv /path/to/venv
moviepilot install deps --recreate
moviepilot install deps --config-dir /path/to/moviepilot-config
@@ -164,7 +164,7 @@ moviepilot install deps --config-dir /path/to/moviepilot-config
说明:
- 默认会自动选择本地已安装的 `Python 3.12+` 解释器
- 默认会自动选择本地已安装的 `Python 3.14+` 解释器
- 安装器要求 `uv 0.12.5`,并按仓库提交的 `uv.lock` 同步依赖;不会在本地重新解析一套未锁定结果
- `moviepilot_rust` 加速扩展通过 `moviepilot-rust` PyPI 依赖安装,主项目本地安装不需要 Rust toolchain
- 安装完成后可在前端“高级设置 - 实验室”中关闭或重新开启 Rust 加速;如果后端未加载扩展,该开关会保持关闭且不可操作
+14 -8
View File
@@ -6,7 +6,7 @@
在开始之前,请确保您的系统已安装以下软件:
- **Python 3.12+**
- **Python 3.14+**
- **uv 0.12.5**Python 版本、虚拟环境和依赖锁定工具)
- **Git** (用于版本控制)
- **RAR 解压工具**:本地开发如需测试或使用 `.rar` 字幕包解压,请安装 `unar``unrar``7z``bsdtar` 之一;Docker 镜像会内置 `unar`
@@ -35,9 +35,10 @@ uv sync --locked --no-dev --no-install-project
| 位置 | 用途 | 维护方式 |
| --- | --- | --- |
| `pyproject.toml``[project].dependencies` | 主程序生产运行依赖。 | 开发者按直接依赖的兼容范围维护。 |
| `pyproject.toml``[project].dependencies` | 两套 Python 运行时共享的主程序生产依赖。 | 开发者按直接依赖的兼容范围维护。 |
| `pyproject.toml``[dependency-groups].dev` | pytest、覆盖率、Pylint 和源码构建等开发工具。 | 不进入 Docker 生产运行环境。 |
| `uv.lock` | Python 3.12+ 和受支持平台共享的完整解析结果。 | 修改 `pyproject.toml` 后由 `uv lock` 更新并提交。 |
| `pyproject.toml` `[dependency-groups].runtime-*` | 标准与 free-threaded 解释器互斥的 ABI 敏感运行依赖。 | 只放两套运行时确实不同的直接依赖。 |
| `uv.lock` | Python 3.14+、两套运行时 profile 和受支持平台共享的完整解析结果。 | 修改 `pyproject.toml` 后由 `uv lock` 更新并提交。 |
主程序不再维护 `requirements.in``requirements-dev.in``requirements.txt`,也不生成
平台专属的 requirements 锁文件。Docker、CLI 和 CI 都以提交的 `uv.lock` 为安装输入。
@@ -86,10 +87,11 @@ chmod +x scripts/start-local.sh
新增或升级依赖时,先确认依赖属于哪个层级:
1. **运行时依赖**:被 `app/` 生产代码直接导入,或是生产功能、后台任务、插件框架启动必需,写入 `[project].dependencies`
2. **开发 / 测试 / 静态检查 / 构建依赖**:只用于单测、覆盖率、lint 辅助、源码构建等,写入 `[dependency-groups].dev`
3. **工具依赖**:仓库要求使用 `uv 0.12.5`;不应为了安装工具而把它加入主程序运行依赖
4. **插件依赖**由插件清单声明并在插件安装阶段处理,不直接并入主程序依赖。
1. **共享运行时依赖**:被 `app/` 生产代码直接导入,或是生产功能、后台任务、插件框架启动必需,写入 `[project].dependencies`
2. **ABI 敏感运行依赖**:标准与 free-threaded 解释器必须选择不同制品或版本时,分别写入 `runtime-standard``runtime-free-threaded`;两组保持互斥并由运行时统一选择
3. **开发 / 测试 / 静态检查 / 构建依赖**:只用于单测、覆盖率、lint 辅助、源码构建等,写入 `[dependency-groups].dev`
4. **工具依赖**仓库要求使用 `uv 0.12.5`;不应为了安装工具而把它加入主程序运行依赖。
5. **插件依赖**:由插件清单声明并在插件安装阶段处理,不直接并入主程序依赖。
修改后更新并校验锁文件:
@@ -97,9 +99,13 @@ chmod +x scripts/start-local.sh
uv lock
uv lock --check
uv sync --locked
uv pip check
uv sync --locked --offline --inexact --no-dev --check
```
`uv pip check` 可用于查看第三方包元数据诊断,但不作为项目依赖合同:`oss2` 已停止维护,其元数据仍
声明旧 `crcmod`,而主程序统一使用保持相同导入接口的 `crcmod-plus`。项目一致性以锁文件和上述
`uv sync --check` 结果为准。
`uv.lock` 同时覆盖 Linux x86_64/arm64、macOS x86_64/arm64 和 Windows x64。统一锁文件只
固定解析结果,不能替代这些平台的真实安装门禁;平台条件依赖变更必须通过对应 CI 环境验证。
@@ -25,7 +25,7 @@
3. `PluginManager` 的加载、生命周期、注册表、投影、存储、目录、路径、同步、依赖、克隆和文件监控分别由 `app/runtime/extensions/plugin/` 下的单职责组件承担;旧管理器只保留 V3 ABI 门面和兼容调用顺序。
4. 动态插件 API 使用专用 raw 路由;主程序统一响应信封不进入插件 `get_api()`。前端 `pluginApi` 对非 `Response` envelope 的 payload 原样交付调用方。
5. 旧插件导入仅由 `app/runtime/compat/manifest.py` 精确映射;canonical 模块不复制旧 Manager/Helper/Oper 导出。`app/plugins/` 仍是运行时副本,继续排除在宿主架构扫描之外。
6. 2026-08-24 当前机器基线为 810 个宿主 Python 模块、6,560 条内部导入边;数据库边界、Adapter→DB、Runtime→DB、Application→DB 及新增 API/Agent/Chain 目标边均为 0。架构门禁、插件兼容快照和基线脚本均已重新生成。
6. 2026-08-24 当前机器基线为 811 个宿主 Python 模块、6,572 条内部导入边;数据库边界、Adapter→DB、Runtime→DB、Application→DB 及新增 API/Agent/Chain 目标边均为 0。架构门禁、插件兼容快照和基线脚本均已重新生成。
7. 订阅写入统一归入 `app/application/subscription/write.py`;插件动态路由和文件夹操作统一归入 `app/application/plugin/routes.py``folders.py`。重构期间新增且未形成插件 ABI 的 `app/application/subscribe.py``app/application/plugins.py` 已直接删除,不进入 compat manifest。
8. 2026-08-24 完成 Module Contract V2 宿主观察面收口:212 个 spec 均使用可执行的显式 aggregation
`legacy` 只保留为未知第三方自定义方法的开放 fallback;插件方法名、kwargs、优先级和异常隔离 ABI 不变。
@@ -106,7 +106,7 @@ MoviePilot V3 已经完成一轮重要基础工作:原 `app/core`、`app/helpe
### 4.3 模块规模
排除 `app/plugins/` 后,2026-08-24 当前静态扫描得到 810 个 Python 模块、6,560 条内部导入边。下表保留 2026-08-18 收口时的一级目录规模快照(代码行数包含注释和空行,用于趋势比较而非质量评分):
排除 `app/plugins/` 后,2026-08-24 当前静态扫描得到 811 个 Python 模块、6,572 条内部导入边。下表保留 2026-08-18 收口时的一级目录规模快照(代码行数包含注释和空行,用于趋势比较而非质量评分):
| 一级目录 | 约代码行数 | Python 文件数 | 判断 |
| --- | ---: | ---: | --- |
@@ -154,8 +154,8 @@ MoviePilot V3 已经完成一轮重要基础工作:原 `app/core`、`app/helpe
| 指标 | 初始审计 | 当前基线 | 说明 |
| --- | ---: | ---: | --- |
| Python 模块数 | 约 654 | 810 | 增量来自单一职责的 Application、Runtime、Adapter、插件组件和维护用例模块 |
| 内部导入边 | 约 5,623 | 6,560 | 显式端口增加模块数但移除了反向边;边数不作为单独质量目标 |
| Python 模块数 | 约 654 | 811 | 增量来自单一职责的 Application、Runtime、Adapter、插件组件和维护用例模块 |
| 内部导入边 | 约 5,623 | 6,572 | 显式端口增加模块数但移除了反向边;边数不作为单独质量目标 |
| SCC 数 | 14 | 1 | 自有代码 SCC 已归零,仅保留 TMDB 移植包内部隔离例外 |
| `adapters -> db` | 存在 | 0 | `PluginHelper``MoviePilotServerHelper` 的本地数据读取已移到组合根/Application |
| `runtime -> db` | 存在 | 0 | 插件存储、服务配置均改为启动注入 |
+5 -5
View File
@@ -4,9 +4,9 @@
| Item | Detail |
|---|---|
| Language | Python 3.12+ |
| Primary CI Python version | Python 3.12 |
| Dependency compatibility CI | Supported platform matrix on Python 3.12, plus newer interpreter coverage on Linux x86_64 |
| Language | Python 3.14+ |
| Primary CI Python version | Python 3.14 |
| Dependency compatibility CI | Python 3.14 supported-platform matrix plus Linux amd64/arm64 standard and free-threaded Docker profiles |
| Async runtime | asyncio (native), integrated with FastAPI/Uvicorn |
---
@@ -106,7 +106,7 @@
| Item | Detail |
|---|---|
| Project metadata | `pyproject.toml` — runtime dependencies in `[project].dependencies`, development tooling in `[dependency-groups].dev` |
| Lock | `uv.lock` — committed resolution for Python 3.12+ and supported platforms |
| Lock | `uv.lock` — committed resolution for Python 3.14+ and supported platforms |
| Package manager | uv 0.12.5 |
| Runtime install | `uv sync --locked --no-dev --no-install-project` |
| Dev/test/lint/build install | `uv sync --locked` |
@@ -131,7 +131,7 @@
|---|---|---|
| pytest | Test runner | `uv run --locked --no-sync pytest tests/test_xxx.py` |
| pylint | Static analysis | `uv run --locked --no-sync pylint app/` |
| uv | Lock and environment consistency | `uv lock --check && uv pip check` |
| uv | Lock and environment consistency | `uv lock --check && uv sync --locked --offline --inexact --no-dev --check` |
| pip-audit | Locked dependency vulnerability scan | `uv export --quiet --locked --no-dev --no-emit-project -o /tmp/moviepilot-audit-requirements.txt && uvx --from pip-audit==2.10.1 pip-audit --require-hashes --disable-pip --strict --progress-spinner off -r /tmp/moviepilot-audit-requirements.txt` |
---
+4 -3
View File
@@ -25,15 +25,16 @@ uv lock --check
# Update the lock after editing pyproject.toml
uv lock
# Verify installed dependency consistency
uv pip check
# Verify the installed environment against the locked project
uv sync --locked --offline --inexact --no-dev --check
```
**Rules:**
- Runtime dependencies belong in `[project].dependencies` in `pyproject.toml`.
- Test, coverage, lint, and explicit build tooling belong in `[dependency-groups].dev`.
- Commit the updated `uv.lock`; do not maintain or generate main-program requirements files.
- Use uv 0.12.5 and Python 3.12+.
- `uv pip check` is diagnostic only because unmaintained third-party metadata may name a compatible superseded distribution.
- Use uv 0.12.5 and Python 3.14+.
---
+1 -1
View File
@@ -11,7 +11,7 @@
## Python Version and Typing
- Target: **Python 3.12+**. Python 3.12 is the primary CI version; compatibility CI also verifies newer interpreters.
- Target: **Python 3.14+**. Python 3.14 is the primary CI version; dependency CI also verifies supported platforms and both Linux runtime profiles.
- **Type annotations are required** on all public methods and function signatures.
- Use `Optional[X]` for nullable types (do not use `X | None` — keep consistency with the existing codebase style).
- Use `Union[X, Y]` for multi-type parameters.
+1 -1
View File
@@ -136,7 +136,7 @@ Before marking any task as complete:
- [ ] Related pytest tests pass
- [ ] No new pylint error-level issues in `pylint app/`
- [ ] If dependencies changed: the package is in the correct `pyproject.toml` group, `uv.lock` is current, locked sync and `uv pip check` pass, and the locked runtime dependency audit passes
- [ ] If dependencies changed: the package is in the correct `pyproject.toml` group, `uv.lock` is current, the locked project consistency check and runtime dependency audit pass
- [ ] If CLI behavior changed: `docs/cli.md` and related tests are updated
- [ ] If MCP/API behavior changed: `docs/mcp-api.md` and related skill files are updated
- [ ] If database schema changed: a new Alembic migration exists under `database/versions/`
@@ -103,7 +103,7 @@ When updating a dependency:
1. Decide the dependency layer: runtime packages go to `[project].dependencies`; test, coverage, lint, and explicit build tooling go to `[dependency-groups].dev`.
2. Run `uv lock`, commit the updated `uv.lock`, and verify it with `uv lock --check`.
3. Run `uv sync --locked`, `uv pip check`, and the locked runtime dependency audit documented in `03-commands.md`.
3. Run `uv sync --locked`, the locked project consistency check, and the runtime dependency audit documented in `03-commands.md`.
4. Run the full test suite: `uv run --locked --no-sync pytest`.
---
+1 -1
View File
@@ -29,7 +29,7 @@ chmod +x moviepilot-site-collector-linux
当前自动构建产物尚未接入 Windows 或 Apple 代码签名。Windows SmartScreen 或 macOS Gatekeeper 可能因此显示安全提示。仅在文件来自 MoviePilot 官方 GitHub Release,且校验摘要一致时运行;不要从聊天、网盘或第三方站点接收采集器。
如果系统阻止运行,可改用随 MoviePilot 源码提供的本地采集脚本;该方式需要 Python 3.12+ 及完整后端依赖,不适合作为普通用户的首选路径。
如果系统阻止运行,可改用随 MoviePilot 源码提供的本地采集脚本;该方式需要 Python 3.14+ 及完整后端依赖,不适合作为普通用户的首选路径。
## 维护者发布流程
+243
View File
@@ -0,0 +1,243 @@
# MoviePilot V3t 运行时治理
> 状态:持续维护。本文定义 `moviepilot-v3` 与 `moviepilot-v3t` 的产品边界、运行依赖分层、
> 故障恢复合同和兼容项退场门禁。具体版本以 `pyproject.toml` 与 `uv.lock` 为准。
## 1. 治理目标
MoviePilot 同一版本提供两套镜像:
- `moviepilot-v3` 使用标准 CPython 3.14,是默认稳定镜像和故障回滚基线。
- `moviepilot-v3t` 使用 CPython 3.14 free-threaded 构建,用于获得多线程 CPU 并行能力;
Rust 能力固定启用,不能在运行时关闭。
两套镜像必须来自相同源码 revision、使用相同产品版本和同一份 `uv.lock`。V3t 不替换默认镜像,
也不在业务代码中维护一套平行实现;解释器、原生 ABI 或插件不兼容时,应切回同版本标准 V3。
正式发布按同版本制品对验收和晋升 `latest`;任一变体未通过构建、扫描或发布时,本次版本不移动
两边的 `latest`。这不会回退已经发布的标准 V3,且避免两个 `latest` 指向不同源码版本。
V3t 的目标不是让所有请求都更快。它主要改善可并行的 Python CPU 热点,同时验证主程序、原生扩展
和插件生态在 free-threaded 解释器下的正确性。启动、内存、普通 API 和数据库路径不得为获得局部
并发收益而出现不可接受的退化。
## 2. 单一依赖事实源
`pyproject.toml` 中的 `project.dependencies` 是两套镜像共享的运行依赖,ABI 敏感依赖放在两个互斥组:
- `runtime-standard`
- `runtime-free-threaded`
`tool.uv.conflicts` 保证两个组不能同时解析。Docker 的两个构建 stage 分别固定选择对应组;源码升级、
启动恢复和插件安装后的宿主恢复由 `app.runtime.dependencies.runtime_dependency_group()` 根据当前
解释器的 `Py_GIL_DISABLED` 构建标志选择 profile。
依赖选择不得改为镜像标签判断,也不得在业务模块中按包名散落 V3/V3t 分支。Dockerfile 只选择运行
profile,依赖名称、版本和 source 语义全部由 `pyproject.toml``uv.lock` 管理。
## 3. 当前原生依赖矩阵
下表中的版本用于解释当前分叉原因,不代替锁文件。版本变化后应同步更新本表的治理状态。
| 能力 | 标准 V3 | V3t | 当前处理与上游解除条件 |
| --- | --- | --- | --- |
| Python | CPython 3.14 | CPython 3.14t | 跟随同一 Python 3.14 patch 版本;升级后必须重新验证解释器 ABI、GIL 状态、启动与完整测试。 |
| `moviepilot-rust` | `cp314-abi3` | `cp314t` | 同一包版本按 wheel tag 选择;两套 ABI 和 V2 使用的 `cp311-abi3` 必须在发布链中保持独立可用。 |
| `bcrypt` | 4.x | 5.x | V3t 使用提供 free-threaded 制品的版本;当同一稳定版本同时满足两套 ABI 与密码合同后可合并约束。 |
| Brotli | 1.2.0 wheel | 同版本固定源码构建 | V3t 当前只对该 profile 使用固定上游源码。上游提供可复现的稳定 `cp314t` wheel 后,验证导入、压缩结果、并发与 GIL,再删除 source 覆盖。 |
| CRC 加速 | `crcmod-plus` 2.3.1 | `crcmod-plus` 2.3.1 | 两套镜像统一使用继续维护且兼容 `crcmod` 导入接口的实现。`oss2` 的陈旧元数据仍声明不再维护的 `crcmod`,由 uv 在解析时排除该传递依赖;宿主不在运行时映射、卸载或替插件兼容旧分发包。 |
| `lxml` | 6.1.2 | 7.0.0b1 | V3t 暂用提供目标 ABI 的预发布版本,是当前最高风险项。稳定版提供 `cp314t` wheel 后,需通过 XML、HTML、RSS、站点解析、并发和内存验证再替换。 |
| PostgreSQL 同步驱动 | `psycopg2-binary` 2.x | `psycopg[c]` 3.3.4 | 当前分叉同时受 ABI 与实测性能影响,不要求仅为版本统一而收敛。若上游能力或性能变化,必须重跑三方案 PostgreSQL A/B 后再决策。异步路径继续使用 `asyncpg`。 |
| `orjson` | 3.12.0 wheel | 同版本源码构建 | V3t 使用同一锁定版本并启用 free-threaded 构建变量。上游发布覆盖 Linux amd64/arm64 的稳定 `cp314t` wheel 后可删除本地构建要求。 |
| 中文转换 | `zhconv-rs` | `moviepilot-rust.zhconv_fast()` | 主程序统一经 `app.foundation.text.convert()`,插件统一经 SDK;不得让调用方感知后端差异。只有语义、性能、体积和 ABI 均更优时才考虑统一实现。 |
| 站点资源 | `cpython-314` | `cpython-314t` | 资源文件必须按解释器 ABI 独立构建和选取,不能让 V3t 复用普通 CPython 扩展,也不能影响 V2 的历史 ABI 制品。 |
表中差异不是全部都要消除。只有临时 source 构建、预发布依赖或第三方元数据兼容适合在上游成熟后
优先退场;已由性能与产品边界证明合理的驱动或实现选择,可以继续由 profile 集中管理。
## 4. 依赖检查与自愈合同
“自愈”包含两个不同的恢复边界,不能与普通依赖校验混为一谈。
### 4.1 启动前恢复
容器启动前先导入一组后端核心依赖。导入成功时不运行 uv 同步;导入失败时才选择可用依赖源,并对
`/app` 执行锁定的项目同步。同步命令必须:
- 使用当前虚拟环境中的解释器推导 runtime profile
- 使用 `--locked`,禁止启动时重新求解未锁定版本;
- 使用 `--inexact`,保留共享环境中的插件额外依赖;
- 只恢复主项目运行依赖,不安装项目本身或开发依赖;
- 恢复后再次执行核心导入探针,仍失败则停止后端启动并保留诊断入口。
源码更新事务中的依赖同步和失败回滚使用同一 profile 选择入口。否则 V3t 在恢复时可能被普通默认组
覆盖,得到“3.14t 解释器 + 标准原生依赖”的无效组合。
### 4.2 插件安装后的宿主恢复
插件与主程序共享虚拟环境。插件依赖安装前后都会采集宿主健康快照,只对安装后新增的异常执行补偿:
1. 优先使用安装前生成的主程序保护约束恢复被修改的包;
2. 约束不可用时,按主项目 `pyproject.toml``uv.lock` 和当前 runtime profile 恢复;
3. 恢复完成后重新执行依赖诊断与核心能力探针;
4. 宿主即使恢复成功,本次插件安装仍返回失败,不能把被回滚的安装报告为成功。
依赖诊断使用 `uv pip check`,并按安装前后的稳定错误集合识别新增问题;这样既不会把
`oss2` 对旧 `crcmod` 的陈旧元数据误归因于本次安装,也不会让既有告警遮蔽其他新增错误。核心能力
探针统一由 `app.doctor.dependencies` 执行。普通启动使用轻量模式验证 Web 栈、中文分词与转换;
镜像构建及插件安装前后使用完整模式,继续验证 ABI 敏感原生扩展、CRC C 实现、PostgreSQL C 实现及
导入后的 GIL 状态。插件允许的非核心依赖升级不要求与宿主锁文件逐版本相同,因此插件健康检查不得
用整份 `uv.lock` 强制回滚共享环境。
自愈是异常补偿,不是每次启动的常态安装流程。任何新增恢复入口都必须复用统一 profile 选择函数,
不得自己拼装默认 uv group。
## 5. GIL 与原生扩展可观测性
构建阶段必须导入并执行已知核心原生依赖,验证 V3t 在导入前、导入后和热点执行后均保持 GIL 关闭。
运行阶段还必须保留动态观测,因为构建探针无法穷举插件延迟导入的第三方扩展:
- 启动收尾日志记录解释器类型和实际 GIL 状态;
- 登录后全局设置与系统环境 API 返回 `PYTHON_FREE_THREADED``PYTHON_GIL_ENABLED`,系统环境 API
同时返回 `RUST_ACCEL_REQUIRED`
- 插件安装或重载导致 GIL 从关闭变为开启时,日志记录对应插件归因;
- 前端在 V3t 实际启用 GIL 时显示兼容告警,正常状态不误报。
不得通过 `PYTHON_GIL=0` 或等效方式强制绕过扩展的 GIL 声明。扩展一旦使进程退化为 GIL 模式,
应保留可观测性并修复或替换依赖;无法及时处理时回退标准 V3。
## 6. 当前 A/B 基线与决策
以下数据来自同一源码 revision、相同资源限制的 arm64 候选镜像,用于确定当前架构和依赖策略。后续
上游重放、依赖更新、构建链变化或采集 schema 扩充都会使其失去“最终发布验收”资格,但不抹去已经
建立的设计结论;新的发布候选必须重跑并在本节追加或替换相应数据。
### 6.1 镜像与解释器基线
| 指标 | 标准 V3 | V3t | 结论 |
| --- | ---: | ---: | --- |
| arm64 镜像体积 | 647.5 MiB | 674.0 MiB | V3t 增加 26.5 MiB,约 4.1%;标准镜像仍处于既有 660+ MB 发布口径。 |
| 启动就绪中位数 | 6.177 s | 5.608 s | V3t 约快 9.2%,未造成启动退化;不据此承诺所有部署都会更快。 |
| 空载 working set | 274.9 MiB | 336.8 MiB | V3t 增加约 22.6%,低于 25% 验收阈值。 |
| 主工作负载 PSS | 296.4 MiB | 356.3 MiB | V3t 增加约 20.2%,属于实验镜像的明确资源成本。 |
| 32 线程纯 Python CPU 探针 | 13,143.7 ops/s | 42,583.5 ops/s | V3t 约为 3.24 倍,证明解释器并行收益;该探针不经过 Rust 开关。 |
| 32 线程直接 Rust ABI 探针 | 40,832.4 ops/s | 61,550.9 ops/s | V3t 约为 1.51 倍;两边都直接调用 Rust,仅验证原生 ABI 与并发。 |
| 普通 API 最大 p95 比例 | 基线 | 1.10x | 四个本地 API 中最大退化来自订阅列表,仍低于 1.25x 门禁。 |
同 revision 的 amd64 最终候选也已完成交叉架构验收。标准 V3 与 V3t 的本地镜像体积分别为
680.6 MiB 和 714.0 MiBV3t 增加 33.4 MiB、约 4.9%。两者均使用 Python 3.14.7,标准 V3
保持 GIL 启用;V3t 使用 free-threaded 解释器,完整原生依赖、`psycopg` C 实现、Rust 文本能力及
`sites.cpython-314t-x86_64-linux-gnu.so` 依次加载后 GIL 始终关闭。两个镜像均以空白配置启动到
`/health/ready` 且 Docker health 为 healthy。该启动运行在 arm64 宿主的 amd64 模拟环境中,只作为
发布候选功能与 ABI 验收,不纳入性能比例。
该候选还通过了四分片全量回归(5,981 passed、3 skipped、14 subtests passed)、PostgreSQL 18.6
迁移/提交/回滚、现代插件清单、历史 `requirements.txt`、插件源码与依赖恢复以及恢复后单实例加载。
这些结果证明双 profile 方案可行;最终发布仍必须使用最新 revision 和正式不可变 digest 重跑。
### 6.2 应用热点三组
真正受产品 Rust 开关影响的应用识别热点必须分成三个对象:
| 运行方式 | 解释器 | 应用实现 | 产品语义 |
| --- | --- | --- | --- |
| V3 + Python | 标准 CPython | Python fallback | 标准镜像关闭 `RUST_ACCEL` 的兼容基线。 |
| V3 + Rust | 标准 CPython | `moviepilot-rust` | 标准镜像启用 `RUST_ACCEL` 的默认性能路径。 |
| V3t + Rust | free-threaded CPython | `moviepilot-rust` | V3t 固定路径,Rust 不允许关闭。 |
当前六个交替样本的中位耗时分别为 `0.068769s``0.035339s``0.035511s`,三组业务结果校验和
一致。标准 V3 启用 Rust 后耗时约为 Python fallback 的 `0.514x`;V3t 固定 Rust 路径约为标准 V3
Rust 路径的 `1.005x`,即慢约 0.5%,仍低于 1.25x 门禁。解释器探针的并发收益不能抵销产品路径
退化,因此两组比例必须继续独立验收。
### 6.3 PostgreSQL 三方案
三组使用同一 PostgreSQL 18.6、相同 SQL 和六轮全排列顺序,以标准 V3 + `psycopg2` 为 1.00 倍基线:
| 方案 | 单连接查询吞吐 | 16 线程查询吞吐 | 批量事务写耗时 |
| --- | ---: | ---: | ---: |
| V3 + `psycopg2` | 1.00x | 1.00x | 1.00x |
| V3 + `psycopg3 binary` | 0.946x | 0.367x | 0.094x |
| V3t + `psycopg3 C` | 0.931x | 1.258x | 0.096x |
因此标准 V3 保留 `psycopg2`V3t 为满足 free-threaded ABI 使用 `psycopg3 C``psycopg3` 的批量
事务写明显更快,但查询吞吐没有形成全面优势;当前不为统一驱动扩大数据库重构。18 个当前镜像样本
均通过驱动实现、GIL、SQL 结果、长事务并行和样本完整性门禁。三种制品内嵌的 libpq 分别为 17.9、
18.0 和 18.6,性能差异不能全部归因于 Python 驱动。未来驱动或应用访问模式变化
时,必须重跑真实短事务、批量写、连接池、长事务和迁移场景,不能只用单项微基准改写结论。
### 6.4 环境检查成本
`uv sync --check --locked --offline --inexact` 适合验证镜像构建、开发环境和锁定项目能否复现,
不用于覆盖插件允许的非核心依赖升级。真正恢复可能访问包索引,耗时仍受缺失制品、缓存命中、网络和
原生构建影响,不能用检查耗时推断用户故障恢复时长。
当前公共 A/B schema 2 已记录完整安装包集合及哈希、全部原生 distribution 和 wheel tag、导入前后
GIL、working set、RSS/PSS/USS、API p50/p95、SQLite 同步/异步结果和 1/8/16/32 线程热点。两套镜像
各安装 174 个包;V3t 的 16 个核心原生模块在同一进程依次导入后,GIL 始终保持关闭。
### 6.5 冷启动、插件恢复与宿主自愈
空白配置卷首次启动需要下载 CloakBrowser 内核。该场景可能接近用户报告的“两分钟启动”,主要瓶颈
是持久浏览器缓存未命中和下载网络,不是插件初始化;缓存命中后的普通重启不重复下载。
现代 `pyproject.toml` 插件(`boltons==25.0.0`)与历史 `requirements.txt` 插件
`humanize==4.15.0`)均已在 V3t 真实容器中验收。首次运行从快照恢复两个源码目录耗时 1.60ms,
联网安装依赖及激活耗时 4.64s;优雅关停后使用同一不可变镜像和持久 `/config` 断网重建,源码恢复
耗时 1.95ms,仅从持久 uv 缓存恢复依赖及激活耗时 1.56s。两轮各插件都只产生一条实例初始化记录,
最终 API 状态均为 `active`、无 pending/failed,且没有启用 GIL。该结果证明插件源码恢复不应被依赖
下载阻塞,依赖未就绪的插件可以在 Web 可用后渐进加载。
删除一个主程序核心依赖后重启,启动前探针能触发锁定自愈,`--inexact` 保留插件额外依赖;恢复后
插件仍为单实例 `active`,V3t GIL 仍关闭。该路径的价值是让受污染共享环境能够恢复启动,不是性能
优化;它只在核心导入失败时执行,正常重启不会承担这段解析成本。
## 7. 上游能力的渐进式接入
第三方上游发布新版本或新 wheel 后,按以下顺序处理,不能看到文件名包含 `cp314t` 就直接删除本地兼容:
1. **确认制品**:稳定版本覆盖 Linux amd64/arm64wheel tag 与解释器 ABI 匹配;源码构建路线还要固定
可审计的源版本和构建工具链。
2. **建立候选锁**:只修改对应 runtime group 或 source,更新单一 `uv.lock`,确认标准与 free-threaded
两套 profile 均可 `uv sync --locked` 重建。
3. **验证原生合同**:导入、核心功能、错误边界、首次初始化和 1/8/16/32 线程并发结果一致;V3t
在每个阶段保持 GIL 关闭。
4. **验证恢复链**:覆盖启动前恢复、源码更新与回滚、现代插件清单、历史 `requirements.txt` 和插件
安装后宿主恢复,确认 profile 不串组且插件额外依赖不被裁剪。
5. **执行同 revision A/B**:先证明标准 V3 修改前后无可重复退化,再比较同一 revision、同一资源限制
和不可变镜像 digest 的 V3/V3t。至少保留镜像体积、启动、RSS/PSS/USS、API p50/p95、SQLite、
PostgreSQL 和 CPU 热点原始样本。
6. **完成双架构验收**amd64、arm64 的构建、漏洞扫描、启动和核心功能均通过后,才发布候选制品。
7. **删除临时处理**:同一变更中移除不再需要的 source、版本分叉或排除规则,并更新本表、锁文件、
构建探针和回归测试;不要永久保留失效的兼容分支。
公共 A/B 基线为 `scripts/perf/free_threaded_ab.py`,使用方法和输出合同见
[`scripts/perf/README.md`](../scripts/perf/README.md)。正式验收原始数据应按发布候选独立留存,PR 只提交
可复用脚本和维护者可判断的汇总结论。
## 8. 插件生态边界
- 插件不得直接依赖 V3/V3t 的内部实现选择,应使用 `app.sdk` 暴露的文本、网络和运行时能力。
- 插件直接导入 `zhconv_rs` 等标准 V3 专属包时,V3t 可以明确判定为不兼容;宿主不伪造第三方模块。
- 插件若自行声明不再维护的 `crcmod`,必须由插件迁移到 `crcmod-plus`;宿主不映射分发包、不增加插件
特判,也不在运行期卸载插件声明的依赖。
- 插件携带原生 wheel 时必须匹配当前解释器和平台 ABI。缺少 `cp314``cp314t` 制品属于插件依赖
兼容问题,不通过宿主插件 ID 特判绕过。
- 插件安装后若使 V3t 重新启用 GIL,功能可能仍可运行,但该进程已经失去 free-threaded 产品语义,
必须告警并引导用户升级插件、移除依赖或切回标准 V3。
V3 `3.0.0` 的依赖集合随镜像交付。相同版本 Tag 的 `release` 自动更新只执行版本比较,不下载源码或
同步依赖;用户必须拉取并重建容器后才会使用新镜像内的锁定环境。`/app``/opt/venv` 不属于标准
持久化卷,非标准挂载和 `dev` 自动更新不属于正式镜像迁移合同。
## 9. 变更检查表
修改 Python patch 版本、runtime group、原生依赖、Rust 制品、资源 ABI 或双镜像构建链时,至少确认:
- [ ] `pyproject.toml``uv.lock` 同步,两个互斥 profile 均能锁定重建。
- [ ] 标准 V3 依赖集合、性能、体积和功能没有因 V3t 改动退化。
- [ ] V3t 核心原生扩展导入及并发执行后 GIL 仍关闭。
- [ ] amd64、arm64 的 Python、Rust 和站点资源 ABI 匹配。
- [ ] SQLite、PostgreSQL、插件安装/恢复、源码升级/回滚和启动自愈通过。
- [ ] 动态 GIL API、启动日志、插件归因和前端告警保持一致。
- [ ] 安全扫描结果已按实际安装制品、利用面和上游修复状态审计。
- [ ] 上游已成熟的临时兼容已删除,仍保留的例外在本文有明确解除条件。