mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 01:22:57 +00:00
fix(e2e): bound kitchen sink gateway teardown
This commit is contained in:
@@ -28,6 +28,8 @@ const INSTALL_TIMEOUT_MS = readPositiveInt(
|
||||
);
|
||||
const RPC_TIMEOUT_MS = readPositiveInt(process.env.OPENCLAW_KITCHEN_SINK_RPC_CALL_MS, 60000);
|
||||
const MAX_RSS_MIB = readPositiveInt(process.env.OPENCLAW_KITCHEN_SINK_MAX_RSS_MIB, 2048);
|
||||
const GATEWAY_TEARDOWN_GRACE_MS = 10000;
|
||||
const GATEWAY_TEARDOWN_KILL_GRACE_MS = 2000;
|
||||
const OUTPUT_CAPTURE_CHARS = readPositiveInt(
|
||||
process.env.OPENCLAW_KITCHEN_SINK_OUTPUT_CAPTURE_CHARS,
|
||||
1024 * 1024,
|
||||
@@ -537,18 +539,34 @@ async function startGateway(runner, port, env, logPath) {
|
||||
return child;
|
||||
}
|
||||
|
||||
async function stopGateway(child) {
|
||||
if (!child || child.exitCode !== null) {
|
||||
export async function stopGateway(child, options = {}) {
|
||||
if (!child || child.exitCode !== null || child.signalCode !== null) {
|
||||
return;
|
||||
}
|
||||
const teardownGraceMs = Math.max(0, options.teardownGraceMs ?? GATEWAY_TEARDOWN_GRACE_MS);
|
||||
const killGraceMs = Math.max(0, options.killGraceMs ?? GATEWAY_TEARDOWN_KILL_GRACE_MS);
|
||||
const exited = new Promise((resolve) => child.once("exit", resolve));
|
||||
const waitForExit = async (ms) =>
|
||||
child.exitCode !== null || child.signalCode !== null
|
||||
? true
|
||||
: await Promise.race([exited.then(() => true), delay(ms).then(() => false)]);
|
||||
|
||||
signalGateway(child, "SIGTERM");
|
||||
const started = Date.now();
|
||||
while (child.exitCode === null && Date.now() - started < 10000) {
|
||||
await delay(100);
|
||||
if (await waitForExit(teardownGraceMs)) {
|
||||
return;
|
||||
}
|
||||
if (child.exitCode === null) {
|
||||
signalGateway(child, "SIGKILL");
|
||||
signalGateway(child, "SIGKILL");
|
||||
if (await waitForExit(killGraceMs)) {
|
||||
return;
|
||||
}
|
||||
releaseUnsettledGatewayChild(child);
|
||||
}
|
||||
|
||||
function releaseUnsettledGatewayChild(child) {
|
||||
child.stdin?.destroy?.();
|
||||
child.stdout?.destroy?.();
|
||||
child.stderr?.destroy?.();
|
||||
child.unref?.();
|
||||
}
|
||||
|
||||
function signalGateway(child, signal) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
runCommand,
|
||||
sampleProcess,
|
||||
sampleWindowsProcessByPort,
|
||||
stopGateway,
|
||||
summarizeProcessSamples,
|
||||
usesBuiltOpenClawEntry,
|
||||
} from "../../scripts/e2e/kitchen-sink-rpc-walk.mjs";
|
||||
@@ -39,6 +41,36 @@ describe("kitchen-sink RPC isolated state", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("kitchen-sink RPC gateway teardown", () => {
|
||||
it("releases gateway handles when the process ignores teardown signals", async () => {
|
||||
const child = new EventEmitter() as EventEmitter & {
|
||||
exitCode: number | null;
|
||||
kill: ReturnType<typeof vi.fn>;
|
||||
signalCode: NodeJS.Signals | null;
|
||||
stderr: { destroy: ReturnType<typeof vi.fn> };
|
||||
stdin: { destroy: ReturnType<typeof vi.fn> };
|
||||
stdout: { destroy: ReturnType<typeof vi.fn> };
|
||||
unref: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
child.exitCode = null;
|
||||
child.signalCode = null;
|
||||
child.kill = vi.fn(() => true);
|
||||
child.stderr = { destroy: vi.fn() };
|
||||
child.stdin = { destroy: vi.fn() };
|
||||
child.stdout = { destroy: vi.fn() };
|
||||
child.unref = vi.fn();
|
||||
|
||||
await stopGateway(child, { killGraceMs: 1, teardownGraceMs: 1 });
|
||||
|
||||
expect(child.kill).toHaveBeenNthCalledWith(1, "SIGTERM");
|
||||
expect(child.kill).toHaveBeenNthCalledWith(2, "SIGKILL");
|
||||
expect(child.stdin.destroy).toHaveBeenCalledOnce();
|
||||
expect(child.stdout.destroy).toHaveBeenCalledOnce();
|
||||
expect(child.stderr.destroy).toHaveBeenCalledOnce();
|
||||
expect(child.unref).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe("kitchen-sink RPC command output capture", () => {
|
||||
it("keeps a bounded tail and tracks truncated output", () => {
|
||||
const first = appendBoundedOutput({ text: "", truncatedChars: 0 }, "abcdef", 5);
|
||||
|
||||
Reference in New Issue
Block a user