Skip to content

Commit f4d4e62

Browse files
committed
fix(process): report tree-kill completion by callback
1 parent 7d4a037 commit f4d4e62

4 files changed

Lines changed: 44 additions & 30 deletions

File tree

packages/agent-core/src/harness/env/kill-tree.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,20 @@ export function killProcessTree(pid: number, opts?: KillProcessTreeOptions): voi
5858
export function signalProcessTree(
5959
pid: number,
6060
signal: "SIGTERM" | "SIGKILL",
61-
opts?: { detached?: boolean },
62-
): Promise<void> {
61+
opts?: { detached?: boolean; onComplete?: () => void },
62+
): void {
6363
if (!Number.isFinite(pid) || pid <= 0) {
64-
return Promise.resolve();
64+
opts?.onComplete?.();
65+
return;
6566
}
6667

6768
if (process.platform === "win32") {
68-
return signalProcessTreeWindowsAndWait(pid, signal);
69+
void signalProcessTreeWindowsAndWait(pid, signal).then(opts?.onComplete);
70+
return;
6971
}
7072

7173
signalProcessTreeUnix(pid, signal, opts?.detached !== false);
72-
return Promise.resolve();
74+
opts?.onComplete?.();
7375
}
7476

7577
function normalizeGraceMs(value?: number): number {

src/process/kill-tree.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ describe("killProcessTree", () => {
235235

236236
await withMockedPlatform("win32", async () => {
237237
const completed = vi.fn();
238-
void signalProcessTree(8989, "SIGKILL").then(completed);
238+
signalProcessTree(8989, "SIGKILL", { onComplete: completed });
239239
await Promise.resolve();
240240
expect(completed).not.toHaveBeenCalled();
241241

@@ -253,7 +253,7 @@ describe("killProcessTree", () => {
253253

254254
await withMockedPlatform("win32", async () => {
255255
const completed = vi.fn();
256-
void signalProcessTree(9090, "SIGKILL").then(completed);
256+
signalProcessTree(9090, "SIGKILL", { onComplete: completed });
257257

258258
await vi.advanceTimersByTimeAsync(2_999);
259259
expect(completed).not.toHaveBeenCalled();

src/process/supervisor/adapters/child.test.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import {
1818
const { spawnWithFallbackMock, signalProcessTreeMock, createWindowsOutputDecoderMock } = vi.hoisted(
1919
() => ({
2020
spawnWithFallbackMock: vi.fn(),
21-
signalProcessTreeMock: vi.fn(() => Promise.resolve()),
21+
signalProcessTreeMock: vi.fn(
22+
(_pid: number, _signal: string, opts?: { onComplete?: () => void }) => {
23+
opts?.onComplete?.();
24+
},
25+
),
2226
createWindowsOutputDecoderMock: vi.fn(() => ({
2327
decode: (chunk: Buffer | string) => (Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk),
2428
flush: () => "",
@@ -203,9 +207,11 @@ describe("createChildAdapter", () => {
203207
// Detachment flag is now passed to signalProcessTree so it knows whether
204208
// it can safely group-kill via -pid. (#71662)
205209
const expectedDetached = process.platform !== "win32" && !process.env.OPENCLAW_SERVICE_MARKER;
206-
expect(signalProcessTreeMock).toHaveBeenCalledWith(4321, "SIGKILL", {
207-
detached: expectedDetached,
208-
});
210+
expect(signalProcessTreeMock).toHaveBeenCalledWith(
211+
4321,
212+
"SIGKILL",
213+
expect.objectContaining({ detached: expectedDetached }),
214+
);
209215
expect(killMock).toHaveBeenCalledWith("SIGKILL");
210216
});
211217

@@ -227,9 +233,11 @@ describe("createChildAdapter", () => {
227233
adapter.kill();
228234
await Promise.resolve();
229235

230-
expect(signalProcessTreeMock).toHaveBeenCalledWith(8888, "SIGKILL", {
231-
detached: false,
232-
});
236+
expect(signalProcessTreeMock).toHaveBeenCalledWith(
237+
8888,
238+
"SIGKILL",
239+
expect.objectContaining({ detached: false }),
240+
);
233241
expect(killMock).toHaveBeenCalledWith("SIGKILL");
234242
});
235243

@@ -239,9 +247,11 @@ describe("createChildAdapter", () => {
239247
const { adapter, killMock } = await createAdapterHarness({ pid: 9999 });
240248
adapter.kill();
241249
await Promise.resolve();
242-
expect(signalProcessTreeMock).toHaveBeenCalledWith(9999, "SIGKILL", {
243-
detached: false,
244-
});
250+
expect(signalProcessTreeMock).toHaveBeenCalledWith(
251+
9999,
252+
"SIGKILL",
253+
expect.objectContaining({ detached: false }),
254+
);
245255
expect(killMock).toHaveBeenCalledWith("SIGKILL");
246256
} finally {
247257
delete process.env.OPENCLAW_SERVICE_MARKER;
@@ -382,10 +392,10 @@ describe("createChildAdapter", () => {
382392
vi.useFakeTimers();
383393
setPlatform("win32");
384394
let resolveTreeKill: (() => void) | undefined;
385-
signalProcessTreeMock.mockReturnValueOnce(
386-
new Promise<void>((resolve) => {
387-
resolveTreeKill = resolve;
388-
}),
395+
signalProcessTreeMock.mockImplementationOnce(
396+
(_pid: number, _signal: string, opts?: { onComplete?: () => void }) => {
397+
resolveTreeKill = opts?.onComplete;
398+
},
389399
);
390400

391401
const stub = createStubChild(9753);
@@ -419,10 +429,10 @@ describe("createChildAdapter", () => {
419429
vi.useFakeTimers();
420430
setPlatform("win32");
421431
let resolveTreeKill: (() => void) | undefined;
422-
signalProcessTreeMock.mockReturnValueOnce(
423-
new Promise<void>((resolve) => {
424-
resolveTreeKill = resolve;
425-
}),
432+
signalProcessTreeMock.mockImplementationOnce(
433+
(_pid: number, _signal: string, opts?: { onComplete?: () => void }) => {
434+
resolveTreeKill = opts?.onComplete;
435+
},
426436
);
427437

428438
const stub = createStubChild(9754);
@@ -450,10 +460,10 @@ describe("createChildAdapter", () => {
450460
vi.useFakeTimers();
451461
setPlatform("win32");
452462
let resolveTreeKill: (() => void) | undefined;
453-
signalProcessTreeMock.mockReturnValueOnce(
454-
new Promise<void>((resolve) => {
455-
resolveTreeKill = resolve;
456-
}),
463+
signalProcessTreeMock.mockImplementationOnce(
464+
(_pid: number, _signal: string, opts?: { onComplete?: () => void }) => {
465+
resolveTreeKill = opts?.onComplete;
466+
},
457467
);
458468

459469
const stub = createStubChild(9755);

src/process/supervisor/adapters/child.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,9 @@ export async function createChildAdapter(params: {
437437
signalProcessTree(pid, signal, { detached: childIsDetached });
438438
};
439439
const signalProcessTreeForChildAndWait = (pid: number, signal: "SIGTERM" | "SIGKILL") =>
440-
signalProcessTree(pid, signal, { detached: childIsDetached });
440+
new Promise<void>((resolve) => {
441+
signalProcessTree(pid, signal, { detached: childIsDetached, onComplete: resolve });
442+
});
441443
const kill = (signal?: NodeJS.Signals) => {
442444
const pid = child.pid ?? undefined;
443445
if (signal === undefined || signal === "SIGKILL") {

0 commit comments

Comments
 (0)