fix(process): settle launcher exit without dropping descendant lineage

Refresh a mismatched launcher snapshot only when a new live process table proves its PID absent. Preserve uncertainty for detached descendants and continue checking ownership of surviving group members before signaling. Index retained lineage membership instead of rescanning it for each refreshed process.

Adapted from #1357. Validation: 71 source tests passed, one Linux-only identity case skipped; 32 real process-boundary tests passed on macOS.

Co-authored-by: You Wish <29498139+Y0UW1SH@users.noreply.github.com>
This commit is contained in:
morluto
2026-10-11 10:55:03 +08:00
parent e5bc4e85ec
commit 80896a1d64
4 changed files with 252 additions and 42 deletions
+4 -4
View File
@@ -6,9 +6,9 @@ export const liveProcesses = (
): readonly ProcessTableEntry[] =>
members.filter(({ state }) => !state.startsWith("Z"));
/** Return every live-table descendant of a launcher in breadth-first order. */
/** Return every live-table descendant of the selected roots in breadth-first order. */
export const descendantsOf = (
launcherPid: number,
rootPids: readonly number[],
processes: readonly ProcessTableEntry[],
): ProcessTableEntry[] => {
const childrenByParent = new Map<number, ProcessTableEntry[]>();
@@ -18,8 +18,8 @@ export const descendantsOf = (
childrenByParent.set(process.parentPid, siblings);
}
const descendants: ProcessTableEntry[] = [];
const pending = [...(childrenByParent.get(launcherPid) ?? [])];
const visited = new Set<number>([launcherPid]);
const pending = rootPids.flatMap((pid) => childrenByParent.get(pid) ?? []);
const visited = new Set<number>(rootPids);
for (const process of pending) {
if (visited.has(process.pid)) continue;
visited.add(process.pid);