fix(test): prevent plugin cache accumulation in shared workers (#157470)

* fix(test): settle retired plugin caches between cases

Join the canonical plugin cache retirement owner after shared registry resets
and final suite cleanup, preserving borrowed caches and reporting cleanup
failures. Retired metadata previously accumulated across Gateway test files
until the shared worker exhausted its heap.

* test(gateway): settle expected plugin retirement failures
This commit is contained in:
Peter Steinberger
2026-09-24 13:18:36 -07:00
committed by GitHub
parent 6b4595575a
commit ec78e8e0d6
3 changed files with 24 additions and 0 deletions
@@ -10,6 +10,7 @@ import { registerPreparedModelRuntimePublicationListener } from "../agents/prepa
import { registerPreparedModelRuntimeClose } from "../agents/prepared-model-runtime.lifecycle.js";
import { getPreparedModelRuntimeStartupStatus } from "../agents/prepared-model-runtime.startup-status.js";
import { GATEWAY_SHUTDOWN_TIMEOUT_MS } from "../infra/gateway-shutdown-budget.js";
import { waitForPluginCacheRetirement } from "../plugins/plugin-cache.js";
import { getPluginValueInstance } from "../plugins/plugin-instance-scope.js";
import { createDeferredCore } from "../shared/deferred.js";
import { createGatewayMetadataCloseFixture } from "./server-close.metadata.test-support.js";
@@ -189,6 +190,10 @@ it.each(["static catalog", "synthetic auth"] as const)(
if (cleanupFailure) {
// Other shutdown work must not hide a discarded plugin cleanup outcome.
expect(collectNestedErrorCandidates(closeError)).toContain(cleanupFailure);
// The process-cache reset retains the same outcome for its next observer.
expect((await waitForPluginCacheRetirement()).failures).toEqual([
{ pluginId: fixture.pluginId, hookId: "instance", error: cleanupFailure },
]);
} else {
expect(closeError).toBeUndefined();
}
@@ -11,6 +11,7 @@ import {
import { SUPERVISOR_HINT_ENV_VARS } from "../infra/supervisor-markers.js";
import { flushLogger, setLoggerOverride } from "../logging/logger.js";
import { getGatewayPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-state.js";
import { waitForPluginCacheRetirement } from "../plugins/plugin-cache.js";
import { getPluginValueInstance } from "../plugins/plugin-instance-scope.js";
import { PluginInstance } from "../plugins/plugin-instance.js";
import type { MemoryPluginRuntime } from "../plugins/registry-contribution-types.js";
@@ -218,6 +219,12 @@ it.each(["final", "sibling", "cache", "restart", "memory-and-plugin", "memory-on
if (hasPluginFailure) {
expect.soft(collectNestedErrorCandidates(error)).toContain(pluginFailure);
expect(pluginSawOpenDatabase).toBe(true);
if (mode === "cache" || mode === "restart") {
// The process-cache reset retains the same outcome for its next observer.
expect((await waitForPluginCacheRetirement()).failures).toEqual([
{ pluginId: fixture.pluginId, hookId: "instance", error: pluginFailure },
]);
}
} else {
expect(error).toBeUndefined();
}
+12
View File
@@ -340,6 +340,16 @@ function resolveDefaultPluginRegistryProxy(): PluginRegistry {
return workerRuntimeState.defaultPluginRegistry;
}
async function settlePluginCacheRetirements(): Promise<void> {
const { waitForPluginCacheRetirement } = await vi.importActual<
typeof import("../src/plugins/plugin-cache.js")
>("../src/plugins/plugin-cache.js");
const { failures } = await waitForPluginCacheRetirement();
if (failures.length > 0) {
throw new AggregateError(failures, "Plugin cache retirement failed during test cleanup");
}
}
async function installDefaultPluginRegistry(): Promise<void> {
// Worker module resets retire the lifecycle maps. Activate through the current
// real module, never a cached closure or a suite's partial runtime mock.
@@ -349,6 +359,7 @@ async function installDefaultPluginRegistry(): Promise<void> {
workerRuntimeState.materializedDefaultPluginRegistry = null;
resetPluginRuntimeStateForTest();
setActivePluginRegistry(resolveDefaultPluginRegistryProxy());
await settlePluginCacheRetirements();
}
// Some suites import channel/plugin consumers at module top level, before
@@ -393,4 +404,5 @@ afterAll(async () => {
await drainSessionStoreWriterQueuesForTest();
clearSessionStoreCacheForTest();
await drainFileLockStateForTest();
await settlePluginCacheRetirements();
});