Skip to content
实验报告
待周评
切走再切回来,旧结果为什么还能冒出来?
开源工程观察 · 实验研究

切走再切回来,旧结果为什么还能冒出来?

你从 A 切到 B,又回到 A。第一次访问的请求这时才返回,它还应该更新眼前的内容吗?一个控制返回顺序的实验,说明同名工作区为何不够。

2026-09-16English →

切走再切回来,旧结果为什么还能冒出来?

先打开工作区 A,文件列表还在加载。你等不及,切到 B 看了一眼,又回到 A。

这时,第一次打开 A 时发出的请求才返回。它的工作区名称没错,查的也是文件列表。问题是:它回答的是这一次访问,还是上一次?

名称重新相同了,第一次请求却没有因此变成新请求。我们想验证的,就是程序能否分清这两次访问。

给每次访问一个能分清的标记

这个思路来自 Jinwoo-H 提交的 Orca #20914。Orca 的移动端会向运行 AI 助手的电脑请求文件信息,再保存结果,供后续查询使用。

这次改造把请求和暂存结果交给同一个管理对象。可以先把它理解成一位只负责当前使用阶段的记录员:它需要知道,哪份结果属于现在,哪份已经过时。

具体实现给请求附上当时的有效标记。切换或重置后,旧标记失效。即使又回到 A,第一次访问留下的标记也不会重新生效。代码把这样区分开的使用阶段称为“代际”。

它值得我们研究,是因为长期运行的助手会经历切换、重连和恢复。只认“还是那个工作区”,容易忽略它已经经历过变化。上游把 A→B→A 明确列为验证场景,我们据此安排了一个能控制返回顺序的实验。

让第一次的结果故意迟到

我们执行固定版本的完整请求管理模块:先发出 A 的请求,让它暂时等着;再切换范围;最后才让旧结果返回,检查它能否被存入并再次读出。

随后做一项对照:只去掉“这份标记是否仍属于当前代际”的检查,再运行同样的场景。这是为理解机制而主动改出来的版本,并非 Orca 的历史发布版本

我们安排的场景原模块去掉代际检查后
A→B→A,第一次 A 的结果迟到拒绝存入存入旧结果,之后能读到
名称没变,但主动重置拒绝存入存入旧结果,之后能读到
告诉管理对象:使用身份已换了一轮拒绝旧请求的标记存入旧结果,但新身份范围读不到
身份已经变化,却没有告诉管理对象旧结果仍能存入并读到旧结果仍能存入并读到
把请求标记交给另一个管理对象拒绝:标记不属于它仍然拒绝

前两行给出了直观答案:拒绝旧结果所需的依据,不能只有工作区名字。

第四行则提醒我们,这个保护需要输入。如果调用程序没有报告身份变化,管理对象就无从判断“现在已经换了一轮”。这一行使用的是我们故意漏报变化的构造输入,不能据此认定真实登录流程存在缺陷。

同名工作区的两次访问

图 1:第一次 A 的请求迟到时,再次访问 A 已属于新的使用阶段。来源:本轮可控时序实验,由作者绘制;不是手机现场录屏。

存下了、读到了、显示了,还隔着几步

还有一个容易误解的结果。我们先让外部范围改变,但暂时不通知管理对象。这时提交旧结果,它返回“已接收”。

接着,我们按新的范围读取,管理对象才发现变化,并清掉此前暂存的内容。旧值最终没有被读出来。

这些暂存内容,在程序里叫作“缓存”。实验说明,受测实现既在结果存入时检查,也会在下次读取时确认自己服务的还是不是原来的范围。只看“已接收”三个字,会漏掉后半段保护。

但屏幕是下一层。拿到异步结果的调用程序,如果直接拿手中的旧值更新画面,没有经过新的读取检查,就还需要自己的顺序判断。Orca 的文件搜索代码保留了这样的显示顺序检查;我们的实验没有运行手机画面,不能替整个页面作保证。

要回答“旧内容会不会冒出来”,需要沿着结果走完:谁接收它,谁读取它,谁把它放到眼前。

下一个问题:哪些变化必须告诉程序?

切换工作区是显眼的变化。重新认证、换到另一台执行电脑,也可能让之前的结果不再适用。

接下来值得追问的是:哪些变化会使当前数据过时?程序从哪里获得这些变化的真实信号?谁负责把信号交给请求管理对象?仅仅起一个“版本号”的名字,并不能替代这条来源。

对于普通使用者,可以从一个具体经历说起:切回来以后,文件列表有没有先显示新内容,又短暂跳回旧内容?当时是否发生了重连或账号切换?这样的记录能帮助开发者安排准确的复现顺序。

对于开发者,还可以进一步看一次请求要更新画面几次:如果先显示“加载中”,再显示“已完成”,同一个请求标记应该怎样约束这两次更新?这是后续设计问题,本轮没有验证这种扩展。

回到同一个地方,时间没有倒流。程序需要分清的,始终是这份结果属于哪一次访问。

给技术读者:补充时序、责任边界与固定版本

来源作者 Jinwoo-H;固定候选 89711d6f55670781d23fc1a2d4e2aecf4d725758。PR 于 2026-09-16 UTC 合入。执行完整 generation-scoped-request-owner.ts,8 个时序/边界场景在原实现与仅移除 generation 比较的反事实中各运行一次,共 16 条观察。

正文表中“请求标记”对应 lease,“使用阶段”对应 generation,调用方给出的检查范围对应 scope。原实现同时检查 lease 的 owner 归属和 generation;去掉后者不影响前者。包含身份代际的范围会改变键,因此消融场景中“接收了旧值”也不等于新范围能读到它。

补充场景还覆盖相同代际的正常提交,以及旧请求结束时的清理:旧请求结束、新请求尚在进行时,旧清理没有误删新请求的位置;后续同类读取仍共享新请求,重复加载次数为零。

上游明确说明,移动端当前没有可直接使用的协商能力代际,不能凭空制造;认证代际的边界测试也不等于真实认证路径。显示查询另有顺序检查,缓存 commit 的返回值不是对调用方手中旧值的新鲜度保证。未来状态加载器需要“加载中/完成”两次发布,来源将其列为未完成迁移点。

没有运行真实远程调用、手机界面、主机迁移或登录流程。私有 TypeScript 类型标记也不被描述为对恶意同进程 JavaScript 的安全隔离。

固定源码、全部观察与复跑探针。这些结果没有确认 CodeFlowMu 存在相同问题。

研究仓库

Last updated: