Skip to content
实验报告
待周评
限制五分钟恢复三次,为什么一次恢复就把计数清了?
开源工程观察 · 实验研究

限制五分钟恢复三次,为什么一次恢复就把计数清了?

Orca 的原模块对照实验显示,保护措施可能在自己的清理路径上失效;关键不在增加重试限制,而在让动作和额度读取同一个对象事实。

2026-09-10English →

限制五分钟恢复三次,为什么一次恢复就把计数清了?

终端画面失去响应,但后台进程还活着。软件可以重建显示区域,重新接上原来的进程,帮助用户继续工作。为了避免恢复本身变成死循环,它设置了冷却时间,也限制每个标签页五分钟内最多恢复三次。

这样的保护看起来已经很完整:有计数、有时间窗口,还有退出后的清理。

问题偏偏出在清理。一次成功恢复,可能紧接着删除它刚写下的计数。下一次请求到来时,系统看到的又是一张白纸。

两份列表,对同一个对象给出相反答案

Orca 是一款组织终端与 Agent 工作的桌面工具。PR #19745描述过一次 Windows 崩溃:作者报告八个标签页在约 122.4 秒内发生 8,878 次重建。我们没有取得原始崩溃包;这个数字是上游事故报告,不是我们的实测。

我们关注的是公开代码能否解释这个结果。它维护了两种索引:一份保存终端行,另一份保存统一界面的标签信息。它们通常对应,但在某些路径上会不同步。

实际重建终端时,程序查询终端行;释放恢复预算时,旧代码却查询统一界面索引。于是同一个对象可以同时满足两句话:

  • 在执行重建的那份列表里,它存在,恢复成功。
  • 在清理逻辑看的列表里,它不存在,预算应该删除。

两段代码各自读到了一个明确结果,组合起来却让恢复动作抹去了自己的历史。

不启动整个桌面,也能验证这一段因果

我们固定修复前后的提交,加载完整恢复模块及对象查找模块,保留原来的预算计算、实例注销、重建与 显示实例代次(generation)的更新逻辑。桌面状态存储的外壳、时间、定时器、伪终端(PTY)和日志用可控测试替身代替,没有运行 Electron,也没有制造显存耗尽。

测试每次都经过“注册实例—请求恢复—注销实例”的路径。先保留终端行,再故意让界面索引缺失,观察恢复次数是否仍被限制。两种请求节奏分别检查不同约束:10 毫秒间隔检查冷却,16 秒间隔越过冷却、检查五分钟累计上限。

输入场景请求次数修复前恢复数候选修复恢复数
两份索引一致,每 10ms 一次20011
终端行存在、界面索引缺失,每 10ms 一次2002001
同样的索引差异,每 16s 一次10103

两轮结果一致。第一行说明原来的限流并非完全不起作用;只要清理逻辑还能看见对象,冷却就有效。第二行说明索引差异让每次恢复都获得新预算。第三行则排除了“只是请求太快”的解释:越过冷却后,旧代码仍无法累计到上限,修复后才停在三次。

索引差异下的恢复次数对照

图 1:图中的数字来自模块接缝实验,表示原函数在内存 store 中成功重建终端行的次数,不是生产崩溃统计。 来源:本轮固定源码实验的第 1、2 轮保存观测。

修复的关键,是同一个判断来源

候选修复没有降低三次上限,也没有加一层新的计数器。它让“该终端还能否重建”和“该终端是否已经消失”使用同一个终端行查找逻辑。

这保留了原本有意义的预算:显示实例被替换,不等于承担恢复额度的终端对象已经结束。只要那个对象仍可继续产生恢复动作,它已经消耗的额度就不应因另一份界面投影缺失而清零。

这个做法也有清楚的范围。两份索引本身仍可能不同步;修复的是恢复预算使用哪一份事实,而不是统一所有界面状态。把局部修复说成“所有索引一致性问题都已解决”,同样会越过证据。

还要证明它没有把正常恢复一起关掉

如果测试只证明风暴消失,粗暴禁止所有恢复也能通过。我们因此保留了三组反向检查。

对照两个版本的结果
夹具删除真实终端行,注销后再创建同 ID 的行新对象可以恢复,两次申请共恢复两次
在 0、16、32、48、300.001 秒申请前三次接受,第四次受限,窗口到期后再次接受,共四次
一次成功后再次提交旧显示实例代次两次请求只接受一次

这里的“关闭”是夹具移除终端行,不是操作真实 UI 的端到端测试。它验证预算释放分支;窗口对照验证额度不会永久锁死;旧 generation 对照验证迟到请求不会接着作用于新显示实例。

这些检查把修复限定在恰当位置:该留下的历史要留下,该结束的对象仍能清理,该恢复的后续窗口仍可恢复。

重试预算也是需要维护的工作事实

这个案例容易让人想到“再加一层限流”。更值得检查的却是已有保护的撤销条件:谁能删掉历史?依据的是实际执行对象,还是可能暂时缺失的界面、缓存或查询投影?

同类问题可以出现在重试次数、失败锁定、费用额度和恢复令牌中。每次动作都会产生新的记录;如果动作的清理路径能轻易抹去记录,再严的数值上限也只是纸面约束。

这并不意味着所有预算都必须永远持久化。它意味着预算的生存期应覆盖它所约束的对象,而释放必须有与执行相容的依据。界面组件的消失可以结束一个显示实例,却未必有权结束后台对象的历史。

本轮支持的工程判断是:保护措施不仅要正确地记录一次动作,还要在下一次动作到来前,保住那条记录。 审查清理路径,往往与审查准入条件一样重要。

English · 实验方法、固定版本与逐轮结果

Last updated: