mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
perf(gateway): overlap startup work before ready
Signed-off-by: samzong <samzong.lu@gmail.com>
This commit is contained in:
committed by
Peter Steinberger
parent
69aec10852
commit
e7933c9137
@@ -383,6 +383,48 @@ describe("startGatewayPostAttachRuntime", () => {
|
||||
expect(events).toEqual(["sidecars", "returned", "sentinel"]);
|
||||
});
|
||||
|
||||
it("starts sidecars while startup logging is still pending", async () => {
|
||||
const events: string[] = [];
|
||||
let finishStartupLog: (() => void) | undefined;
|
||||
const logGatewayStartup = vi.fn(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
events.push("startup-log-start");
|
||||
finishStartupLog = () => {
|
||||
events.push("startup-log-end");
|
||||
resolve();
|
||||
};
|
||||
}),
|
||||
);
|
||||
const startGatewaySidecars = vi.fn(async () => {
|
||||
events.push("sidecars");
|
||||
return { pluginServices: null, postReadySidecars: [] };
|
||||
});
|
||||
|
||||
const runtimePromise = startGatewayPostAttachRuntime(
|
||||
createPostAttachParams(),
|
||||
createPostAttachRuntimeDeps({
|
||||
logGatewayStartup,
|
||||
refreshLatestUpdateRestartSentinel: vi.fn(async () => null),
|
||||
startGatewaySidecars,
|
||||
}),
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(logGatewayStartup).toHaveBeenCalledTimes(1);
|
||||
expect(startGatewaySidecars).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(events).toEqual(["startup-log-start", "sidecars"]);
|
||||
|
||||
if (!finishStartupLog) {
|
||||
throw new Error("Expected startup log release callback to be initialized");
|
||||
}
|
||||
finishStartupLog();
|
||||
await runtimePromise;
|
||||
|
||||
expect(events).toEqual(["startup-log-start", "sidecars", "startup-log-end"]);
|
||||
});
|
||||
|
||||
it("skips heavy restart sentinel refresh when no sentinel file exists", async () => {
|
||||
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-no-sentinel-"));
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", stateDir);
|
||||
@@ -800,6 +842,50 @@ describe("startGatewayPostAttachRuntime", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("starts and reports plugin services before channel startup completion", async () => {
|
||||
await withEnvAsync(
|
||||
{ OPENCLAW_SKIP_CHANNELS: undefined, OPENCLAW_SKIP_PROVIDERS: undefined },
|
||||
async () => {
|
||||
let releaseChannels: (() => void) | undefined;
|
||||
const pluginServices = { stop: vi.fn(async () => {}) } as never;
|
||||
const onPluginServices = vi.fn();
|
||||
const onSidecarsReady = vi.fn();
|
||||
const startChannels = vi.fn(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
releaseChannels = resolve;
|
||||
}),
|
||||
);
|
||||
hoisted.startPluginServices.mockResolvedValueOnce(pluginServices);
|
||||
|
||||
await startGatewayPostAttachRuntime({
|
||||
...createPostAttachParams({
|
||||
deferSidecars: true,
|
||||
onPluginServices,
|
||||
onSidecarsReady,
|
||||
}),
|
||||
startChannels,
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(startChannels).toHaveBeenCalledTimes(1);
|
||||
expect(hoisted.startPluginServices).toHaveBeenCalledTimes(1);
|
||||
expect(onPluginServices).toHaveBeenCalledWith(pluginServices);
|
||||
});
|
||||
expect(onSidecarsReady).not.toHaveBeenCalled();
|
||||
|
||||
if (!releaseChannels) {
|
||||
throw new Error("Expected channel startup release callback to be initialized");
|
||||
}
|
||||
releaseChannels();
|
||||
await vi.waitFor(() => {
|
||||
expect(onSidecarsReady).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(onPluginServices).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("emits a startup trace span when channel startup is skipped", async () => {
|
||||
const trace = createStartupTraceRecorder();
|
||||
const logChannels = { info: vi.fn(), error: vi.fn() };
|
||||
|
||||
@@ -413,6 +413,7 @@ export async function startGatewaySidecars(params: {
|
||||
deps: CliDeps;
|
||||
startChannels: () => Promise<void>;
|
||||
prewarmPrimaryModel?: typeof prewarmConfiguredPrimaryModel;
|
||||
onPluginServices?: (pluginServices: PluginServicesHandle | null) => void;
|
||||
log: { warn: (msg: string) => void };
|
||||
logHooks: {
|
||||
info: (msg: string) => void;
|
||||
@@ -445,6 +446,31 @@ export async function startGatewaySidecars(params: {
|
||||
}
|
||||
});
|
||||
|
||||
const pluginServicesPromise = measureStartup(
|
||||
params.startupTrace,
|
||||
"sidecars.plugin-services",
|
||||
async () => {
|
||||
try {
|
||||
const { startPluginServices } = await import("../plugins/services.js");
|
||||
return await startPluginServices({
|
||||
registry: params.pluginRegistry,
|
||||
config: params.cfg,
|
||||
workspaceDir: params.defaultWorkspaceDir,
|
||||
startupTrace: params.startupTrace,
|
||||
});
|
||||
} catch (err) {
|
||||
params.log.warn(`plugin services failed to start: ${String(err)}`);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
);
|
||||
const pluginServicesReportPromise = params.onPluginServices
|
||||
? pluginServicesPromise.then((pluginServices) => {
|
||||
params.onPluginServices?.(pluginServices);
|
||||
return pluginServices;
|
||||
})
|
||||
: pluginServicesPromise;
|
||||
|
||||
const skipChannels =
|
||||
isTruthyEnvValue(process.env.OPENCLAW_SKIP_CHANNELS) ||
|
||||
isTruthyEnvValue(process.env.OPENCLAW_SKIP_PROVIDERS);
|
||||
@@ -492,20 +518,7 @@ export async function startGatewaySidecars(params: {
|
||||
}, 250);
|
||||
}
|
||||
|
||||
let pluginServices: PluginServicesHandle | null = null;
|
||||
await measureStartup(params.startupTrace, "sidecars.plugin-services", async () => {
|
||||
try {
|
||||
const { startPluginServices } = await import("../plugins/services.js");
|
||||
pluginServices = await startPluginServices({
|
||||
registry: params.pluginRegistry,
|
||||
config: params.cfg,
|
||||
workspaceDir: params.defaultWorkspaceDir,
|
||||
startupTrace: params.startupTrace,
|
||||
});
|
||||
} catch (err) {
|
||||
params.log.warn(`plugin services failed to start: ${String(err)}`);
|
||||
}
|
||||
});
|
||||
const pluginServices = await pluginServicesReportPromise;
|
||||
|
||||
if (params.cfg.acp?.enabled) {
|
||||
void (async () => {
|
||||
@@ -804,7 +817,7 @@ export async function startGatewayPostAttachRuntime(
|
||||
await params.onStartupPluginsLoaded?.(loaded);
|
||||
}
|
||||
|
||||
await measureStartup(params.startupTrace, "post-attach.log", () =>
|
||||
const startupLogPromise = measureStartup(params.startupTrace, "post-attach.log", () =>
|
||||
runtimeDeps.logGatewayStartup({
|
||||
cfg: params.cfgAtStart,
|
||||
bindHost: params.bindHost,
|
||||
@@ -849,6 +862,12 @@ export async function startGatewayPostAttachRuntime(
|
||||
}),
|
||||
);
|
||||
|
||||
let pluginServicesReported = false;
|
||||
const reportPluginServices = (pluginServices: PluginServicesHandle | null) => {
|
||||
pluginServicesReported = true;
|
||||
params.onPluginServices?.(pluginServices);
|
||||
};
|
||||
|
||||
const sidecarsPromise = params.minimalTestGateway
|
||||
? Promise.resolve({ pluginServices: null, pluginRegistry, postReadySidecars: [] })
|
||||
: new Promise<void>((resolve) => setImmediate(resolve)).then(async () => {
|
||||
@@ -865,6 +884,7 @@ export async function startGatewayPostAttachRuntime(
|
||||
logHooks: params.logHooks,
|
||||
logChannels: params.logChannels,
|
||||
startupTrace: params.startupTrace,
|
||||
onPluginServices: reportPluginServices,
|
||||
}),
|
||||
);
|
||||
const loaderStatsAfter = getPluginModuleLoaderStats();
|
||||
@@ -884,7 +904,9 @@ export async function startGatewayPostAttachRuntime(
|
||||
for (const method of STARTUP_UNAVAILABLE_GATEWAY_METHODS) {
|
||||
params.unavailableGatewayMethods.delete(method);
|
||||
}
|
||||
params.onPluginServices?.(result.pluginServices);
|
||||
if (!pluginServicesReported) {
|
||||
reportPluginServices(result.pluginServices);
|
||||
}
|
||||
params.onPostReadySidecars?.(result.postReadySidecars);
|
||||
params.onSidecarsReady?.();
|
||||
params.startupTrace?.detail("sidecars.ready", [
|
||||
@@ -937,7 +959,8 @@ export async function startGatewayPostAttachRuntime(
|
||||
});
|
||||
|
||||
if (params.deferSidecars !== true) {
|
||||
const [stopGatewayUpdateCheck, tailscaleCleanup, sidecarsResult] = await Promise.all([
|
||||
const [, stopGatewayUpdateCheck, tailscaleCleanup, sidecarsResult] = await Promise.all([
|
||||
startupLogPromise,
|
||||
stopGatewayUpdateCheckPromise,
|
||||
tailscaleCleanupPromise,
|
||||
sidecarsPromise,
|
||||
@@ -949,7 +972,8 @@ export async function startGatewayPostAttachRuntime(
|
||||
};
|
||||
}
|
||||
|
||||
const [stopGatewayUpdateCheck, tailscaleCleanup] = await Promise.all([
|
||||
const [, stopGatewayUpdateCheck, tailscaleCleanup] = await Promise.all([
|
||||
startupLogPromise,
|
||||
stopGatewayUpdateCheckPromise,
|
||||
tailscaleCleanupPromise,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user