fix(cli): renders on Windows stop cancelling when an old parent's pid is reused (#5358)

* fix(cli): renders on Windows stop cancelling when an old parent's pid is reused

* test(cli): a Windows parent created in the same tick as its child stays an ancestor
This commit is contained in:
Miguel Ángel
2026-10-09 19:24:06 +00:00
committed by GitHub
parent 6b341b0d84
commit ce359d91cb
2 changed files with 34 additions and 8 deletions
+19 -8
View File
@@ -85,8 +85,14 @@ export interface ProcessAncestor {
interface ProcessRecord extends ProcessAncestor {
parentPid: number;
startedAt?: bigint | undefined;
}
const holdsReusedPid = (parent: ProcessRecord, child: ProcessRecord | undefined): boolean =>
parent.startedAt !== undefined &&
child?.startedAt !== undefined &&
parent.startedAt > child.startedAt;
function recordsToAncestors(pid: number, records: readonly ProcessRecord[]): ProcessAncestor[] {
const byPid = new Map(records.map((record) => [record.pid, record]));
const ancestors: ProcessAncestor[] = [];
@@ -94,11 +100,12 @@ function recordsToAncestors(pid: number, records: readonly ProcessRecord[]): Pro
let current = pid;
for (let depth = 0; depth < 64; depth++) {
const parent = byPid.get(current)?.parentPid;
const child = byPid.get(current);
const parent = child?.parentPid;
if (parent === undefined || parent <= 1 || visited.has(parent)) break;
visited.add(parent);
const ancestor = byPid.get(parent);
if (!ancestor) break;
if (!ancestor || holdsReusedPid(ancestor, child)) break;
ancestors.push({ pid: ancestor.pid, identity: ancestor.identity });
current = parent;
}
@@ -128,13 +135,17 @@ export function processAncestorSnapshot(pid: number): ProcessAncestor[] {
const records = output
.split(/\r?\n/)
.map((line) => line.trim().split(/\s+/))
.map(([processId, parentProcessId, creationDate]) => ({
pid: Number(processId),
parentPid: Number(parentProcessId),
identity: creationDate ? `windows:${creationDate}` : "",
}))
.map(
([processId, parentProcessId, creationDate]): ProcessRecord => ({
pid: Number(processId),
parentPid: Number(parentProcessId),
identity: creationDate ? `windows:${creationDate}` : "",
startedAt:
creationDate && /^\d+$/.test(creationDate) ? BigInt(creationDate) : undefined,
}),
)
.filter(
(record): record is ProcessRecord =>
(record) =>
Number.isInteger(record.pid) &&
record.pid > 0 &&
Number.isInteger(record.parentPid) &&
@@ -57,4 +57,19 @@ describe("Windows orphan-cleanup child-process options", () => {
expect.objectContaining({ windowsHide: true }),
);
});
it("keeps a Windows parent created in the same tick as its child", () => {
execFileSyncMock.mockImplementation(() => "400 300 333\n300 200 333\n200 1 222\n");
expect(processAncestorSnapshot(400).map((ancestor) => ancestor.pid)).toEqual([300, 200]);
});
it("stops the Windows ancestor chain at a parent pid reused by a later process", () => {
execFileSyncMock.mockImplementation(() => "400 300 444\n300 200 333\n200 999 222\n999 5 555\n");
expect(processAncestorSnapshot(400)).toEqual([
{ pid: 300, identity: "windows:333" },
{ pid: 200, identity: "windows:222" },
]);
});
});