背景
在排查 #1867 时顺带发现的一处 CPU 效率问题(非 bug,与 #1867 无关,按 AGENTS.md Prime Directive #10 单独立项)。
QuickJSScriptRunner.execute(packages/runtime/src/sandbox/quickjs-runner.ts)驱动脚本异步续体的 pump 循环大致是:
for (;;) {
await new Promise((resolve) => setImmediate(resolve)); // 让出事件循环
const pending = runtime.executePendingJobs(); // 排空 VM 任务
// 读取 __error / __result,检查 deadline
}
当脚本在 await 一个尚未 settle 的宿主调用(例如一次较慢的 DB 查询、或一次嵌套写入链)时,这一轮 executePendingJobs() 无事可做、__result 仍为 undefined,循环立即进入下一轮 —— 于是在整段等待期间以 setImmediate 的节奏空转。
实测
用一个「永不 settle」的宿主调用触发 250ms 超时,错误信息里带出了迭代计数:
hook 'slow' exceeded timeout of 250ms (after 50864 pump iterations)
即 250ms 内约 5 万次空转(≈ 20 万次/秒)。一个 30s 预算的 action 会累计约 600 万次唤醒。setImmediate 每轮都会让出事件循环,所以不是硬 busy-spin、不会阻塞其它任务,但这些唤醒纯属浪费(每次 setImmediate 调度 + executePendingJobs 的固定开销)。
建议
让宿主侧在某个 deferred 真正 settle 时通知 pump 循环(共享一个「有新结果」的信号,例如一个每次宿主 leaf settle 时 resolve 的 Promise),循环只在确有工作时才 executePendingJobs 并检查结果;deadline 用一个独立的定时器兜底,而不是靠每轮轮询。退一步的最小改动:对连续空轮引入极小的自适应退避。
需要保留的现有语义:
影响
@objectstack/runtime(hook/action 沙箱执行)。纯 CPU 效率,优先级低(P3)。
背景
在排查 #1867 时顺带发现的一处 CPU 效率问题(非 bug,与 #1867 无关,按 AGENTS.md Prime Directive #10 单独立项)。
QuickJSScriptRunner.execute(packages/runtime/src/sandbox/quickjs-runner.ts)驱动脚本异步续体的 pump 循环大致是:当脚本在
await一个尚未 settle 的宿主调用(例如一次较慢的 DB 查询、或一次嵌套写入链)时,这一轮executePendingJobs()无事可做、__result仍为undefined,循环立即进入下一轮 —— 于是在整段等待期间以 setImmediate 的节奏空转。实测
用一个「永不 settle」的宿主调用触发 250ms 超时,错误信息里带出了迭代计数:
即 250ms 内约 5 万次空转(≈ 20 万次/秒)。一个 30s 预算的 action 会累计约 600 万次唤醒。
setImmediate每轮都会让出事件循环,所以不是硬 busy-spin、不会阻塞其它任务,但这些唤醒纯属浪费(每次 setImmediate 调度 +executePendingJobs的固定开销)。建议
让宿主侧在某个 deferred 真正 settle 时通知 pump 循环(共享一个「有新结果」的信号,例如一个每次宿主 leaf settle 时 resolve 的 Promise),循环只在确有工作时才
executePendingJobs并检查结果;deadline 用一个独立的定时器兜底,而不是靠每轮轮询。退一步的最小改动:对连续空轮引入极小的自适应退避。需要保留的现有语义:
timeoutMs内跑完([P0] Hook sandbox crashes on nested cross-object writes — forces denormalized workarounds across templates #1867 的 pump-budget 回归);影响
@objectstack/runtime(hook/action 沙箱执行)。纯 CPU 效率,优先级低(P3)。