feat(host-agent): add single-instance lock to prevent duplicate-process dispatch races

Acquire an exclusive, non-blocking filelock on the identity state directory
as the first action of create_application(), before resolve_host_identity()
or any enrollment/heartbeat side effect. A second process against the same
identity_path exits immediately with InstanceAlreadyRunningError naming the
lock path; the lock releases automatically on any process exit (including
SIGKILL) via OS-level advisory locking, and explicitly during run_async()'s
shutdown finally block. filelock is promoted from transitive to direct
dependency (version unchanged at 3.29.7).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 15:49:30 +08:00
co-authored by Claude Opus 4.6
parent 99bde4febb
commit d00ada67a5
9 changed files with 444 additions and 102 deletions
+18
View File
@@ -404,6 +404,24 @@ Host Agent 会在本机回环地址提供 Console。必须保留并保护 `tasks
直接注册意味着任何能访问该云端地址的设备都能自行注册成为 Host,没有审批环节,
也没有限流保护;这一取舍依赖网络边界(防火墙/反向代理)而非应用层限制。
### 重复实例保护
Host Agent 启动时会先尝试获取一个独占的本地文件锁(位于
`tasks/host_agent.lock`,与 `host_identity.json` 同目录),确保同一个 identity
状态目录下同一时刻只有一个 Host Agent 进程在运行。这是为了避免两个进程用同一个
`host_id` 同时心跳和 claim,导致任务派发落到从未注册过该设备的进程上(典型的
`DeviceNotFoundError` 事故场景)。
如果启动时锁已被另一个仍在运行的进程持有,Host Agent 会立即以非零退出码退出,
stderr 打印 `error: another Host Agent instance is already running ...`,**不会**
联系云端、不会触发任何 enrollment。处理方式:
1.`lsof tasks/host_agent.lock`(macOS)/ 任务管理器(Windows)或
`pgrep -fa device-host-agent` 找到仍在运行的旧进程。
2. 确认旧进程应被停止后,再 `kill` 它(或等它的 graceful shutdown 完成)。
3. 重新启动 Host Agent。崩溃/被 `kill -9` 的旧进程退出时 OS 会自动释放文件锁,
不需要手动删除 `host_agent.lock`
### 使用本地 Web Console
Host Agent 启动时会同时启动本地 Web Console,用于在这台 Mac 上直接查看和管理