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 的理由,免得下一个扫仓的人再提一次。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions