perf(gateway): shorten gateway.ready fallback timeout from 30s to 5s

The fallback exists as a safety net for the server-side gateway.ready
event. In practice OpenClaw's plugin bootstrap can push the real event
well past 30s (observed: handshake completes, then 30s tick by, then the
fallback fires with no event having arrived). That long tail kept the
stale gating code blocking UI state for the full 30s.

Step 1 moved sessions.list off the gatewayReady gate, so this value now
only matters as a belt-and-braces signal for any future consumer. 5s is
long enough to preserve "event wins when it actually fires on a healthy
boot" while avoiding a multi-second stall whenever the server is slow.

Updated gateway-ready-fallback.test.ts to advance timers around the new
boundary.
This commit is contained in:
Haze
2026-04-24 17:04:10 +08:00
parent a3d5b0555f
commit 6bacbd964d
2 changed files with 12 additions and 7 deletions

View File

@@ -155,7 +155,12 @@ export class GatewayManager extends EventEmitter {
private static readonly HEARTBEAT_TIMEOUT_MS_WIN = 25_000;
private static readonly HEARTBEAT_MAX_MISSES_WIN = 5;
public static readonly RESTART_COOLDOWN_MS = 5_000;
private static readonly GATEWAY_READY_FALLBACK_MS = 30_000;
// Fallback for the server-side gateway.ready event: if the event doesn't
// arrive within this window after the WS handshake completes, we assume the
// gateway is effectively ready so downstream consumers don't block forever.
// Kept short (5s) because handshake completion already implies a working
// RPC channel — this is only a safety net, not the primary signal.
private static readonly GATEWAY_READY_FALLBACK_MS = 5_000;
private lastRestartAt = 0;
/** Set by scheduleReconnect() before calling start() to signal auto-reconnect. */
private isAutoReconnectStart = false;

View File

@@ -90,10 +90,10 @@ describe('GatewayManager gatewayReady fallback', () => {
(manager as unknown as { scheduleGatewayReadyFallback: () => void }).scheduleGatewayReadyFallback();
// Before timeout, no gatewayReady update
vi.advanceTimersByTime(29_000);
vi.advanceTimersByTime(4_000);
expect(statusUpdates.find((u) => u.gatewayReady === true)).toBeUndefined();
// After 30s fallback timeout
// After fallback timeout (5s)
vi.advanceTimersByTime(2_000);
const readyUpdate = statusUpdates.find((u) => u.gatewayReady === true);
expect(readyUpdate).toBeDefined();
@@ -115,13 +115,13 @@ describe('GatewayManager gatewayReady fallback', () => {
// Schedule fallback
(manager as unknown as { scheduleGatewayReadyFallback: () => void }).scheduleGatewayReadyFallback();
// gateway:ready event arrives at 5s
vi.advanceTimersByTime(5_000);
// gateway:ready event arrives before fallback (at 1s, well under 5s)
vi.advanceTimersByTime(1_000);
manager.emit('gateway:ready', {});
expect(statusUpdates.filter((u) => u.gatewayReady === true)).toHaveLength(1);
// After 30s, no duplicate gatewayReady=true
vi.advanceTimersByTime(30_000);
// Well past the fallback window, no duplicate gatewayReady=true
vi.advanceTimersByTime(10_000);
expect(statusUpdates.filter((u) => u.gatewayReady === true)).toHaveLength(1);
});
});