mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-24 07:31:44 +00:00
fix(process): drain Windows stdio before exit fallback settle
This commit is contained in:
@@ -27,32 +27,12 @@ function createStubChild(pid = 1234) {
|
||||
Object.defineProperty(child, "killed", { value: false, configurable: true, writable: true });
|
||||
Object.defineProperty(child, "exitCode", { value: null, configurable: true, writable: true });
|
||||
Object.defineProperty(child, "signalCode", { value: null, configurable: true, writable: true });
|
||||
let emittedClose = false;
|
||||
let emittedExit = false;
|
||||
let closedStreams = 0;
|
||||
const maybeEmitCloseAfterStreamShutdown = () => {
|
||||
if (emittedClose || !emittedExit || closedStreams < 2) {
|
||||
return;
|
||||
}
|
||||
emittedClose = true;
|
||||
child.emit("close", child.exitCode, child.signalCode);
|
||||
};
|
||||
child.stdout.on("close", () => {
|
||||
closedStreams += 1;
|
||||
maybeEmitCloseAfterStreamShutdown();
|
||||
});
|
||||
child.stderr.on("close", () => {
|
||||
closedStreams += 1;
|
||||
maybeEmitCloseAfterStreamShutdown();
|
||||
});
|
||||
const killMock = vi.fn(() => true);
|
||||
child.kill = killMock as ChildProcess["kill"];
|
||||
const emitClose = (code: number | null, signal: NodeJS.Signals | null = null) => {
|
||||
emittedClose = true;
|
||||
child.emit("close", code, signal);
|
||||
};
|
||||
const emitExit = (code: number | null, signal: NodeJS.Signals | null = null) => {
|
||||
emittedExit = true;
|
||||
child.exitCode = code;
|
||||
child.signalCode = signal;
|
||||
child.emit("exit", code, signal);
|
||||
@@ -197,7 +177,7 @@ describe("createChildAdapter", () => {
|
||||
vi.useFakeTimers();
|
||||
setPlatform("win32");
|
||||
|
||||
const { adapter, emitExit } = await (async () => {
|
||||
const { adapter, emitExit, child } = await (async () => {
|
||||
const stub = createStubChild(8642);
|
||||
spawnWithFallbackMock.mockResolvedValue({
|
||||
child: stub.child,
|
||||
@@ -216,6 +196,8 @@ describe("createChildAdapter", () => {
|
||||
});
|
||||
|
||||
emitExit(0, null);
|
||||
child.stdout?.emit("end");
|
||||
child.stderr?.emit("end");
|
||||
await vi.advanceTimersByTimeAsync(300);
|
||||
|
||||
expect(settled).toHaveBeenCalledWith({ code: 0, signal: null });
|
||||
|
||||
@@ -125,6 +125,8 @@ export async function createChildAdapter(params: {
|
||||
let forceKillWaitFallbackTimer: NodeJS.Timeout | null = null;
|
||||
let childExitState: { code: number | null; signal: NodeJS.Signals | null } | null = null;
|
||||
let windowsCloseFallbackTimer: NodeJS.Timeout | null = null;
|
||||
let stdoutDrained = child.stdout == null;
|
||||
let stderrDrained = child.stderr == null;
|
||||
|
||||
const clearForceKillWaitFallback = () => {
|
||||
if (!forceKillWaitFallbackTimer) {
|
||||
@@ -194,21 +196,46 @@ export async function createChildAdapter(params: {
|
||||
};
|
||||
};
|
||||
|
||||
const maybeSettleAfterWindowsExit = () => {
|
||||
if (
|
||||
process.platform !== "win32" ||
|
||||
childExitState == null ||
|
||||
!stdoutDrained ||
|
||||
!stderrDrained
|
||||
) {
|
||||
return;
|
||||
}
|
||||
settleWait(resolveObservedExitState(childExitState));
|
||||
};
|
||||
|
||||
const scheduleWindowsCloseFallback = () => {
|
||||
if (process.platform !== "win32") {
|
||||
return;
|
||||
}
|
||||
clearWindowsCloseFallbackTimer();
|
||||
windowsCloseFallbackTimer = setTimeout(() => {
|
||||
if (waitResult || waitError !== undefined) {
|
||||
return;
|
||||
}
|
||||
child.stdout?.destroy();
|
||||
child.stderr?.destroy();
|
||||
maybeSettleAfterWindowsExit();
|
||||
}, WINDOWS_CLOSE_STATE_SETTLE_TIMEOUT_MS);
|
||||
windowsCloseFallbackTimer.unref?.();
|
||||
};
|
||||
|
||||
child.stdout?.once("end", () => {
|
||||
stdoutDrained = true;
|
||||
maybeSettleAfterWindowsExit();
|
||||
});
|
||||
child.stdout?.once("close", () => {
|
||||
stdoutDrained = true;
|
||||
maybeSettleAfterWindowsExit();
|
||||
});
|
||||
child.stderr?.once("end", () => {
|
||||
stderrDrained = true;
|
||||
maybeSettleAfterWindowsExit();
|
||||
});
|
||||
child.stderr?.once("close", () => {
|
||||
stderrDrained = true;
|
||||
maybeSettleAfterWindowsExit();
|
||||
});
|
||||
|
||||
child.once("error", (error) => {
|
||||
rejectPendingWait(error);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user