Sfoglia il codice sorgente

docs(subprocess): record signal error isolation and fixture safety

Tianyi Cui 3 settimane fa
parent
commit
dd55f38614

+ 2 - 2
.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md
-2026-09-12-linux-scope-direct-kill-settlement.md: d66582ad080c4b22c07d4a78f708d95aaf286da3
-2026-09-12-linux-scope-direct-kill-settlement.zh.md: a50cfeb89ec641f0696590a5092029b5898ae4d8
+2026-09-12-linux-scope-direct-kill-settlement.md: e89d8f801d6f7fb1309f50e0bc3dbad69aa797d0
+2026-09-12-linux-scope-direct-kill-settlement.zh.md: 7ab43107491ec24d5e39a62c401d245b61bb744d

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md

@@ -10,7 +10,7 @@ A failed scope signal can precede the exit notification of a direct process that
 
 ## Decision
 
-The [Linux scope owner](../../../../packages/subprocess/subprocess-local/src/linux-scope.ts) 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. 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.
+The [Linux scope owner](../../../../packages/subprocess/subprocess-local/src/linux-scope.ts) 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.
 

+ 1 - 1
.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.zh.md

@@ -10,7 +10,7 @@ scope 信号发送失败时,已接受 fallback `SIGKILL` 或已不存在的直
 
 ## 决策
 
-[Linux scope owner](../../../../packages/subprocess/subprocess-local/src/linux-scope.ts) 保留最终 scope 信号的失败。进程组 `SIGKILL` 发送成功只证明至少一个成员收到了信号,因此还必须单独确认直接 PID 的信号投递或证明它已不存在。进程组 `SIGTERM` 成功后保持单次投递,避免重复触发可捕获的 TERM handler;该确认不允许进入最终 kill 的停稳等待。直接 `SIGKILL` 成功提交或直接进程被独立确认已不存在后,owner 可以等待一次直接进程退出或启动错误的完成事件,再取得新的 scope 状态。该事件独立于输出排空、启动错误解释和受管范围的完成。直接信号发送失败后,信号零探测必须报告 `ESRCH` 才能证明进程不存在;进程仍存活或探测报告其他错误时,不允许此等待。
+[Linux scope owner](../../../../packages/subprocess/subprocess-local/src/linux-scope.ts) 保留最终 scope 信号的失败。进程组 `SIGKILL` 发送成功只证明至少一个成员收到了信号,因此还必须单独确认直接 PID 的信号投递或证明它已不存在。进程组 `SIGTERM` 成功后保持单次投递,避免重复触发可捕获的 TERM handler;该确认不允许进入最终 kill 的停稳等待。直接 PID 的请求使用 `process.kill()`,让投递错误与子进程的 `error` 事件分开;该事件也承载启动失败,并会拒绝直接结果。`ChildProcess.kill()` 可能在信号被拒绝时、真实退出发生前发出此事件。直接 `SIGKILL` 成功提交或直接进程被独立确认已不存在后,owner 可以等待一次直接进程退出或启动错误的完成事件,再取得新的 scope 状态。该事件独立于输出排空、启动错误解释和受管范围的完成。直接信号发送失败后,信号零探测必须报告 `ESRCH` 才能证明进程不存在;进程仍存活或探测报告其他错误时,不允许此等待。
 
 在直接进程停稳前,既有的 scope 为空证明仍足以完成清理。如果 active scope 查询开始时直接进程尚未退出,且该状态不能证明范围为空,owner 仅等待一次直接进程停稳,随后重新查询 scope。直接进程退出后才开始的查询不需要额外等待或再次查询。仍有进程存活或进程数未知时,保留原始信号失败。状态查询和解析错误仍然报错。
 

+ 2 - 1
packages/subprocess/subprocess-local/src/linux-scope.ts

@@ -157,7 +157,7 @@ export function probeLinuxNative(internals: LinuxScopeInternals = {}): boolean {
 
 interface DirectRange {
   running(): boolean
-  /** Acknowledge TERM delivery; KILL must confirm direct-PID delivery or absence. */
+  /** True for group TERM delivery, direct signal submission, or proven direct-PID absence. */
   signal(signal: 'SIGTERM' | 'SIGKILL'): boolean
   /** Direct exit/error settlement, independent of output drain and managed-range completion. */
   settled: Promise<unknown>
@@ -493,6 +493,7 @@ function signalChildGroup(child: ReturnType<typeof spawn>, signal: 'SIGTERM' | '
   } catch { /* A missing or inaccessible group still permits a direct-process attempt. */ }
   if (groupSignalled && signal === 'SIGTERM') return true
   // Group success can reflect another member; joining direct exit requires its own SIGKILL submission.
+  // ChildProcess.kill can emit an error that settles directOutcome before the real exit.
   return signalLinuxDirectProcess(child.pid as number, () => process.kill(child.pid as number, signal))
 }
 

+ 1 - 0
packages/subprocess/subprocess-local/tests/linux-scope.spec.ts

@@ -62,6 +62,7 @@ class FakeChild extends EventEmitter {
 
 const directories: string[] = []
 
+// Every test must isolate fake PIDs from host signals, including cases without custom mocks.
 beforeEach(() => { denyProcessGroups() })
 
 afterEach(() => {