perf(cli): skip inactive provider cleanup imports (#121997)

This commit is contained in:
Peter Steinberger
2026-08-11 03:51:35 -07:00
committed by GitHub
parent ad22faffe0
commit 452cf09c07
7 changed files with 97 additions and 20 deletions
@@ -23,6 +23,7 @@ import {
hasLocalServiceProcessExited,
stopManagedProviderLocalServices,
} from "./provider-local-service.js";
import { hasManagedProviderLocalServices } from "./provider-runtime-lifecycle.js";
const ONE_SHOT_HOST_READY_TIMEOUT_MS = 30_000;
const ONE_SHOT_HOST_EXIT_TIMEOUT_MS = 5_000;
@@ -204,6 +205,7 @@ describe("provider local service", () => {
});
it("starts an on-demand local service and stops it after idle", async () => {
expect(hasManagedProviderLocalServices()).toBe(false);
const port = await freePort();
const healthUrl = `http://127.0.0.1:${port}/v1/models`;
const model = attachModelProviderLocalService(
@@ -230,9 +232,11 @@ describe("provider local service", () => {
if (!lease) {
throw new Error("Expected provider local service lease");
}
expect(hasManagedProviderLocalServices()).toBe(true);
expect((await fetch(healthUrl)).ok).toBe(true);
lease.release();
await waitForProbeFailure(healthUrl);
expect(hasManagedProviderLocalServices()).toBe(false);
});
it("resolves process configuration from the host config", async () => {
+6 -5
View File
@@ -22,6 +22,7 @@ import {
signalChildProcessTree,
shouldDetachChildForProcessTree,
} from "../process/child-process-tree.js";
import { setManagedProviderLocalServicesActive } from "./provider-runtime-lifecycle.js";
import { unwrapHeadersInitSentinelsForProviderEgress } from "./provider-secret-egress.js";
const log = createSubsystemLogger("provider-local-service");
@@ -228,6 +229,7 @@ export async function ensureProviderLocalService(
installExitHandler();
const managed = services.get(key) ?? { active: 0 };
services.set(key, managed);
setManagedProviderLocalServicesActive(true);
clearIdleTimer(managed);
managed.active += 1;
@@ -294,6 +296,7 @@ export function stopManagedProviderLocalServices(): void {
stopManagedService(key, managed, "host-shutdown");
}
services.clear();
setManagedProviderLocalServicesActive(false);
}
/** Return bounded local-service state for focused lifecycle tests. */
@@ -312,11 +315,7 @@ function validateLocalServiceConfig(service: ModelProviderLocalServiceConfig, pr
}
function resolveHealthUrl(service: ModelProviderLocalServiceConfig, baseUrl: string): string {
const configured = service.healthUrl?.trim();
if (configured) {
return configured;
}
return `${baseUrl.replace(/\/+$/, "")}/models`;
return service.healthUrl?.trim() || `${baseUrl.replace(/\/+$/, "")}/models`;
}
function localServiceKey(
@@ -583,6 +582,7 @@ function scheduleIdleStop(
if (!managed.process) {
if (!managed.starting) {
services.delete(key);
setManagedProviderLocalServicesActive(services.size > 0);
}
return;
}
@@ -613,6 +613,7 @@ function stopManagedService(key: string, managed: ManagedLocalService, reason: s
managed.process = undefined;
managed.lastExit = undefined;
services.delete(key);
setManagedProviderLocalServicesActive(services.size > 0);
if (child) {
drainLocalServiceOutput(child);
}
+20
View File
@@ -0,0 +1,20 @@
// Provider owners record lifecycle facts here so short-lived CLI teardown can
// avoid loading heavyweight provider modules when they never created resources.
let managedProviderLocalServicesActive = false;
let providerTransportDispatcherPoolActive = false;
export function setManagedProviderLocalServicesActive(active: boolean): void {
managedProviderLocalServicesActive = active;
}
export function hasManagedProviderLocalServices(): boolean {
return managedProviderLocalServicesActive;
}
export function setProviderTransportDispatcherPoolActive(active: boolean): void {
providerTransportDispatcherPoolActive = active;
}
export function hasProviderTransportDispatcherPool(): boolean {
return providerTransportDispatcherPoolActive;
}
@@ -1,4 +1,5 @@
import { PinnedDispatcherPool } from "../infra/net/pinned-dispatcher-pool.js";
import { setProviderTransportDispatcherPoolActive } from "./provider-runtime-lifecycle.js";
const PROVIDER_DISPATCHER_POOL_MAX_ENTRIES = 16;
const PROVIDER_DISPATCHER_POOL_IDLE_TTL_MS = 60_000;
@@ -7,10 +8,13 @@ let activePool: PinnedDispatcherPool | undefined;
/** Returns the current process-lifecycle provider dispatcher pool generation. */
export function getProviderTransportDispatcherPool(): PinnedDispatcherPool {
activePool ??= new PinnedDispatcherPool({
maxEntries: PROVIDER_DISPATCHER_POOL_MAX_ENTRIES,
idleTtlMs: PROVIDER_DISPATCHER_POOL_IDLE_TTL_MS,
});
if (!activePool) {
activePool = new PinnedDispatcherPool({
maxEntries: PROVIDER_DISPATCHER_POOL_MAX_ENTRIES,
idleTtlMs: PROVIDER_DISPATCHER_POOL_IDLE_TTL_MS,
});
setProviderTransportDispatcherPoolActive(true);
}
return activePool;
}
@@ -21,6 +25,7 @@ export async function closeProviderTransportDispatcherPool(): Promise<void> {
await pool.closeAll();
if (activePool === pool) {
activePool = undefined;
setProviderTransportDispatcherPoolActive(false);
}
}
}
+35 -5
View File
@@ -58,6 +58,9 @@ const closeActiveMemorySearchManagersMock = vi.hoisted(() => vi.fn(async () => {
const hasMemoryRuntimeMock = vi.hoisted(() => vi.fn(() => false));
const listRegisteredAgentHarnessesMock = vi.hoisted(() => vi.fn((): unknown[] => []));
const disposeRegisteredAgentHarnessesMock = vi.hoisted(() => vi.fn(async () => {}));
const hasManagedProviderLocalServicesMock = vi.hoisted(() => vi.fn(() => false));
const hasProviderTransportDispatcherPoolMock = vi.hoisted(() => vi.fn(() => false));
const providerCleanupModuleImportState = vi.hoisted(() => ({ local: 0, transport: 0 }));
const stopManagedProviderLocalServicesMock = vi.hoisted(() => vi.fn());
const closeProviderTransportDispatcherPoolMock = vi.hoisted(() => vi.fn(async () => {}));
const getActiveMcpLoopbackRuntimeMock = vi.hoisted(() =>
@@ -320,13 +323,20 @@ vi.mock("../agents/harness/registry.js", () => ({
disposeRegisteredAgentHarnesses: disposeRegisteredAgentHarnessesMock,
}));
vi.mock("../agents/provider-local-service.js", () => ({
stopManagedProviderLocalServices: stopManagedProviderLocalServicesMock,
vi.mock("../agents/provider-runtime-lifecycle.js", () => ({
hasManagedProviderLocalServices: hasManagedProviderLocalServicesMock,
hasProviderTransportDispatcherPool: hasProviderTransportDispatcherPoolMock,
}));
vi.mock("../agents/provider-transport-dispatcher-pool.js", () => ({
closeProviderTransportDispatcherPool: closeProviderTransportDispatcherPoolMock,
}));
vi.mock("../agents/provider-local-service.js", () => {
providerCleanupModuleImportState.local += 1;
return { stopManagedProviderLocalServices: stopManagedProviderLocalServicesMock };
});
vi.mock("../agents/provider-transport-dispatcher-pool.js", () => {
providerCleanupModuleImportState.transport += 1;
return { closeProviderTransportDispatcherPool: closeProviderTransportDispatcherPoolMock };
});
vi.mock("../gateway/mcp-http.loopback-runtime.js", () => ({
getActiveMcpLoopbackRuntime: getActiveMcpLoopbackRuntimeMock,
@@ -570,6 +580,8 @@ describe("runCli exit behavior", () => {
});
hasMemoryRuntimeMock.mockReturnValue(false);
listRegisteredAgentHarnessesMock.mockReturnValue([]);
hasManagedProviderLocalServicesMock.mockReturnValue(false);
hasProviderTransportDispatcherPoolMock.mockReturnValue(false);
outputPrecomputedBrowserHelpTextMock.mockReturnValue(false);
outputPrecomputedNodesHelpTextMock.mockReturnValue(false);
outputPrecomputedRootHelpTextMock.mockReturnValue(false);
@@ -595,6 +607,22 @@ describe("runCli exit behavior", () => {
loggingState.forceConsoleToStderr = false;
});
it("does not load inactive provider cleanup modules for cold help", async () => {
tryRouteCliMock.mockResolvedValueOnce(false);
const parseAsync = vi.fn().mockResolvedValueOnce(undefined);
buildProgramMock.mockReturnValueOnce({
commands: [{ name: () => "nodes", aliases: () => [] }],
parseAsync,
});
await withEnvAsync({ OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH: "1" }, async () => {
await runCli(["node", "openclaw", "nodes", "--help"]);
});
expect(parseAsync).toHaveBeenCalledWith(["node", "openclaw", "nodes", "--help"]);
expect(providerCleanupModuleImportState).toEqual({ local: 0, transport: 0 });
});
it("does not import dotenv for gateway forms without a workspace file", async () => {
existsSyncOverride.value = () => false;
expect(dotenvModuleImportState.count).toBe(0);
@@ -673,6 +701,8 @@ describe("runCli exit behavior", () => {
it("completes asynchronous teardown before returning to the outer entrypoint", async () => {
const order: string[] = [];
listRegisteredAgentHarnessesMock.mockReturnValueOnce([{ harness: { id: "copilot" } }]);
hasManagedProviderLocalServicesMock.mockReturnValueOnce(true);
hasProviderTransportDispatcherPoolMock.mockReturnValueOnce(true);
disposeRegisteredAgentHarnessesMock.mockImplementationOnce(async () => {
order.push("harnesses");
});
+14 -6
View File
@@ -274,14 +274,22 @@ async function closeCliResources(): Promise<void> {
}
},
async () => {
const { stopManagedProviderLocalServices } =
await import("../agents/provider-local-service.js");
stopManagedProviderLocalServices();
const { hasManagedProviderLocalServices } =
await import("../agents/provider-runtime-lifecycle.js");
if (hasManagedProviderLocalServices()) {
const { stopManagedProviderLocalServices } =
await import("../agents/provider-local-service.js");
stopManagedProviderLocalServices();
}
},
async () => {
const { closeProviderTransportDispatcherPool } =
await import("../agents/provider-transport-dispatcher-pool.js");
await closeProviderTransportDispatcherPool();
const { hasProviderTransportDispatcherPool } =
await import("../agents/provider-runtime-lifecycle.js");
if (hasProviderTransportDispatcherPool()) {
const { closeProviderTransportDispatcherPool } =
await import("../agents/provider-transport-dispatcher-pool.js");
await closeProviderTransportDispatcherPool();
}
},
async () => {
const { getActiveMcpLoopbackRuntime } =
@@ -1,5 +1,6 @@
import type { Dispatcher } from "undici";
import { afterEach, describe, expect, it, vi } from "vitest";
import { hasProviderTransportDispatcherPool } from "../../agents/provider-runtime-lifecycle.js";
import {
closeProviderTransportDispatcherPool,
getProviderTransportDispatcherPool,
@@ -21,6 +22,14 @@ describe("PinnedDispatcherPool", () => {
await closeProviderTransportDispatcherPool();
});
it("records dispatcher-pool activity only for an allocated generation", async () => {
expect(hasProviderTransportDispatcherPool()).toBe(false);
getProviderTransportDispatcherPool();
expect(hasProviderTransportDispatcherPool()).toBe(true);
await closeProviderTransportDispatcherPool();
expect(hasProviderTransportDispatcherPool()).toBe(false);
});
it("reuses an exact live key and closes it only at lifecycle shutdown", async () => {
const pool = new PinnedDispatcherPool({ maxEntries: 2, idleTtlMs: 60_000 });
const created = createDispatcher();