
T3 Code 服务器后台服务更新机制解析launcher 仲裁、试运行提交边界与 SQLite 回滚【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code导读本文以 docs/internals/server-updates.md 为骨架结合 T3 Code 仓库中 serviceLauncher.ts、serviceProtocol.ts、selfUpdate.ts 等源码系统讲解 T3 Code 后台服务background service的更新架构为什么更新只能由 stable launcher 仲裁、试运行trial为何必须越过提交边界才允许对外就绪、SQLite 三文件快照如何让迁移可回滚、客户端如何通过 update ID 区分替换成功与回滚。读完你将掌握整套服务更新的状态机、IPC 协议与崩溃恢复规则可直接对照源码逐行验证。架构总览谁有资格更新服务T3 Code 的后台服务并非由服务进程自己更新自己。从源码看整个更新流程围绕一个常驻的stable launcher稳定启动器展开其完整实现位于 apps/server/src/serviceLauncher.tslauncher 是被 systemdLinux或 launchdmacOS选中的服务运行时service runtime的唯一持有者也是服务持久状态durable service state的唯一写入方服务子进程server child只通过继承的 IPC 通道请求更新永不重写自己的服务定义service definition也不选择自己的替代者本地服务命令如t3 service系列命令只允许在服务停止期间替换 launcher 与状态文件前台 CLI 进程不进行自我更新foreground CLI processes do not self-update。Launcher 之所以必须稳定注释写得很清楚它要跨服务版本存活keep working across server versions因此 serviceLauncher.ts 只用 Node 内置模块node:child_process、node:fs、node:crypto等不依赖任何 Effect 运行时——它是整个可执行体中唯一不能假设其余部分可加载的组件。Launcher 的入口要求环境变量T3CODE_HOME读取baseDir/runtime/service-state.json由SERVICE_STATE_FILE常量定义见 serviceProtocol.ts中的ServiceState后进入运行循环状态文件无效时直接抛错而不是猜测该启动哪个运行时。运行时目录与精确版本安装服务更新针对的是**精确版本exact version**的不可变运行时。每个目标版本以解压后的 release 归档形式固定在baseDir/runtime/versions/version/ ├── t3 # 可执行文件Windows 为 t3.exe └── .install-complete # 安装完成哨兵文件内容为该版本号路径构造见 serviceLauncher.ts 中的runtimePaths安装事务则集中在 pinnedRuntime.ts精确版本安装使重启不依赖 npm 缓存驱逐cache eviction或漂移的发布 tag安装与预检preflight都在暂存staging阶段完成之后才发布不可变运行时哨兵文件只在解压与校验成功后写入——仅检查可执行文件存在不够因为 tar 会先写出可执行文件再写出原生包被中断的安装可能留下看似完整实则损坏的目录树pinnedRuntime.ts安装全程加信号量串行化pinnedRuntimeInstallLock超时上限为 10 分钟。runtimeExists校验serviceLauncher.ts同时要求入口文件存在且哨兵内容与版本号一致两者任一失败都视为该运行时缺失或不完整。预检为什么协议版本决定能否升级文档强调预检检查 launcher 协议因为需要新回滚保证的目标版本不能安全地运行在旧 launcher 之下。升级 launcher 本身需要一次本地服务更新local service update。预检实现在 servicePreflight.ts目标运行时以__service-preflight子命令被拉起见 selfUpdate.tst3 __service-preflight --database-path dbPath --launcher-protocol protocol若传入的 launcher 协议不等于当前SERVICE_LAUNCHER_PROTOCOL预检返回blocked原因固定为This release requires a newer T3 Code service launcher. Update it on the server machine.只有status ready且返回版本与目标版本一致才算通过返回的 JSON 需能被decodeServicePreflightResult完整解码否则判定暂存失败。预检是暂存阶段installing之前的downloading阶段的一部分超时 30 秒。它把目标运行时是否具备新协议要求的回滚能力这个问题前置到切流之前解决。提交边界Commit Boundary试运行的三阶段状态机文档将更新分为三个核心阶段pending → trialprepared→ committed / rolled-back / failed。对应状态机记录在 serviceProtocol.ts 的ServiceState与ServiceUpdateRecord中。1. 记录 pending 后才确认子进程通过 IPC 发送request-update携带targetVersion与dbPathlauncher 在 serviceLauncher.ts 的#handleUpdateRequest中做完整校验校验项拒绝原因reason请求方必须是 active 角色Only the active server can request an update.请求方版本必须等于状态中的 activeVersionThe requesting server is not the selected active version.不能已有 pending 更新Another server update is already pending.目标必须是精确 SemVer禁止 dist-tag/范围/路径The requested target is not an exact version.目标必须更新Remote updates must select a newer server version.数据库路径必须绝对路径The requested database path is not absolute.目标运行时必须已完整安装The requested target runtime is missing or incomplete.通过校验后launcher先持久化写盘ServiceState { update: pending }再回复update-accepted携带随机生成的updateId。也就是说确认发生在持久化之后防止确认后崩溃导致以为已受理、实际无记录。精确版本的正则与比较器见 serviceProtocol.ts 与compareExactServiceVersionsBigInt 实现构建元数据被忽略。随后 launcher 等待HANDOFF_DELAY_MS 2000ms再终止旧子进程并启动试运行#beginTrial→#startTrial。2. 试运行必须越过激活门activation gate文档给出了严格的就绪定义试运行必须完成迁移、获取依赖、绑定 HTTP、把所有长驻根long-running root停在激活门activation gate之后才能上报prepared。仅靠监听器已经起来并不能证明运行时已具备提交条件——监听器可能在关键获取完成之前就开始接受流量。prepared上报携带updateIdserviceProtocol.ts。Launcher 收到后校验角色必须是 trial、状态仍为 pending、updateId 匹配、目标版本匹配serviceLauncher.ts。一旦满足launcher清除PREPARED_TIMEOUT_MS 120_000ms的试运行超时定时器持久化提交activeVersion更新为目标版本、update.status置为committed写盘丢弃数据库快照回复committed携带 updateId此后子进程才允许释放激活门、接受命令、发布 ready。顺序不可颠倒先写盘提交再放行子进程。试运行失败或超时prepared-timeout则回到#returnToPrevious把状态置为rolled-back/failed并重启旧版本提交之后目标版本成为权威版本此后按服务管理器systemd/launchd的常规重启策略运行。3. 状态写入的持久化方式所有运行时状态转换都使用同目录替换same-directory replacement写入临时文件 →fsync文件 →rename覆盖 →fsync目录serviceLauncher.ts。syncDirectory对 Windows 做了兼容NTFS 自行日志化 rename目录 fsync 会以 EPERM 失败被吞掉。状态文件损坏或协议不匹配时readServiceState直接抛错阻止启动绝不猜测该启动哪个版本。数据库回滚SQLite 三文件快照文档的关键点旧子进程退出后launcher 对 SQLite 的主文件、WAL、共享内存文件shared-memory file做快照从而让试运行的迁移在没有 down migration的情况下也可逆。实现要点serviceLauncher.ts三件套由DB_FILE_SUFFIXES [, -wal, -shm]定义备份到baseDir/runtime/db-backup/updateId/快照每个 update 只做一次backupDatabaseOnce若发现备份目录已存在则直接跳过因为重启后的 launcher 可能面对的是同一试运行进程上次尝试留下的数据库写入覆盖它可能把失败尝试的变更混进快照快照先写入.staging临时目录、逐文件fsync后整体rename并fsync父目录保证快照原子可见回滚前先写持久化恢复标记restore marker.restore-pending再覆盖三个文件并逐个 fsync——若恢复中途崩溃重启后 launcher 在#recover里检测到标记就优先把恢复做完任何版本启动前恢复都必须先收尾对应 reasonrollback-interrupted备份目录要保留到提交commit为止或直到恢复与终态回滚记录都已持久化提交成功后调用discardDatabaseBackup清理。回滚的完整路径#returnToPreviousserviceLauncher.ts终止试运行 → 恢复数据库备份 → 持久化rolled-back/failed终态activeVersion回到 fromVersion→ 丢弃备份 → 启动旧版本为 active。边界说明附件attachments及其他 SQLite 之外的文件不在此回滚边界内。文档明确将快照语义限定为让 trial migrations 可逆而非全量文件系统回滚。崩溃恢复矩阵#recover每次 launcher 启动包括更新中途崩溃后都走#recoverserviceLauncher.ts启动时观察到的状态恢复动作状态中无 pending 更新丢弃残留备份直接以 activeVersion 启动pending 且存在.restore-pending标记先完成恢复置failedreason:rollback-interrupted回滚到旧版本pending 且目标运行时缺失置failedreason:target-runtime-missing回滚到旧版本pending 且目标运行时完整重新进入试运行#startTrial启动时还会清除.service-stopping停止标记该标记是 launcher 在显式停止前同步写入的用于让子进程区分服务要关停与launcher 即将启动我的替代者这两种场景serviceProtocol.ts新 launcher 启动意味着服务重新运行旧的停止标记必须作废。客户端确认update ID 而非重连即成功一个被接受的更新仍是 pending。文档的提醒很关键重连本身无法区分替换成功与回滚。因此客户端把 launcher 的 update ID 与重连后的 ready 事件关联起来再检查 outcomecommitted / rolled-back / failed与目标版本老版本服务器没有 update ID则保留仅按版本号关联version-only correlation的降级路径。协议层面对此提供了完整支撑ServiceLauncherChildMessage中的prepared与ServiceLauncherParentMessage中的update-accepted/committed/update-rejected全部携带updateIdserviceProtocol.ts。子进程侧的ServiceLauncherClientserviceLauncherClient.ts在继承的 IPC 上做请求-应答交换30 秒无响应判定timeoutprepareTrial仅接受committed且 updateId 与 pending 匹配的回复否则视为不可能回复。启动上下文通过环境变量T3_SERVICE_LAUNCHER_CONTEXT注入子进程SERVICE_LAUNCHER_CONTEXT_ENVdecodeServiceLauncherContext会强校验childVersion与当前 package 版本一致版本不符直接判定version-mismatch——保证子进程不会在错误的身份下启动。拒绝原因即错误面update-rejected携带的 reason 会原样透传ServiceLauncherRejectedError的 message 就是 launcher 的拒绝理由serviceLauncherClient.ts。未安装后台服务时capability 为null远端更新会直接失败并提示Remote updates require the T3 Code background service. Run t3 service install on the server machine.见 selfUpdate.ts。桌面端Desktop更新独立的两阶段交接桌面更新与 boot-service 更新走完全不同的两阶段交接原因很朴素安装桌面应用会停掉它内置的 bundled backend若后端先关机唯一成功的 RPC 结果可能丢失。实现位于 DesktopAppUpdate.ts 与 selfUpdate.ts 的withRunningThreadContinuation准备阶段后端被桌面 app 启动、通过桌面遥测控制 FD 通信调用 desktop app 的更新流程等到ready-to-install状态上报后返回desktopUpdateToken即本次请求的requestId此时连接仍然存活DesktopAppUpdate.ts提交阶段客户端只有在收到 token 之后才调用commitDesktopUpdate(requestId)提交该 token——否则后端关机可能恰好发生在唯一的成功 RPC 结果之前导致结果丢失客户端随后必须在重连后观察 prepared 版本确认安装生效若安装失败桌面端会重启被停止的后端并针对同一 token 回放失败replay the failure for the same token——token 以HashSet形式保存在desktopContinuationTokens中未提交的 token 在失败时会重新加回集合保证失败的更新可重试且幂等。commitDesktopUpdate还通过handoffAccepted回调与不可中断块Effect.uninterruptible保证提交请求一旦发出就不会被中断取消desktopUpdates订阅先于请求发出Subscribe before sending the request so a fast first report cannot be missed防止快速上报被错过。关键常量与配置速查常量值出处SERVICE_LAUNCHER_PROTOCOL2协议 2 支持试运行前快照 SQLiteserviceProtocol.tsSERVICE_STATE_FILEservice-state.jsonserviceProtocol.tsSERVICE_STOP_MARKER_FILE.service-stoppingserviceProtocol.tsHANDOFF_DELAY_MS2_000serviceLauncher.tsPREPARED_TIMEOUT_MS120_000serviceLauncher.tsTERMINATE_GRACE_MS5_000宽限后 SIGKILLserviceLauncher.tsIPC 应答超时30 秒serviceLauncherClient.ts暂存预检超时30 秒selfUpdate.ts运行时安装超时10 分钟pinnedRuntime.ts数据库快照件数3主文件、-wal、-shmserviceLauncher.ts恢复标记.restore-pendingserviceLauncher.ts总结T3 Code 的服务更新以launcher 是状态唯一写入方为不可动摇的根基精确版本暂存 协议预检解决能不能升pending 先持久化再确认 试运行越过激活门才提交解决何时算成功SQLite 三文件一次性快照 持久恢复标记解决失败怎么退update ID 关联解决客户端怎么确认。而桌面端用 token 两阶段交接解决安装即断连的最后一公里。这套设计的每一处都可在上述源码文件中逐行验证也是理解t3 service命令族与后台服务可靠性的最佳入口。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考