首页
/ Motrix 2.0.0-beta.22:Motrix aria2 fork 内核固化、连接数安全兼容与桌面轻量模式深度解析

Motrix 2.0.0-beta.22:Motrix aria2 fork 内核固化、连接数安全兼容与桌面轻量模式深度解析

2026-09-07 10:03:49作者:裴锟轩Denise

导语

Motrix 2.0.0-beta.22 将内置下载内核统一升级为 Motrix 官方维护的 aria2 fork 1.37.0-motrix.10,让"用户自行替换官方 aria2"这一常见场景变得安全可控,同时修复了内核任务接管的 SQLite 外键竞态,新增低内存占用桌面轻量模式并打磨了新建任务窗口体验。本文以本版本的官方发布说明为主线,结合仓库源码逐项解析 fork 识别、连接数兜底、错误码 28 重试、父任务持久化顺序、轻量模式渲染进程策略等实现细节,帮助你理解并安全测试这一预发布版本。

版本总览:一次围绕下载内核安全性的系统性更新

本版本(对应仓库文档 docs/release-notes/2.0.0-beta.22.md 及其简体中文版 docs/release-notes/2.0.0-beta.22.zh-CN.md)的改动可以归纳为六大主题:

  1. 内核升级与产物固化:桌面端、Server、Docker、Flatpak 统一使用验证过的 Motrix aria2 fork 1.37.0-motrix.10
  2. fork 身份检测前置:启动引擎前先执行 aria2c --version,不依赖 RPC 即可识别"官方 aria2 / 其它非 Motrix 内核",避免不兼容内核在识别前崩溃。
  3. 兼容性参数持久化:检测到非 Motrix 内核时,把 max-connection-per-serversplit 持久调整为官方上限 16。
  4. 错误码 28 防御性重试:URI 任务创建遇到自定义内核拒绝参数时自动用兼容值重试一次。
  5. 任务接管竞态修复:先持久化接管的父任务,再允许子文件 / Peer 写入,消除可能中止启动的 SQLite 外键竞态(对应 issue #1920)。
  6. 交互与资源优化:轻量模式释放隐藏主窗口的渲染进程,新建任务窗口仅在打开时读取剪贴板。

内核升级:统一的 aria2 fork 与引擎锁文件

fork 为什么是 Motrix 的"自己人"

官方 aria2 自 1.36 之后维护节奏放缓,而 Motrix 需要在任务会话恢复、SQLite 持久化、结果移除语义等场景获得确定性的行为。因此 Motrix 维护了自己的 fork(motrixapp/aria2),以 1.37.0-motrix.<patch> 形式发布。beta.22 之前的若干 beta 一直在推进该 fork,本版本把它作为全平台默认内核:

  • 桌面端、Server、Docker、Flatpak 均使用 1.37.0-motrix.10
  • 每个受支持平台的内核产物,都在锁文件中固定文件名、大小与 SHA-256 摘要。

当前仓库的 scripts/engine.lock.json 展示了这套锁文件的完整结构:以 enginerepotagversion 标识 fork 出处,再按平台(如 darwin-arm64darwin-x64win32-x64)给出 filebinarchiveSha256binarySha256。值得注意的是,仓库当前已推进到 1.37.0-motrix.14(源码中还出现对 .13 引入的 addUriWithCookies 的判断),可见 fork patch 号在后续 beta 中持续演进,但"用锁文件按摘要钉住产物"的机制自 beta.22 起保持一致。

fork 带来的能力差异

src/core/engine/aria2/feature-report.ts 可以看到,Motrix 通过 aria2c --version 输出来构造 EngineFeatureReport,其中两个与版本强相关的判断是:

  • hasBtSeedUnverified / hasBtSaveMetadata:以 1.37.0 为阈值(--bt-seed-unverified--bt-save-metadata 在该版本引入);
  • hasSqlitePersistence:特性列表里是否含 SQLite3-Persistence,这是 fork 专属的持久化能力标记;
  • hasMoveStorage:aria2 尚不支持,代码中固定为 false

buildFeatureReport 被两条路径共用——进程探测路径(Aria2ProcessManager 解析 aria2c --version)与 RPC 连接路径(Aria2Adapter 读取 aria2.getVersion),从源码注释看,此前两条路径各自内联构造 report 且逻辑漂移过,此函数是"单一事实来源"。

fork 身份检测:在 spawn 之前识别引擎

为什么必须"前置检测"

如果不先识别内核就按 Motrix 的参数启动,官方 aria2 很可能在解析到 fork 专属启动参数时就立刻退出,届时错误已经发生、RPC 永远连不上,排查成本极高。beta.22 的做法是把检测放到 doStart 流程的 probe 阶段:先跑 aria2c --version 拿到 report,再决定后续如何组参。

src/core/engine/engine-supervisor.ts 中,启动状态机显式划分为 probe → config → spawn → rpc 四个阶段:

const featureReport = await this.processManager.probe(this.binaryPath)
this.featureReport = featureReport
// 把探测结果注入 adapter,使持久化删除信任门禁反映真实内核
this.adapter.setFeatureReport(featureReport)
if (!isMotrixFork(featureReport)) {
  const payload: EngineCompatibilityWarningPayload = {
    version: featureReport.version,
    connectionLimit: STANDARD_ARIA2_CONNECTION_LIMIT,
  }
  this.eventBus.emit(Events.EngineCompatibilityWarning, payload)
}

注意这里在 RPC 尚未建立前就完成了识别,正是发布说明中"检测不依赖 RPC,因此不兼容的内核不会在被识别之前就因启动参数崩溃"的源码依据。

识别规则:两个标记缺一不可

isMotrixFork 的判定逻辑见 src/core/engine/aria2/feature-report.ts

export function isMotrixFork(
  report: Pick<EngineFeatureReport, 'version' | 'hasSqlitePersistence'>
): boolean {
  return (
    /^\d+\.\d+\.\d+-motrix\.\d+$/i.test(report.version) &&
    report.hasSqlitePersistence
  )
}

即要求同时满足:

  1. 版本号形如 1.37.0-motrix.10(语义化版本 + -motrix.<patch> 后缀);
  2. 版本输出包含 fork 专属的 SQLite3-Persistence 特性。

配套的单测在 src/core/engine/aria2/feature-report.test.ts 中验证:1.37.0-motrix.10 + SQLite3-Persistence 判为 true;只满足后缀或只满足特性均判为 false。版本比较器 semverGte 对无法解析的分量采用"失败即 false"(fail closed)的保守策略。

这一"双标记"设计是有深意的:带后缀说明血统,带 fork 特性说明持久化能力真实存在。仅凭版本号可能被伪造的 --version 输出欺骗,而仅有特性词不能证明是 Motrix fork。

检测到官方 aria2 时:参数封顶 + 告警 + 持久化

16 是什么?官方内核的单任务连接上限

官方 aria2 将单任务的连接数上限硬编码为 16,而 Motrix fork 放开了这一限制以支持更高并发。常量定义于 src/core/engine/aria2/feature-report.ts

/** Official aria2's per-task connection ceiling. */
export const STANDARD_ARIA2_CONNECTION_LIMIT = 16

如果用户用官方 aria2(或其它非 Motrix 实现)替换内置 aria2c,而 Motrix 仍以 16 以上的 max-connection-per-server / split 启动任务,官方内核会直接拒绝参数,导致任务失败甚至行为异常。beta.22 的三层防护如下。

第一层:运行时热应用封顶(applyCompatibilityLimits

每次配置变化(含热更新)都会经过 src/core/engine/engine-supervisor.tsapplyCompatibilityLimits:只要 featureReport 存在且非 Motrix fork,就把 maxConnectionPerServersplitMath.min 钳到 16,保证任何情况下都不会给官方内核下发越界值。

第二层:持久化 + 切换到自定义性能配置

applyCompatibilityLimits 只影响内存中的值,若用户原配置保存在"高性能"等命名 profile 中,设置校验阶段很可能把固定数值重新写回,导致 16 被"还原"成不兼容值。因此 persistCompatibilityLimitssrc/core/engine/engine-supervisor.ts)在检测到钳制生效时,会把配置持久写回并把 performanceProfile 切换为 'custom'

const persisted: EngineSettings = {
  ...compatible,
  // 命名 profile 会在校验时重放固定值。把调整后的设置切到 custom,
  // 让 16 成为持久事实。
  performanceProfile: 'custom',
}

即使设置写入失败(如磁盘异常),也只会记录 warn 并退回内存中的兼容值继续运行,"设置写失败绝不能复活原始的启动崩溃"是源码注释明确的意图。

第三层:用户可见告警

EngineCompatibilityWarning 事件携带 { version, connectionLimit },订阅端把"你正在使用非 Motrix 内核,连接数已被限制为 16"呈现给用户。测试覆盖见 src/core/notifications/engine-compatibility-subscriber.test.ts

热更新路径同样受控

引擎运行中如果用户把连接数调高,syncHotOptionssrc/core/engine/engine-supervisor.ts)会先对"变更前"与"变更后"两组配置都做 applyCompatibilityLimits,只把确实变化且在兼容范围内的参数经 aria2.changeGlobalOption 下发,防止绕过钳制。

URI 任务创建的兜底:aria2 错误码 28

启动配置阶段有钳制,但创建任务的路径理论上仍可能被第三方/自定义引擎拒绝——例如用户配置虽被钳到 16,引擎却连 16 都不同意,或通过其它入口携带了越界值。官方 aria2 对选项校验失败统一返回错误码 28。

精确的分类器

src/core/engine/aria2/error-utils.ts 中的 isConnectionLimitRangeError 刻意做窄匹配,避免把无关选项错误当作可重试错误静默吞掉:

export function isConnectionLimitRangeError(err: unknown): boolean {
  const message = err instanceof Error ? err.message : String(err)
  const isExpectedRange = /must\s+be\s+between\s+1\s+and\s+16\b/i.test(message)
  const identifiesConnectionOption =
    /max-connection-per-server|(?:^|\W)split(?:\W|$)/i.test(message)
  const identifiesAria2OptionError = /errorCode\s*=\s*28\b/i.test(message)
  return (
    isExpectedRange &&
    (identifiesConnectionOption || identifiesAria2OptionError)
  )
}

三个条件需同时成立:错误文本包含"必须在 1 与 16 之间"的范围描述、涉及 max-connection-per-serversplit 选项、且(或)标明 errorCode = 28

单次兼容重试

创建任务(addUri / 带 cookie 的 addUriWithCookies)统一走 src/core/engine/aria2/aria2-adapter.tsaddUriWithConnectionFallback

  1. 若此前未记录过"该引擎拒绝高连接数",先按原参数提交;
  2. 捕获到 isConnectionLimitRangeErrorcapConnectionOptions(..., 16) 确实改变了参数时,把 connectionOptionLimit 缓存为 16;
  3. 用兼容值重试一次并返回结果;
  4. 重试仍失败则原样抛出,由上层正常处理。

这样即使自定义内核在 addUri 阶段才暴露兼容性,任务也不会因一次参数被拒而直接丢失。

任务接管竞态修复:先持久化父任务

问题背景

当 aria2 中残留着上次运行的任务(比如应用异常退出、或用户手动塞入),Motrix 启动时需要"接管"这些任务:把父任务(HTTP/FTP 任务或 BT 种子任务)落库,再把子文件、Peer 等子行关联到父任务上。旧实现中父任务的持久化与子数据的保存存在时序缝隙:若子文件/Peer 先于父任务写入数据库,SQLite 的外键约束会报错,进而可能中止桌面端或 Server 的启动流程(issue #1920)。

修复后的顺序保证

发布说明明确:先持久化接管的父任务,之后才允许任务检查器保存子文件或 Peer。这一顺序约定在源码中被测试固化下来。在 src/core/task/create-task-handler.test.ts 中,测试断言新任务的生命周期严格按以下顺序执行:

expect(order).toEqual(['persist-parent', 'record-added', 'publish'])

即:persistParent(先落父任务)→ record-added(记录活动)→ publish(对外发布)。对应的实现在 src/core/task/create-task-handler.ts 的接口注释中,明确了"父任务创建后先持久化、再记录 Added 活动、最后发布"的契约。媒体任务等其它创建路径(如 src/core/task/media-task-coordinator.ts)同样遵循 parentTaskCreated(task, persist) 这一先持久化再扩散的模式。

对于从引擎侧恢复/接管的场景,src/core/task/completed-engine-task-cleanup.tsadoptpersist 也以显式注入依赖的形式出现,测试 src/core/task/completed-engine-task-cleanup.test.ts 覆盖了"先观察到的已下载完成的小文件被接管"的路径。整体上,"父行先落库、子行后写入"的时序约束由任务管理器的持久化队列统一保证,避免恢复存量 aria2 任务时中断启动。

桌面轻量模式:释放渲染进程、保留下载能力

设计目标

Electron 应用里,主窗口渲染进程往往占用可观内存。beta.22 引入"轻量模式":当主窗口隐藏且允许时,释放主窗口对应的渲染进程,同时下载引擎照常运行——因为下载引擎与任务服务运行在主进程侧,不依赖渲染进程的存活。

后台策略的单一决策函数

平台差异(Windows/Linux 保留托盘 vs macOS 依靠 Dock)被收敛进 src/main/platform/desktop-background-policy.ts

export function resolveDesktopBackgroundPolicy({
  lightweightMode,
  platform,
  runMode,
}: DesktopBackgroundPolicyInput): DesktopBackgroundPolicy {
  // HideTray 是仅 macOS 的模式,因为 Dock 在那里是可靠的重开入口。
  // Windows 与 Linux 必须始终保留托盘(即使设置文件被手工改成不支持的值)。
  const keepTray = platform !== 'darwin' || runMode !== RunMode.HideTray

  return {
    keepTray,
    // macOS 可从 Dock 重开。Windows/Linux 轻量模式强制保留托盘,
    // 因此释放最后一个渲染进程也不会失去所有可发现的重开入口。
    releaseMainWindowWhenHidden:
      lightweightMode && (platform === 'darwin' || keepTray),
  }
}

由此得到三个可验证的结论:

  • 只有启用轻量模式,且存在可靠的重开入口(macOS Dock,或 Windows/Linux 托盘)时,才会 releaseMainWindowWhenHidden
  • Windows 与 Linux 无论设置如何都保留托盘,防止"设置被手动改为不支持的值"后无路可回;
  • 条件允许时后台启动无需创建渲染进程(headless 启动),下载继续。

策略消费方见 src/main/window/window-manager.ts,配套测试 src/main/platform/desktop-background-policy.test.tssrc/main/window/window-manager.test.ts 覆盖了各平台组合;设置项本身定义在 src/shared/types/settings.ts 并由 src/shared/schemas/app-settings.ts 做 schema 校验。

新建任务窗口体验改进

本版本对新建任务窗口(Add Task 弹窗)做了两处交互修复:

  1. 剪贴板只在打开时读取:此前若窗口打开期间剪贴板内容变化(例如用户复制了别的东西),正在编辑的表单会被新剪贴板内容覆盖。修复后剪贴板仅在窗口打开瞬间采样一次。相关实现与测试位于 src/renderer/components/add-task/add-task-form.tsxsrc/renderer/components/add-task/url-textarea.tsx 及对应 .test.tsx
  2. 折叠"高级选项"恢复紧凑高度:展开再折叠高级选项后,窗口高度能回到折叠态的紧凑尺寸,不再残留展开时的高度。

测试前须知与数据安全

beta.22 是预发布软件,发布说明给出了明确的使用约束:

  • 备份:安装前请备份现有 Motrix 应用数据与下载文件;
  • v1 数据尚未验证:从 Motrix v1 数据的迁移路径还没有经过验证,切勿让本 beta 使用你唯一一份 v1 数据;
  • 并行测试:条件允许时,用独立系统账户、设备或 Docker 数据目录并行运行 v2,不要只依赖本 beta 保存重要下载;
  • 自换内核预期:如果你自行替换内置 aria2c,只要它不是同时报告 Motrix fork 版本号与 SQLite3-Persistence 特性,Motrix 就会把连接数限制为 16。

发布门禁与计划产物矩阵

beta.22 属于受保护发布门禁流程的产物:只有全部受保护的发布门禁通过后,本版本才会进入公开分发。门禁通过后的计划产物如下表:

分发方式 架构 计划产物
macOS 12 或更高 arm64(Apple Silicon)、x64(Intel) DMG 与 ZIP
Windows x64 未签名 NSIS 安装包(.exe)与 ZIP
Linux x64arm64 AppImage、DEB 与 RPM
Flatpak Native Host companion linux/x64linux/arm64 Motrix-Native-Host-2.0.0-beta.22-linux-<arch>.tar.gz
Docker Hub / GHCR linux/amd64linux/arm64 两个 registry 中不可变的 2.0.0-beta.22 镜像 tag
Snap Store 本 beta 不发布

所有容器门禁通过后,带版本的镜像引用为 docker.io/motrixapp/motrix-server:2.0.0-beta.22ghcr.io/agalwood/motrix-server:2.0.0-beta.22。存储、网络与升级方面的说明可参考仓库内的 docs/docker-server.md 部署文档。

分发细节与注意事项

针对各打包形态,发布说明补充了以下边界情况:

  • AppImage:桌面集成是 opt-in 的,只写入当前用户的 XDG 数据目录;由于 AppImage 挂载镜像内的 Native Messaging host 在镜像外没有稳定路径,浏览器扩展暂不能把任务移交给 AppImage 版本;
  • Flatpak:单独验证,不随该版本 tag 发布,GitHub 预发布版会附上其 Native Host companion 压缩包;
  • Windows:不提供 arm64 与任何 32 位包;安装包未签名,可能触发 Windows SmartScreen 警告,请只在官方预发布版发布后从官方渠道下载;
  • 容器镜像:beta tag 不可变,不会更新 lateststable 等稳定浮动 tag;
  • Snap:本 beta 不发布。预发布 Snap 运行会在源码校验后停止,不会构建 Snap、上传 Store revision 或改动 latest/edge

小结

2.0.0-beta.22 是 Motrix 2.0 在"下载内核自主可控"方向上的关键一步:通过 aria2c --version 的 fork 双标记识别把引擎差异前置暴露,通过 16 封顶钳制与自定义 profile 持久化把兼容性变成持久事实,通过错误码 28 的窄匹配单次重试保住任务;同时修复了任务接管的父行先落库时序,并在桌面端引入可按需释放渲染进程的轻量模式。如果你想在仓库中进一步验证这些行为,推荐按下面顺序阅读:

测试前请务必遵守发布说明的数据备份建议,在隔离环境中体验该预发布版本。

登录后查看全文
热门项目推荐
相关项目推荐