From d27d7ba6d358b59269a1a1c25a53e809c7dfd8fb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 9 Jul 2026 05:55:34 -0400 Subject: [PATCH] perf(test): narrow gateway context reload coverage --- src/gateway/server-plugin-fallback-context.ts | 61 ++++++++++++++++ src/gateway/server-plugins.test.ts | 8 ++- src/gateway/server-plugins.ts | 70 ++----------------- 3 files changed, 73 insertions(+), 66 deletions(-) create mode 100644 src/gateway/server-plugin-fallback-context.ts diff --git a/src/gateway/server-plugin-fallback-context.ts b/src/gateway/server-plugin-fallback-context.ts new file mode 100644 index 000000000000..402b7e681bab --- /dev/null +++ b/src/gateway/server-plugin-fallback-context.ts @@ -0,0 +1,61 @@ +import { resolveGlobalSingleton } from "../shared/global-singleton.js"; +import type { GatewayRequestContext } from "./server-methods/types.js"; + +const FALLBACK_GATEWAY_CONTEXT_STATE_KEY: unique symbol = Symbol.for( + "openclaw.fallbackGatewayContextState", +); + +type FallbackGatewayContextState = { + context: GatewayRequestContext | undefined; + resolveContext: (() => GatewayRequestContext | undefined) | undefined; +}; + +const getFallbackGatewayContextState = () => + resolveGlobalSingleton(FALLBACK_GATEWAY_CONTEXT_STATE_KEY, () => ({ + context: undefined, + resolveContext: undefined, + })); + +/** Set the process fallback gateway context for channel adapters outside WS requests. */ +export function setFallbackGatewayContext(ctx: GatewayRequestContext): () => void { + const fallbackGatewayContextState = getFallbackGatewayContextState(); + fallbackGatewayContextState.context = ctx; + fallbackGatewayContextState.resolveContext = undefined; + return () => { + const currentFallbackGatewayContextState = getFallbackGatewayContextState(); + if ( + currentFallbackGatewayContextState.context === ctx && + currentFallbackGatewayContextState.resolveContext === undefined + ) { + currentFallbackGatewayContextState.context = undefined; + } + }; +} + +export function setFallbackGatewayContextResolver( + resolveContext: () => GatewayRequestContext | undefined, +): () => void { + const fallbackGatewayContextState = getFallbackGatewayContextState(); + fallbackGatewayContextState.context = undefined; + fallbackGatewayContextState.resolveContext = resolveContext; + return () => { + const currentFallbackGatewayContextState = getFallbackGatewayContextState(); + if (currentFallbackGatewayContextState.resolveContext === resolveContext) { + currentFallbackGatewayContextState.context = undefined; + currentFallbackGatewayContextState.resolveContext = undefined; + } + }; +} + +/** Clear the fallback gateway context installed for non-WS dispatch paths. */ +export function clearFallbackGatewayContext(): void { + const fallbackGatewayContextState = getFallbackGatewayContextState(); + fallbackGatewayContextState.context = undefined; + fallbackGatewayContextState.resolveContext = undefined; +} + +export function getFallbackGatewayContext(): GatewayRequestContext | undefined { + const fallbackGatewayContextState = getFallbackGatewayContextState(); + const resolved = fallbackGatewayContextState.resolveContext?.(); + return resolved ?? fallbackGatewayContextState.context; +} diff --git a/src/gateway/server-plugins.test.ts b/src/gateway/server-plugins.test.ts index a80a893e4927..cfdf0143e760 100644 --- a/src/gateway/server-plugins.test.ts +++ b/src/gateway/server-plugins.test.ts @@ -321,9 +321,11 @@ async function createSubagentRuntime( return runtimeModule.createPluginRuntime({ allowGatewaySubagentBinding: true }).subagent; } -async function reloadServerPluginsModule(): Promise { +async function reloadFallbackGatewayContextModule() { + // Existing runtimes retain the old module graph; only the process-global state owner + // must reload to prove a restarted Gateway can replace their fallback context. vi.resetModules(); - return await import("./server-plugins.js"); + return await import("./server-plugin-fallback-context.js"); } function loadGatewayPluginsForTest( @@ -1611,7 +1613,7 @@ describe("loadGatewayPlugins", () => { await runtime.run({ sessionKey: "s-1", message: "hello" }); expect(getLastDispatchedContext()).toBe(staleContext); - const reloaded = await reloadServerPluginsModule(); + const reloaded = await reloadFallbackGatewayContextModule(); const freshContext = createTestContext("fresh"); reloaded.setFallbackGatewayContext(freshContext); diff --git a/src/gateway/server-plugins.ts b/src/gateway/server-plugins.ts index fa74fb834fd2..93278e368721 100644 --- a/src/gateway/server-plugins.ts +++ b/src/gateway/server-plugins.ts @@ -29,70 +29,14 @@ import { resolveGlobalSingleton } from "../shared/global-singleton.js"; import { resolveSafeTimeoutDelayMs } from "../utils/timer-delay.js"; import { ADMIN_SCOPE, APPROVALS_SCOPE, WRITE_SCOPE } from "./method-scopes.js"; import { normalizeOperatorScopeList, type OperatorScope } from "./operator-scopes.js"; -import type { - GatewayRequestContext, - GatewayRequestHandler, - GatewayRequestOptions, -} from "./server-methods/types.js"; +import type { GatewayRequestHandler, GatewayRequestOptions } from "./server-methods/types.js"; +import { getFallbackGatewayContext } from "./server-plugin-fallback-context.js"; -const FALLBACK_GATEWAY_CONTEXT_STATE_KEY: unique symbol = Symbol.for( - "openclaw.fallbackGatewayContextState", -); - -type FallbackGatewayContextState = { - context: GatewayRequestContext | undefined; - resolveContext: (() => GatewayRequestContext | undefined) | undefined; -}; - -const getFallbackGatewayContextState = () => - resolveGlobalSingleton(FALLBACK_GATEWAY_CONTEXT_STATE_KEY, () => ({ - context: undefined, - resolveContext: undefined, - })); - -/** Set the process fallback gateway context for channel adapters outside WS requests. */ -export function setFallbackGatewayContext(ctx: GatewayRequestContext): () => void { - const fallbackGatewayContextState = getFallbackGatewayContextState(); - fallbackGatewayContextState.context = ctx; - fallbackGatewayContextState.resolveContext = undefined; - return () => { - const currentFallbackGatewayContextState = getFallbackGatewayContextState(); - if ( - currentFallbackGatewayContextState.context === ctx && - currentFallbackGatewayContextState.resolveContext === undefined - ) { - currentFallbackGatewayContextState.context = undefined; - } - }; -} - -export function setFallbackGatewayContextResolver( - resolveContext: () => GatewayRequestContext | undefined, -): () => void { - const fallbackGatewayContextState = getFallbackGatewayContextState(); - fallbackGatewayContextState.context = undefined; - fallbackGatewayContextState.resolveContext = resolveContext; - return () => { - const currentFallbackGatewayContextState = getFallbackGatewayContextState(); - if (currentFallbackGatewayContextState.resolveContext === resolveContext) { - currentFallbackGatewayContextState.context = undefined; - currentFallbackGatewayContextState.resolveContext = undefined; - } - }; -} - -/** Clear the fallback gateway context installed for non-WS dispatch paths. */ -export function clearFallbackGatewayContext(): void { - const fallbackGatewayContextState = getFallbackGatewayContextState(); - fallbackGatewayContextState.context = undefined; - fallbackGatewayContextState.resolveContext = undefined; -} - -function getFallbackGatewayContext(): GatewayRequestContext | undefined { - const fallbackGatewayContextState = getFallbackGatewayContextState(); - const resolved = fallbackGatewayContextState.resolveContext?.(); - return resolved ?? fallbackGatewayContextState.context; -} +export { + clearFallbackGatewayContext, + setFallbackGatewayContext, + setFallbackGatewayContextResolver, +} from "./server-plugin-fallback-context.js"; export function hasInProcessGatewayContext(): boolean { return Boolean(getPluginRuntimeGatewayRequestScope()?.context ?? getFallbackGatewayContext());