异常退出残留lock文件导致下次打开无法正常运行 #310

Closed
opened 2026-09-09 15:52:50 +08:00 by kdletters · 1 comment
Member
No description provided.
suzmii self-assigned this 2026-09-09 15:53:46 +08:00
Member

原因定位与复现结论

异常退出后残留的锁文件里,只有项目级 .agent/project.lock 会阻塞后续使用;%APPDATA% 下的 agent-runner.lock / agent-runner.gui-owner.lock 是 OS 独占句柄锁,进程退出即释放,文件本身不会阻塞下次启动。另外客户端启动失败时既不写 startup.log 也不给任何提示,所以现场只能看到"双击没反应"。

复现 1:项目写锁残留(Rust 单测)

  • 崩溃停在 create_new 与写 payload 之间留下的 0 字节锁:把 mtime 回拨 120 秒后,仍然报 项目正在被其他写操作占用:<项目>\.agent\project.lock;原实现要等满 600 秒才回收。
  • PID 被系统复用给另一个活进程(锁写于 1 小时前):只要那个进程还活着,锁就永远不会被回收。

复现 2:真实二进制(隔离 --config-dir 启动两个实例)

  • 第二个实例在 Tauri setup 阶段 panic,exit code 101;diagnostics/startup.log 只有 startup.appdata.configure.complete,没有 startup.runner.owner-lock.failed,也没有任何弹窗。
  • 强杀第一个实例后,agent-runner.gui-owner.lock / agent-runner.lock 等文件确实残留,但下一次启动完全正常。说明残留文件不是启动失败的原因,真正原因是"锁仍然被活进程持有"。

修复

  1. .agent/project.lock 回收判据升级:锁内新增 processStartedAt;PID 存活时核对进程启动身份,身份不一致即判定 PID 复用并回收;旧锁按"进程启动时间晚于锁 createdAt 加 5 秒容差"推断;空锁 / 坏锁宽限期由 600 秒收紧到 30 秒;无法判定持有者是否存活时保持 600 秒,活持有者始终不回收。
  2. 启动诊断复活:新增 StartupLogSlot,配置目录就绪前后都能写 startup.logstartup.*.failedshow_startup_error_dialog 不再是死分支;Windows 启动失败恢复系统消息框(附诊断日志路径),其它平台写 stderr。

验证

  • project_lock_recovery 6 条回归用例通过,含"身份一致不抢锁""新鲜空锁不抢锁"两条护栏。
  • cargo test --bin genarrative-ai-game-creator-shell lock:133 passed / 0 failed / 2 ignored(2 条为需要真实 Chrome 的用例)。
  • 真实二进制双实例:第二个实例写入 startup.runner.owner-lock.failed details=AI 游戏创作界面已由同一 AppData 目录中的其他进程运行,并弹出可见提示。
  • cargo fmt --checknpm run check:encoding(4333 文件)、git diff --check 通过。

本地提交:439127e88(分支 fix/agc-stale-lock)。

未覆盖:需要真实 Chrome 的两条 ignored 用例未运行;push 与 PR 待确认后执行。

## 原因定位与复现结论 异常退出后残留的锁文件里,只有项目级 `.agent/project.lock` 会阻塞后续使用;`%APPDATA%` 下的 `agent-runner.lock` / `agent-runner.gui-owner.lock` 是 OS 独占句柄锁,进程退出即释放,文件本身不会阻塞下次启动。另外客户端启动失败时既不写 `startup.log` 也不给任何提示,所以现场只能看到"双击没反应"。 ### 复现 1:项目写锁残留(Rust 单测) - 崩溃停在 `create_new` 与写 payload 之间留下的 0 字节锁:把 mtime 回拨 120 秒后,仍然报 `项目正在被其他写操作占用:<项目>\.agent\project.lock`;原实现要等满 600 秒才回收。 - PID 被系统复用给另一个活进程(锁写于 1 小时前):只要那个进程还活着,锁就永远不会被回收。 ### 复现 2:真实二进制(隔离 `--config-dir` 启动两个实例) - 第二个实例在 Tauri setup 阶段 panic,exit code 101;`diagnostics/startup.log` 只有 `startup.appdata.configure.complete`,没有 `startup.runner.owner-lock.failed`,也没有任何弹窗。 - 强杀第一个实例后,`agent-runner.gui-owner.lock` / `agent-runner.lock` 等文件确实残留,但下一次启动完全正常。说明残留文件不是启动失败的原因,真正原因是"锁仍然被活进程持有"。 ## 修复 1. `.agent/project.lock` 回收判据升级:锁内新增 `processStartedAt`;PID 存活时核对进程启动身份,身份不一致即判定 PID 复用并回收;旧锁按"进程启动时间晚于锁 `createdAt` 加 5 秒容差"推断;空锁 / 坏锁宽限期由 600 秒收紧到 30 秒;无法判定持有者是否存活时保持 600 秒,活持有者始终不回收。 2. 启动诊断复活:新增 `StartupLogSlot`,配置目录就绪前后都能写 `startup.log`;`startup.*.failed` 与 `show_startup_error_dialog` 不再是死分支;Windows 启动失败恢复系统消息框(附诊断日志路径),其它平台写 stderr。 ## 验证 - `project_lock_recovery` 6 条回归用例通过,含"身份一致不抢锁""新鲜空锁不抢锁"两条护栏。 - `cargo test --bin genarrative-ai-game-creator-shell lock`:133 passed / 0 failed / 2 ignored(2 条为需要真实 Chrome 的用例)。 - 真实二进制双实例:第二个实例写入 `startup.runner.owner-lock.failed details=AI 游戏创作界面已由同一 AppData 目录中的其他进程运行`,并弹出可见提示。 - `cargo fmt --check`、`npm run check:encoding`(4333 文件)、`git diff --check` 通过。 本地提交:`439127e88`(分支 `fix/agc-stale-lock`)。 未覆盖:需要真实 Chrome 的两条 ignored 用例未运行;push 与 PR 待确认后执行。
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GenarrativeAI/Genarrative#310