Join direct termination before rejecting Linux scope cleanup
等待直接进程终止后再判定 Linux scope 清理失败
A failed scope signal can precede the exit notification of a direct process that accepted fallback `SIGKILL` or has already disappeared. Reporting the signal failure from an active scope observation during that interval can reject cleanup while termination remains in progress. The direct process's exit alone cannot prove that its descendants have stopped.
English
Problem
A failed scope signal can precede the exit notification of a direct process that accepted fallback SIGKILL or has already disappeared. Reporting the signal failure from an active scope observation during that interval can reject cleanup while termination remains in progress. The direct process's exit alone cannot prove that its descendants have stopped.
Decision
The Linux scope owner retains a failed final scope signal. A successful process-group SIGKILL proves delivery to at least one member, so the direct PID requires its own signal acknowledgement or absence proof. A successful group SIGTERM remains a single delivery to avoid repeating a catchable TERM handler; its acknowledgement does not authorize the final-kill settlement wait. Direct-PID requests use process.kill() so delivery errors remain separate from the child's error event, which also carries launch failures and rejects the direct outcome. ChildProcess.kill() can emit that event on a denied signal before the real exit. Successful direct SIGKILL submission or independently proven direct-process absence permits one wait for the direct process's exit or launch-error settlement before a fresh scope observation. This event is independent of output draining, startup-error interpretation, and managed-range completion. After rejected direct signaling, a signal-zero probe must report ESRCH to establish absence; a surviving process or another probe error permits no such wait.
Existing scope-emptiness proofs remain sufficient before direct settlement. When an active scope observation began before direct exit and cannot prove emptiness, the owner consumes the direct settlement wait once and then queries the scope again. An observation begun after direct exit requires no extra wait or query. A surviving range or unknown process count retains the original signal failure. State-query and parsing errors remain failures.
The native-containment decision continues to own descendant membership and the separation between direct outcomes and whole-range quiescence. The hosted-image fixture policy retains its existing test budgets; those budgets do not establish signal completion.
Alternatives considered
Reject on the first active observation. Rejected because successful direct signaling requests termination but does not synchronously deliver its exit notification.
Increase graces or repeat observations without a completion event. Rejected because elapsed time cannot establish that the signaled process has exited.
Treat direct exit as successful cleanup. Rejected because descendants can outlive their direct parent and remain in the scope.
Consequences
Cleanup joins direct-process settlement only after a successful signal or proven absence while preserving independent scope verification. Configured grace periods and polling budgets remain unchanged. The wait can delay a signal error until direct settlement; it cannot convert a surviving or unobservable managed range into success.
中文
问题
scope 信号发送失败时,已接受 fallback SIGKILL 或已不存在的直接进程可能尚未送达退出通知。在此期间根据 active scope 状态报告信号失败,会让仍在终止过程中的清理被判为失败。直接进程退出本身也不能证明其后代已停止。
决策
Linux scope owner 保留最终 scope 信号的失败。进程组 SIGKILL 发送成功只证明至少一个成员收到了信号,因此还必须单独确认直接 PID 的信号投递或证明它已不存在。进程组 SIGTERM 成功后保持单次投递,避免重复触发可捕获的 TERM handler;该确认不允许进入最终 kill 的停稳等待。直接 PID 的请求使用 process.kill(),让投递错误与子进程的 error 事件分开;该事件也承载启动失败,并会拒绝直接结果。ChildProcess.kill() 可能在信号被拒绝时、真实退出发生前发出此事件。直接 SIGKILL 成功提交或直接进程被独立确认已不存在后,owner 可以等待一次直接进程退出或启动错误的完成事件,再取得新的 scope 状态。该事件独立于输出排空、启动错误解释和受管范围的完成。直接信号发送失败后,信号零探测必须报告 ESRCH 才能证明进程不存在;进程仍存活或探测报告其他错误时,不允许此等待。
在直接进程停稳前,既有的 scope 为空证明仍足以完成清理。如果 active scope 查询开始时直接进程尚未退出,且该状态不能证明范围为空,owner 仅等待一次直接进程停稳,随后重新查询 scope。直接进程退出后才开始的查询不需要额外等待或再次查询。仍有进程存活或进程数未知时,保留原始信号失败。状态查询和解析错误仍然报错。
原生 containment 决策 继续负责后代成员关系,以及直接结果与整个范围完全停稳的区分。托管镜像 fixture 策略 保留既有测试预算,但这些预算不能证明信号处理已经完成。
考虑过的替代方案
首次观察到 active 状态就报错。 否决,因为直接信号发送成功只是请求终止,不会同步送达退出通知。
增大宽限期或在没有完成事件时重复查询。 否决,因为经过的时间不能证明收到信号的进程已经退出。
将直接进程退出视为清理成功。 否决,因为后代可以在直接父进程退出后继续存活于 scope 内。
影响
清理只在信号成功发送或已证明进程不存在后等待直接进程停稳,同时保留独立的 scope 验证。配置的宽限期和轮询预算保持不变。此等待可能将信号错误推迟到直接进程停稳后报告,但不能让仍有存活进程或无法观察的受管范围被判为成功。