mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 14:12:28 +08:00
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:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user