Skip to content

超时守卫「armed 之后扔掉」还剩两处生产实例(另有两处用 unref 代替回收)—— #4813 / #4875 的清仓 #4952

Description

@xuyushun441-sys

发现自 #4875 的实现过程(PR #4950)。未认领。#4813#4875 是同一种形状的剩余实例。

#4813(PR #4874,kernel.ts 的 init/start)和 #4875(PR #4950,health-monitor.ts 的健康检查)
各修掉一处。修 #4875 时顺手扫了仓里所有 Promise.race + setTimeout 的守卫点,剩下这些:

A. 真·漏(守卫 armed 后既不 clear 也不 unref)

A1. packages/services/service-automation/src/engine.ts:4296 executeWithTimeout()

    private executeWithTimeout(
        promise: Promise< NodeExecutionResult >,
        timeoutMs: number,
        nodeId: string,
    ): Promise< NodeExecutionResult > {
        return Promise.race([
            promise,
            new Promise< NodeExecutionResult >((_, reject) =>
                setTimeout(() => reject(new Error(`Node '${nodeId}' timed out after ${timeoutMs}ms`)), timeoutMs),
            ),
        ]);
    }

调用点在 :3921,每个流程节点一根。这是三处里最值得修的:节点执行是热路径,漏的量级随
流程复杂度和触发频率线性增长,而且和 #4875 一样会把一次性进程(os CLI 里跑到 flow 的路径)
timeoutMs 钉住。

A2. packages/core/src/hot-reload.ts:275 —— 插件 destroy() 的 shutdown 守卫:

        const shutdownPromise = plugin.destroy();
        const timeoutPromise = new Promise((_, reject) => {
          setTimeout(() => reject(new Error('Shutdown timeout')), config.shutdownTimeout);
        });

        await Promise.race([shutdownPromise, timeoutPromise]);

#4813 修掉的两处一字不差。热重载路径今天调用不多,但漏法相同。

B. 用 unref() 代替回收(需要一次判定,不一定是 bug)

B1. packages/core/src/kernel.ts:430 shutdown() —— PR #4874 的 changeset 已点名这处
用的是 unref(),并写明为什么 unref() 在 init/start 那两处是错的:unref'd 的守卫不再钉住
事件循环的同时也不再是一个守卫 —— 若 hook 永不 settle 且没有别的东西撑着事件循环,Node
会在定时器触发前退出,超时被静默吞掉。shutdown 是不是同一回事需要判一次:那里的语义可能是
「进程反正要退了,吞掉也无所谓」,也可能同样应该改成 finally { clearTimeout }

B2. packages/objectql/src/engine.ts:2971 checkDriversHealth() —— 显式 unref(),
注释写着「Never hold the event loop open for a probe」。同样是 B1 的判定题:一个被静默吞掉的
探针超时会让 checkHealth 挂死时永远不返回,而不是报超时。

已经是对的(留档,免得下次重扫)

  • packages/objectql/src/hook-wrappers.ts:236 —— finally { clearTimeout }
  • packages/runtime/src/app-plugin.ts:1159 —— race 后 clearTimeout(timer)
  • packages/services/service-job/src/run-with-policy.ts:50 —— .finally(() => clearTimeout(timer))
  • packages/plugins/driver-mongodb/src/test-mongod.ts:72 —— finally { clearTimeout }

建议

A1/A2 直接按 #4874 的同形 helper 收编(try { await Promise.race(...) } finally { clearTimeout(guard) }),
回归测试照 PR #4950 的三条写:赢下 race 不留 ref'd 定时器 / 多轮不累积(fake timers 计数,能识破
unref() 式假修复)/ 真挂住时超时照常上报。B1/B2 需要维护者先判语义,再决定是否改。

拆成一个 issue 而不是四个,是因为四处共用同一条判据和同一个修法;如果 B 组的判定结论是「保持
unref()」,建议在那两处补一行注释指向 #4813 的理由,免得下一个扫仓的人再提一次。

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions