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
+66 -19
View File
@@ -31,6 +31,10 @@ from importlib.metadata import distributions
from requests import Response
from app.runtime.cache import cached, is_fresh
from app.runtime.dependencies import (
iter_runtime_profile_requirement_strings,
iter_runtime_requirement_strings,
)
from app.runtime.settings import RuntimeSettingsCompat
from app.adapters.system.package import (
PackageInstallRequest,
@@ -205,10 +209,7 @@ class PluginHelper(metaclass=WeakSingleton):
"starlette",
"uvicorn",
})
_runtime_import_probe = (
"import alembic, fastapi, pydantic, pydantic_core, pydantic_settings, "
"sqlalchemy, starlette, uvicorn; from pydantic import BaseModel, Field"
)
_runtime_import_probe = "app.doctor.dependencies"
@staticmethod
def is_local_repo_url(repo_url: Optional[str]) -> bool:
@@ -1316,12 +1317,12 @@ class PluginHelper(metaclass=WeakSingleton):
return list(dict.fromkeys(wheels_dirs))
@staticmethod
def __build_runtime_uv_command(*args: str) -> List[str]:
"""构造绑定当前解释器环境的 uv pip 命令。"""
def __build_runtime_uv_check_command() -> List[str]:
"""构造绑定当前解释器环境的 uv 依赖诊断命令。"""
uv_bin = find_uv(Path(sys.executable))
if not uv_bin:
return []
return [str(uv_bin), "pip", *args, "--python", sys.executable]
return [str(uv_bin), "pip", "check", "--python", sys.executable]
@staticmethod
def __format_package_name(name: str) -> str:
@@ -1355,8 +1356,8 @@ class PluginHelper(metaclass=WeakSingleton):
return roots
try:
manifest = load_dependency_file(project_file)
for requirement in manifest.dependencies:
for raw_requirement in iter_runtime_requirement_strings(project_file):
requirement = Requirement(raw_requirement)
if not cls.__marker_matches(requirement.marker):
continue
package_name = cls.__standardize_pkg_name(requirement.name)
@@ -1469,6 +1470,20 @@ class PluginHelper(metaclass=WeakSingleton):
return protected_packages
@classmethod
def __get_strict_runtime_packages(cls) -> Set[str]:
"""返回核心包及当前 ABI profile 中不得被插件改写的根包。"""
packages = set(cls._protected_runtime_packages)
project_file = settings.ROOT_PATH / "pyproject.toml"
try:
for raw_requirement in iter_runtime_profile_requirement_strings(project_file):
requirement = Requirement(raw_requirement)
if cls.__marker_matches(requirement.marker):
packages.add(cls.__standardize_pkg_name(requirement.name))
except Exception as error:
logger.error(f"解析运行依赖 profile 失败:{project_file} - {error}")
return packages
@staticmethod
def __is_upgrade_only_conflict(specifier_set: SpecifierSet, installed_version: Version) -> bool:
"""
@@ -1516,6 +1531,7 @@ class PluginHelper(metaclass=WeakSingleton):
允许后续安装继续调整版本。
"""
conflicts = []
strict_packages = cls.__get_strict_runtime_packages()
try:
manifest = load_dependency_file(dependency_file)
for requirement in manifest.dependencies:
@@ -1532,7 +1548,7 @@ class PluginHelper(metaclass=WeakSingleton):
package_name,
str(installed_version),
f"来自 {requirement.url} 的同名包",
package_name in cls._protected_runtime_packages,
package_name in strict_packages,
))
continue
@@ -1540,7 +1556,7 @@ class PluginHelper(metaclass=WeakSingleton):
installed_version,
prereleases=True
):
is_core = package_name in cls._protected_runtime_packages
is_core = package_name in strict_packages
# 非核心包的纯升级冲突允许放行,由安装约束控制实际版本。
if is_core or not cls.__is_upgrade_only_conflict(
requirement.specifier, installed_version):
@@ -1590,9 +1606,10 @@ class PluginHelper(metaclass=WeakSingleton):
suffix=".txt",
delete=False
) as temp_file:
strict_packages = cls.__get_strict_runtime_packages()
for package_name, version in sorted(protected_packages.items()):
if package_name in cls._protected_runtime_packages:
# 核心包严格锁定,插件不得改写
if package_name in strict_packages:
# 核心与 ABI profile 根包严格锁定,插件不得改写
temp_file.write(f"{cls.__format_package_name(package_name)}=={version}\n")
else:
# 非核心主程序依赖:允许升级,但禁止降级
@@ -1697,31 +1714,60 @@ class PluginHelper(metaclass=WeakSingleton):
执行全部运行环境自检并返回逐项结果,避免前一项失败遮蔽后续异常。
"""
health_snapshot = {}
uv_check = cls.__build_runtime_uv_command("check")
uv_check = cls.__build_runtime_uv_check_command()
if uv_check:
checks = [("uv check", uv_check)]
else:
health_snapshot["uv check"] = (False, "未找到 uv 可执行文件")
checks = []
checks.append(("核心依赖导入检查", [sys.executable, "-c", cls._runtime_import_probe]))
checks.append(("核心依赖导入检查", [
sys.executable,
"-m",
cls._runtime_import_probe,
"--full",
]))
for check_name, command in checks:
success, message = SystemUtils.execute_with_subprocess(command)
health_snapshot[check_name] = (success, message)
return health_snapshot
@staticmethod
def __runtime_health_error_lines(check_name: str, message: str) -> set[str]:
"""提取稳定诊断项,忽略执行器附加的命令摘要。"""
lines = {line.strip() for line in message.splitlines() if line.strip()}
if check_name != "uv check":
return lines
package_errors = set(re.findall(
r"The package `[^`]+` requires `[^`]+`, but [^\r\n;]+",
message,
))
return package_errors or lines
@staticmethod
def __runtime_health_regression_message(
baseline_health: Dict[str, Tuple[bool, str]],
current_health: Dict[str, Tuple[bool, str]]
) -> str:
"""
汇总相对基线从正常变为异常的检查项,不解析第三方工具的错误文本
汇总相对基线新增的异常;已有诊断失败不能遮蔽后续新增错误
"""
regressions = []
for check_name, (success, message) in current_health.items():
baseline_success = baseline_health.get(check_name, (True, ""))[0]
baseline_success, baseline_message = baseline_health.get(check_name, (True, ""))
if baseline_success and not success:
regressions.append(f"{check_name}失败:{message}")
elif not baseline_success and not success:
baseline_lines = PluginHelper.__runtime_health_error_lines(
check_name,
baseline_message,
)
current_lines = PluginHelper.__runtime_health_error_lines(
check_name,
message,
)
added_lines = sorted(current_lines - baseline_lines)
if added_lines:
regressions.append(f"{check_name}新增错误:{' | '.join(added_lines)}")
return "".join(regressions)
@classmethod
@@ -2573,7 +2619,7 @@ class PluginHelper(metaclass=WeakSingleton):
async def __async_run_runtime_healthcheck(cls) -> Dict[str, Tuple[bool, str]]:
"""异步执行插件安装后的运行环境检查。"""
health_snapshot: Dict[str, Tuple[bool, str]] = {}
uv_check = cls.__build_runtime_uv_command("check")
uv_check = cls.__build_runtime_uv_check_command()
if uv_check:
checks = [("uv check", uv_check)]
else:
@@ -2581,8 +2627,9 @@ class PluginHelper(metaclass=WeakSingleton):
checks = []
checks.append(("核心依赖导入检查", [
sys.executable,
"-c",
"-m",
cls._runtime_import_probe,
"--full",
]))
for check_name, command in checks:
health_snapshot[check_name] = (
+3
View File
@@ -6,6 +6,8 @@ from dataclasses import dataclass, field
from pathlib import Path
from urllib.parse import urlsplit, urlunsplit
from app.runtime.dependencies import runtime_sync_arguments
@dataclass(frozen=True)
class PackageInstallRequest:
@@ -132,6 +134,7 @@ def _build_uv_sync_command(uv_bin: Path, request: PackageInstallRequest, use_ind
"--no-dev",
"--no-install-project",
"--inexact",
*runtime_sync_arguments(),
]
if use_index and request.package_index_url:
command.extend(["--default-index", request.package_index_url])
+3 -1
View File
@@ -1,6 +1,7 @@
import json
import platform
import sys
import sysconfig
from pathlib import Path
from typing import Callable
@@ -63,7 +64,8 @@ class ResourceHelper:
def _get_python_version_tag() -> str:
"""返回资源文件名使用的 CPython ABI 标签。"""
version = sys.version_info
return f"cp{version.major}{version.minor}"
free_threaded = "t" if sysconfig.get_config_var("Py_GIL_DISABLED") else ""
return f"cp{version.major}{version.minor}{free_threaded}"
@staticmethod
def _get_machine_tag() -> str:
+8 -1
View File
@@ -2,6 +2,7 @@ import logging
from functools import lru_cache
from typing import List, Optional, Tuple
from app.foundation.environment import is_free_threaded_runtime
from app.runtime.log import logger, log_settings
from app.runtime.settings import get_runtime_setting
@@ -19,6 +20,11 @@ def _rust_accel_enabled() -> bool:
return bool(get_runtime_setting("RUST_ACCEL"))
def is_required() -> bool:
"""free-threaded 运行时必须使用不会重新启用 GIL 的 Rust 快路径。"""
return is_free_threaded_runtime()
def is_available() -> bool:
"""
判断 Rust 扩展是否可用
@@ -30,7 +36,7 @@ def is_config_enabled() -> bool:
"""
判断系统配置是否允许使用 Rust 加速
"""
return _rust_accel_enabled()
return is_required() or _rust_accel_enabled()
def is_enabled() -> bool:
@@ -47,6 +53,7 @@ def status() -> dict:
return {
"available": is_available(),
"enabled": is_enabled(),
"required": is_required(),
"import_error": str(_import_error) if _import_error else "",
}