mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
test: deflake the Gateway waiter lifetime and Bun plugin retention fixtures (#160200)
- server.request-lifetime: the default non-ephemeral test client starts a session-history prewarm (`ws:preauth`) alongside `ws:agent.wait`, so the "exactly one active root work" assertion raced it (seen as `2 !== 1` in CI). The waiter cases now connect as an ephemeral CLI client; failure messages name the active holders. - runtime.retention: under Bun, forced collections inside Promise continuations can leave JavaScriptCore's microtask stack holding completed async values, so WeakRef checks intermittently saw retained instances. Collect inside native setImmediate callbacks instead; assertions unchanged.
This commit is contained in:
parent
7fac9b18dc
commit
c136ec6314
2 changed files with 26 additions and 4 deletions
|
|
@ -6,6 +6,10 @@ import path from "node:path";
|
|||
import { setImmediate as nextTurn } from "node:timers/promises";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { WebSocket } from "ws";
|
||||
import {
|
||||
GATEWAY_CLIENT_IDS,
|
||||
GATEWAY_CLIENT_MODES,
|
||||
} from "../../packages/gateway-protocol/src/client-info.js";
|
||||
import type { ResponseFrame } from "../../packages/gateway-protocol/src/schema/frames.js";
|
||||
import { isAgentRunRestartAbortReason } from "../agents/run-termination.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
|
|
@ -14,6 +18,7 @@ import { initializeGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
|||
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
|
||||
import {
|
||||
getActiveGatewayRootWorkCount,
|
||||
getActiveGatewayRootWorkHolders,
|
||||
markGatewayRestartDraining,
|
||||
} from "../process/gateway-work-admission.js";
|
||||
import { getAsyncWorkSignal } from "../shared/async-work-scope.js";
|
||||
|
|
@ -93,7 +98,16 @@ describe("public Gateway close request lifetime", () => {
|
|||
await gateway.server.startupSettled;
|
||||
phase = "WebSocket connection";
|
||||
ws = await gateway.openWs();
|
||||
await connectOk(ws, { scopes: ["operator.admin"] });
|
||||
// An ephemeral CLI avoids admitting history prewarming alongside the waiter.
|
||||
await connectOk(ws, {
|
||||
scopes: ["operator.admin"],
|
||||
client: {
|
||||
id: GATEWAY_CLIENT_IDS.CLI,
|
||||
mode: GATEWAY_CLIENT_MODES.CLI,
|
||||
version: "1.0.0",
|
||||
platform: "test",
|
||||
},
|
||||
});
|
||||
const response = onceMessage<Pick<ResponseFrame, "type" | "id" | "ok" | "error">>(
|
||||
ws,
|
||||
(frame) => frame.type === "res" && frame.id === "wait-for-shutdown",
|
||||
|
|
@ -114,7 +128,9 @@ describe("public Gateway close request lifetime", () => {
|
|||
requestSent = true;
|
||||
phase = "waiter admission";
|
||||
await Promise.race([entered.promise, prematureReply]);
|
||||
expect(getActiveGatewayRootWorkCount()).toBe(1);
|
||||
expect(getActiveGatewayRootWorkCount(), getActiveGatewayRootWorkHolders().join(", ")).toBe(
|
||||
1,
|
||||
);
|
||||
// A failing owner is released through the real terminal registry, not an abandoned wait.
|
||||
releaseTimer = setTimeout(() => {
|
||||
emergencyRelease = true;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import fs from "node:fs";
|
|||
import { createRequire } from "node:module";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { setImmediate as scheduleImmediate } from "node:timers";
|
||||
import { setImmediate } from "node:timers/promises";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import {
|
||||
|
|
@ -24,8 +25,13 @@ async function collect() {
|
|||
assert.ok(gc, "The retention child requires --expose-gc");
|
||||
const control = new WeakRef({ unowned: true });
|
||||
for (let pass = 0; pass < 8; pass += 1) {
|
||||
await setImmediate();
|
||||
gc();
|
||||
// JavaScriptCore's promise-microtask stack can retain completed async values.
|
||||
await new Promise<void>((resolve) => {
|
||||
scheduleImmediate(() => {
|
||||
gc();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
assert.equal(control.deref(), undefined, "Unowned control must collect");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue