perf(gateway): defer scheduled service imports

This commit is contained in:
Peter Steinberger
2026-05-27 03:52:04 +01:00
parent 0126aba57f
commit b74cd69c6f
3 changed files with 101 additions and 67 deletions
+5 -48
View File
@@ -2,10 +2,13 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
import { isVitestRuntimeEnv } from "../infra/env.js";
import { startHeartbeatRunner, type HeartbeatRunner } from "../infra/heartbeat-runner.js";
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
import type { ChannelHealthMonitor } from "./channel-health-monitor.js";
import { startChannelHealthMonitor } from "./channel-health-monitor.js";
import { isGatewayModelPricingEnabled } from "./model-pricing-config.js";
import type { startGatewayMaintenanceTimers } from "./server-maintenance.js";
export {
startGatewayChannelHealthMonitor,
startGatewayRuntimeServices,
type GatewayChannelManager,
} from "./server-runtime-startup-services.js";
type GatewayRuntimeServiceLogger = {
child: (name: string) => {
@@ -22,10 +25,6 @@ export type GatewayMaintenanceHandles = NonNullable<
Awaited<ReturnType<typeof startGatewayMaintenanceTimers>>
>;
export type GatewayChannelManager = Parameters<
typeof startChannelHealthMonitor
>[0]["channelManager"];
function createNoopHeartbeatRunner(): HeartbeatRunner {
return {
stop: () => {},
@@ -33,26 +32,6 @@ function createNoopHeartbeatRunner(): HeartbeatRunner {
};
}
export function startGatewayChannelHealthMonitor(params: {
cfg: OpenClawConfig;
channelManager: GatewayChannelManager;
}): ChannelHealthMonitor | null {
const healthCheckMinutes = params.cfg.gateway?.channelHealthCheckMinutes;
if (healthCheckMinutes === 0) {
return null;
}
const staleEventThresholdMinutes = params.cfg.gateway?.channelStaleEventThresholdMinutes;
const maxRestartsPerHour = params.cfg.gateway?.channelMaxRestartsPerHour;
return startChannelHealthMonitor({
channelManager: params.channelManager,
checkIntervalMs: (healthCheckMinutes ?? 5) * 60_000,
...(staleEventThresholdMinutes != null && {
staleEventThresholdMs: staleEventThresholdMinutes * 60_000,
}),
...(maxRestartsPerHour != null && { maxRestartsPerHour }),
});
}
export function startGatewayCronWithLogging(params: {
cron: { start: () => Promise<void> };
logCron: { error: (message: string) => void };
@@ -220,28 +199,6 @@ function startGatewayModelPricingRefreshOnDemand(params: {
};
}
export function startGatewayRuntimeServices(params: {
minimalTestGateway: boolean;
cfgAtStart: OpenClawConfig;
channelManager: GatewayChannelManager;
log: GatewayRuntimeServiceLogger;
}): {
heartbeatRunner: HeartbeatRunner;
channelHealthMonitor: ChannelHealthMonitor | null;
stopModelPricingRefresh: () => void;
} {
const channelHealthMonitor = startGatewayChannelHealthMonitor({
cfg: params.cfgAtStart,
channelManager: params.channelManager,
});
return {
heartbeatRunner: createNoopHeartbeatRunner(),
channelHealthMonitor,
stopModelPricingRefresh: () => {},
};
}
export function activateGatewayScheduledServices(params: {
minimalTestGateway: boolean;
cfgAtStart: OpenClawConfig;
@@ -0,0 +1,65 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import type { ChannelHealthMonitor } from "./channel-health-monitor.js";
import { startChannelHealthMonitor } from "./channel-health-monitor.js";
type GatewayRuntimeServiceLogger = {
child: (name: string) => {
info: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
};
error: (message: string) => void;
};
export type GatewayChannelManager = Parameters<
typeof startChannelHealthMonitor
>[0]["channelManager"];
function createNoopHeartbeatRunner() {
return {
stop: () => {},
updateConfig: (_cfg: OpenClawConfig) => {},
};
}
export function startGatewayChannelHealthMonitor(params: {
cfg: OpenClawConfig;
channelManager: GatewayChannelManager;
}): ChannelHealthMonitor | null {
const healthCheckMinutes = params.cfg.gateway?.channelHealthCheckMinutes;
if (healthCheckMinutes === 0) {
return null;
}
const staleEventThresholdMinutes = params.cfg.gateway?.channelStaleEventThresholdMinutes;
const maxRestartsPerHour = params.cfg.gateway?.channelMaxRestartsPerHour;
return startChannelHealthMonitor({
channelManager: params.channelManager,
checkIntervalMs: (healthCheckMinutes ?? 5) * 60_000,
...(staleEventThresholdMinutes != null && {
staleEventThresholdMs: staleEventThresholdMinutes * 60_000,
}),
...(maxRestartsPerHour != null && { maxRestartsPerHour }),
});
}
export function startGatewayRuntimeServices(params: {
minimalTestGateway: boolean;
cfgAtStart: OpenClawConfig;
channelManager: GatewayChannelManager;
log: GatewayRuntimeServiceLogger;
}): {
heartbeatRunner: ReturnType<typeof createNoopHeartbeatRunner>;
channelHealthMonitor: ChannelHealthMonitor | null;
stopModelPricingRefresh: () => void;
} {
const channelHealthMonitor = startGatewayChannelHealthMonitor({
cfg: params.cfgAtStart,
channelManager: params.channelManager,
});
return {
heartbeatRunner: createNoopHeartbeatRunner(),
channelHealthMonitor,
stopModelPricingRefresh: () => {},
};
}
+31 -19
View File
@@ -1114,14 +1114,13 @@ export async function startGatewayServer(
getActiveTaskCount = earlyRuntime.getActiveTaskCount;
runtimeState.skillsChangeUnsub = earlyRuntime.skillsChangeUnsub;
const [{ startGatewayEventSubscriptions }, gatewayRuntimeServices] = await startupTrace.measure(
"runtime.post-early-imports",
() =>
const [{ startGatewayEventSubscriptions }, { startGatewayRuntimeServices }] =
await startupTrace.measure("runtime.post-early-imports", () =>
Promise.all([
import("./server-runtime-subscriptions.js"),
import("./server-runtime-services.js"),
import("./server-runtime-startup-services.js"),
]),
);
);
const runtimeSubscriptions = await startupTrace.measure("runtime.subscriptions", () =>
startGatewayEventSubscriptions({
broadcast,
@@ -1138,7 +1137,7 @@ export async function startGatewayServer(
Object.assign(runtimeState, runtimeSubscriptions);
const runtimeServices = await startupTrace.measure("runtime.services", () =>
gatewayRuntimeServices.startGatewayRuntimeServices({
startGatewayRuntimeServices({
minimalTestGateway,
cfgAtStart,
channelManager,
@@ -1516,6 +1515,13 @@ export async function startGatewayServer(
const sessionDeliveryRecoveryMaxEnqueuedAt = Date.now();
let postAttachRuntimeReturned = false;
let scheduledServicesActivated = false;
let scheduledServicesModulePromise: Promise<
typeof import("./server-runtime-services.js")
> | null = null;
const loadScheduledServicesModule = () => {
scheduledServicesModulePromise ??= import("./server-runtime-services.js");
return scheduledServicesModulePromise;
};
const activateScheduledServicesWhenReady = () => {
if (
closePreludeStarted ||
@@ -1525,20 +1531,25 @@ export async function startGatewayServer(
) {
return;
}
const activated = gatewayRuntimeServices.activateGatewayScheduledServices({
minimalTestGateway,
cfgAtStart,
deps,
sessionDeliveryRecoveryMaxEnqueuedAt,
cron: runtimeState.cronState.cron,
startCron: false,
logCron,
log,
pluginLookUpTable,
});
scheduledServicesActivated = true;
runtimeState.heartbeatRunner = activated.heartbeatRunner;
runtimeState.stopModelPricingRefresh = activated.stopModelPricingRefresh;
void loadScheduledServicesModule().then((gatewayRuntimeServices) => {
if (closePreludeStarted) {
return;
}
const activated = gatewayRuntimeServices.activateGatewayScheduledServices({
minimalTestGateway,
cfgAtStart,
deps,
sessionDeliveryRecoveryMaxEnqueuedAt,
cron: runtimeState.cronState.cron,
startCron: false,
logCron,
log,
pluginLookUpTable,
});
runtimeState.heartbeatRunner = activated.heartbeatRunner;
runtimeState.stopModelPricingRefresh = activated.stopModelPricingRefresh;
});
};
({
stopGatewayUpdateCheck: runtimeState.stopGatewayUpdateCheck,
@@ -1693,6 +1704,7 @@ export async function startGatewayServer(
log.warn(`gateway: failed to promote config last-known-good backup: ${String(err)}`);
});
if (!minimalTestGateway) {
const gatewayRuntimeServices = await loadScheduledServicesModule();
postReadyMaintenanceTimer = gatewayRuntimeServices.scheduleGatewayPostReadyMaintenance({
delayMs: POST_READY_MAINTENANCE_DELAY_MS,
isClosing: () => closePreludeStarted,