Files
deepseek-harness/.agents/notes/rejected/simplification/2026-07-26-builtin-timer-promises-for-hand-rolled-sleeps.zh.md
T

2.9 KiB
Raw Blame History

Agent Note: 用 node:timers/promises 替代手写的可取消休眠

Status: rejected — 实现(PR #679)证伪了行为等价前提:vitest 的假时钟不拦截 node:timers/promises,这次替换用确定性的快速测试换来约 10 行删除,得不偿失

English | 中文

问题

三个包手写了用 promise 包装的定时器,而 node:timers/promises 内置模块早已提供同等能力;其他包(dsh-llm-mock-server 的 pause()、dsh-lsp-local、dsh-acp-snapshot)已经在使用该内置模块,因此这些手写副本同时也是一处一致性缺口:

  • packages/llm/llm-retry/src/index.ts 的 cancellableDelay()(约 14 行):new Promise + setTimeout + 手动添加和移除中止监听器,定时器触发时 resolve 为 true、被中止时 resolve 为 false,仅在退避等待处消费一次。
  • packages/workflow/workflow-workerthread/src/host.ts 的 sleep()(约 7 行):promise 包装、已 unref 的 setTimeout,用作 dispose(资源释放)宽限的时间上界。
  • packages/pty/pty-local/src/session.ts 的 delay()(约 4 行):朴素的 promise 包装 setTimeout,用于轮询与拆卸等待。

提案

用 import { setTimeout } from 'node:timers/promises' 替换这三处实现:

  • llm-retry:try { await setTimeout(delayMs, undefined, { signal }); /* retry */ } catch { /* abort → fail */ }。传入 signal 后,该 promise 只会因中止错误而拒绝,已提前中止的 signal 则立即拒绝;行为完全一致,包括中止时清除定时器。按仓库的空 catch 规则,这个空 catch 注明其吞下的是 abort 拒绝。
  • workflow-workerthread:setTimeout(ms, undefined, { ref: false }),语义完全等价,包括不会让事件循环保持存活。
  • pty-local:import { setTimeout as delay } from 'node:timers/promises',签名完全相同,调用点无需改动。

没有专属测试固定这些辅助函数本身;各包的行为测试套件继续通过。

曾考虑的替代方案

  • p-timeout/p-defer 一类的包。 不予采纳:内置模块恰好精确覆盖这些调用点;为一行 await 引入外部包是负收益。
  • 维持现状。 不予采纳,但理由较弱:成本确实很小,但仓库其他地方已经在用这一内置惯用法,而同一内置能力存在两个手写变体,就会招来第三个。

验收标准

  • 这三个包都不再各自定义 promise 包装的 setTimeout 辅助函数,而是都从 node:timers/promises 导入。
  • llm-retry、workflow-workerthread 与 pty-local 的测试套件原样通过(行为等价)。

风险

基本没有风险:不涉及模型可见的输出,没有平台顾虑,也不新增依赖。llm-retry 的改写把一个返回布尔值的辅助函数变成 try/catch 控制流,这是一项局部可读性判断,由实施 PR(Pull Request)裁量。