mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
refactor(gateway): remove unused resident lifecycle registry (#128710)
* refactor(gateway): remove unused resident lifecycle registry * chore(gateway): prune deleted registry assertion baseline
This commit is contained in:
committed by
GitHub
parent
5e438e9d0b
commit
1c0e02ccb0
@@ -3052,7 +3052,6 @@ src/gateway/server-plugins-node-runtime.ts 1
|
||||
src/gateway/server-plugins.ts 2
|
||||
src/gateway/server-reload-restart.ts 2
|
||||
src/gateway/server-reload-utils.ts 1
|
||||
src/gateway/server-resident-registry.ts 1
|
||||
src/gateway/server-restart-sentinel-agent-delivery.ts 2
|
||||
src/gateway/server-runtime-handles.ts 11
|
||||
src/gateway/server-runtime-state-prepare.ts 2
|
||||
|
||||
@@ -151,8 +151,6 @@ export async function startGatewayCoreRuntime(input: {
|
||||
workerEnvironmentStartup,
|
||||
broadcastPluginEvent,
|
||||
activateRuntimeSecrets,
|
||||
residentRegistry,
|
||||
shutdownRuntime,
|
||||
} = runtime;
|
||||
let currentPluginMetadataSnapshot = runtime.pluginMetadataSnapshot;
|
||||
if (desktopSessionRegistry) {
|
||||
@@ -171,11 +169,8 @@ export async function startGatewayCoreRuntime(input: {
|
||||
if (secretEgressProxy) {
|
||||
kernel.addGatewayLifetimeSidecar(secretEgressProxy);
|
||||
}
|
||||
let earlyRuntimePromise: ReturnType<
|
||||
Awaited<ReturnType<typeof loadGatewayStartupEarlyModule>>["startGatewayEarlyRuntime"]
|
||||
> | null = null;
|
||||
const startEarlyRuntime = () => {
|
||||
earlyRuntimePromise ??= loadGatewayStartupEarlyModule().then(({ startGatewayEarlyRuntime }) =>
|
||||
const earlyRuntime = await startupTrace.measure("runtime.early", () =>
|
||||
loadGatewayStartupEarlyModule().then(({ startGatewayEarlyRuntime }) =>
|
||||
startGatewayEarlyRuntime({
|
||||
minimalTestGateway,
|
||||
cfgAtStart,
|
||||
@@ -221,25 +216,7 @@ export async function startGatewayCoreRuntime(input: {
|
||||
getRuntimeConfig,
|
||||
startupTrace,
|
||||
}),
|
||||
);
|
||||
return earlyRuntimePromise;
|
||||
};
|
||||
const discoveryResident = residentRegistry.register({
|
||||
name: "bonjour-discovery",
|
||||
start: startEarlyRuntime,
|
||||
stop: async () => await kernel.swapBonjourStop(null)?.(),
|
||||
});
|
||||
const taskAndSkillsResident = residentRegistry.register({
|
||||
name: "task-and-skills-runtime",
|
||||
start: async () => await discoveryResident.start(),
|
||||
stop: async () => {
|
||||
const earlyRuntime = await startEarlyRuntime();
|
||||
await earlyRuntime.skillsChangeUnsub();
|
||||
shutdownRuntime.stopTaskRegistryMaintenance();
|
||||
},
|
||||
});
|
||||
const earlyRuntime = await startupTrace.measure("runtime.early", () =>
|
||||
taskAndSkillsResident.start(),
|
||||
),
|
||||
);
|
||||
kernel.setEarlyRuntimeHandles(earlyRuntime);
|
||||
|
||||
@@ -250,9 +227,8 @@ export async function startGatewayCoreRuntime(input: {
|
||||
import("./server-runtime-startup-services.js"),
|
||||
]),
|
||||
);
|
||||
const eventSubscriptionsResident = residentRegistry.register({
|
||||
name: "event-subscriptions",
|
||||
start: () =>
|
||||
const { sessionCompanion, sessionObserver, ...runtimeSubscriptionUnsubs } =
|
||||
await startupTrace.measure("runtime.subscriptions", () =>
|
||||
startGatewayEventSubscriptions({
|
||||
log,
|
||||
broadcast,
|
||||
@@ -267,16 +243,7 @@ export async function startGatewayCoreRuntime(input: {
|
||||
restartRecoveryCandidates,
|
||||
terminalSessions,
|
||||
}),
|
||||
stop: async () => {
|
||||
await runtimeState.agentUnsub?.();
|
||||
runtimeState.heartbeatUnsub?.();
|
||||
runtimeState.transcriptUnsub?.();
|
||||
runtimeState.lifecycleUnsub?.();
|
||||
runtimeState.taskUnsub?.();
|
||||
},
|
||||
});
|
||||
const { sessionCompanion, sessionObserver, ...runtimeSubscriptionUnsubs } =
|
||||
await startupTrace.measure("runtime.subscriptions", () => eventSubscriptionsResident.start());
|
||||
);
|
||||
Object.assign(runtimeState, runtimeSubscriptionUnsubs);
|
||||
|
||||
await startupTrace.measure("runtime.services", () =>
|
||||
|
||||
@@ -56,18 +56,6 @@ describe("createGatewayKernel", () => {
|
||||
expect(getStartup()).toMatchObject({ ok: true, status: "started" });
|
||||
expect(getReadiness()).toMatchObject({ ready: true, failing: [] });
|
||||
|
||||
const discoveryResident = kernel.residentRegistry
|
||||
.list()
|
||||
.find((resident) => resident.name === "bonjour-discovery");
|
||||
if (!discoveryResident) {
|
||||
throw new Error("Expected the Gateway discovery resident");
|
||||
}
|
||||
const residentFirstStop = vi.fn(async () => {});
|
||||
kernel.kernel.swapBonjourStop(residentFirstStop);
|
||||
await discoveryResident.stop();
|
||||
expect(residentFirstStop).toHaveBeenCalledOnce();
|
||||
expect(kernel.runtimeState.bonjourStop).toBeNull();
|
||||
|
||||
const closeFirstStop = vi.fn(async () => {});
|
||||
kernel.kernel.swapBonjourStop(closeFirstStop);
|
||||
const configReloaderStop = createDeferred();
|
||||
@@ -80,7 +68,6 @@ describe("createGatewayKernel", () => {
|
||||
expect(getReadiness()).toMatchObject({ ready: false, failing: ["gateway-draining"] });
|
||||
configReloaderStop.resolve();
|
||||
await closing;
|
||||
await discoveryResident.stop();
|
||||
expect(closeFirstStop).toHaveBeenCalledOnce();
|
||||
expect(kernel.runtimeState.bonjourStop).toBeNull();
|
||||
} finally {
|
||||
|
||||
@@ -85,7 +85,6 @@ export async function prepareGatewayLifecycle(params: {
|
||||
watchNodeRequestHandler,
|
||||
defaultWorkspaceDir,
|
||||
activeTaskCount,
|
||||
residentRegistry,
|
||||
desktopSessionRegistry,
|
||||
nodeDesktopStreamBroker,
|
||||
nodeDesktopObserveAvailable,
|
||||
@@ -598,35 +597,28 @@ export async function prepareGatewayLifecycle(params: {
|
||||
});
|
||||
};
|
||||
|
||||
const diagnosticHeartbeatResident = residentRegistry.register({
|
||||
name: "diagnostic-heartbeat",
|
||||
start: () => {
|
||||
// Gateway lifecycle owns both this existing heartbeat timer and the monitor
|
||||
// it samples, so startup failure and normal close tear them down together.
|
||||
startDiagnosticHeartbeat(undefined, {
|
||||
getConfig: getRuntimeConfig,
|
||||
startupGraceMs: 60_000,
|
||||
sampleLiveness: () => {
|
||||
const sample = readinessEventLoopHealth.persistentDegradationSnapshot();
|
||||
if (!sample || sample.degradedSinceMs == null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
reasons: sample.reasons,
|
||||
intervalMs: sample.intervalMs,
|
||||
degradedSinceMs: sample.degradedSinceMs,
|
||||
eventLoopDelayP99Ms: sample.delayP99Ms,
|
||||
eventLoopDelayMaxMs: sample.delayMaxMs,
|
||||
eventLoopUtilization: sample.utilization,
|
||||
cpuCoreRatio: sample.cpuCoreRatio,
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
stop: () => stopDiagnosticHeartbeat(),
|
||||
});
|
||||
if (diagnosticsEnabled) {
|
||||
diagnosticHeartbeatResident.start();
|
||||
// Gateway lifecycle owns both this existing heartbeat timer and the monitor
|
||||
// it samples, so startup failure and normal close tear them down together.
|
||||
startDiagnosticHeartbeat(undefined, {
|
||||
getConfig: getRuntimeConfig,
|
||||
startupGraceMs: 60_000,
|
||||
sampleLiveness: () => {
|
||||
const sample = readinessEventLoopHealth.persistentDegradationSnapshot();
|
||||
if (!sample || sample.degradedSinceMs == null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
reasons: sample.reasons,
|
||||
intervalMs: sample.intervalMs,
|
||||
degradedSinceMs: sample.degradedSinceMs,
|
||||
eventLoopDelayP99Ms: sample.delayP99Ms,
|
||||
eventLoopDelayMaxMs: sample.delayMaxMs,
|
||||
eventLoopUtilization: sample.utilization,
|
||||
cpuCoreRatio: sample.cpuCoreRatio,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
type GatewayResident<
|
||||
TStart extends () => unknown = () => unknown,
|
||||
TStop extends () => unknown = () => unknown,
|
||||
> = {
|
||||
name: string;
|
||||
start: TStart;
|
||||
stop: TStop;
|
||||
};
|
||||
|
||||
export type GatewayResidentRegistry = {
|
||||
register: <TStart extends () => unknown, TStop extends () => unknown>(
|
||||
resident: GatewayResident<TStart, TStop>,
|
||||
) => GatewayResident<TStart, TStop>;
|
||||
list: () => readonly GatewayResident[];
|
||||
};
|
||||
|
||||
/** Records resident lifecycle owners without changing when callers start or stop them. */
|
||||
export function createGatewayResidentRegistry(): GatewayResidentRegistry {
|
||||
const residents: GatewayResident[] = [];
|
||||
const names = new Set<string>();
|
||||
|
||||
return {
|
||||
register: <TStart extends () => unknown, TStop extends () => unknown>(
|
||||
resident: GatewayResident<TStart, TStop>,
|
||||
) => {
|
||||
if (names.has(resident.name)) {
|
||||
throw new Error(`Gateway resident already registered: ${resident.name}`);
|
||||
}
|
||||
names.add(resident.name);
|
||||
residents.push(resident as GatewayResident);
|
||||
return resident;
|
||||
},
|
||||
list: () => residents,
|
||||
};
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import { createGatewayControlUiRootLifecycle } from "./server-control-ui-root.js
|
||||
import type { GatewayInstanceRuntime } from "./server-instance-runtime.types.js";
|
||||
import type { GatewayServerLiveState } from "./server-live-state.js";
|
||||
import type { GatewayRequestContext } from "./server-methods/types.js";
|
||||
import { createGatewayResidentRegistry } from "./server-resident-registry.js";
|
||||
import type { SharedGatewaySessionGenerationState } from "./server-shared-auth-generation.js";
|
||||
import type { prepareGatewayServerBootstrap } from "./server-startup-bootstrap.js";
|
||||
import { createGatewayTransportBridge } from "./server-transport-bridge.js";
|
||||
@@ -380,25 +379,13 @@ export async function prepareGatewayKernelState(params: {
|
||||
const systemAgentSessions: GatewayRequestContext["systemAgentSessions"] = new Map();
|
||||
|
||||
const deps = createDefaultDeps();
|
||||
const residentRegistry = createGatewayResidentRegistry();
|
||||
const runtimeStateRef: { current: GatewayServerLiveState | null } = { current: null };
|
||||
const cronStartState = { handled: false };
|
||||
const gatewayTls = await startupTrace.measure("tls.runtime", () =>
|
||||
loadGatewayTlsRuntime(cfgAtStart.gateway?.tls, log.child("tls")),
|
||||
);
|
||||
const serverStartedAt = Date.now();
|
||||
const eventLoopHealthState: {
|
||||
current?: ReturnType<typeof createGatewayEventLoopHealthMonitor>;
|
||||
} = {};
|
||||
const eventLoopHealthResident = residentRegistry.register({
|
||||
name: "event-loop-health",
|
||||
start: () => {
|
||||
eventLoopHealthState.current ??= createGatewayEventLoopHealthMonitor();
|
||||
return eventLoopHealthState.current;
|
||||
},
|
||||
stop: () => eventLoopHealthState.current?.stop(),
|
||||
});
|
||||
const readinessEventLoopHealth = eventLoopHealthResident.start();
|
||||
const readinessEventLoopHealth = createGatewayEventLoopHealthMonitor();
|
||||
const startupState = {
|
||||
sidecarsReady: minimalTestGateway,
|
||||
pendingReason: "startup-sidecars",
|
||||
@@ -581,7 +568,6 @@ export async function prepareGatewayKernelState(params: {
|
||||
purgeWizardSession,
|
||||
systemAgentSessions,
|
||||
deps,
|
||||
residentRegistry,
|
||||
runtimeStateRef,
|
||||
cronStartState,
|
||||
gatewayTls,
|
||||
|
||||
@@ -129,7 +129,6 @@ export async function finishGatewayStartup(params: {
|
||||
chatMetadataLifecycle,
|
||||
gatewayRequestContext,
|
||||
gatewayInstanceRuntime,
|
||||
residentRegistry,
|
||||
getPluginMetadataSnapshot,
|
||||
} = runtime;
|
||||
const startupPluginRuntimeClaim = kernel.pluginRuntimeGeneration.currentClaim();
|
||||
@@ -184,20 +183,6 @@ export async function finishGatewayStartup(params: {
|
||||
await startupTrace.measure("http.listen", () => startListening());
|
||||
kernel.setDispatchReady(true);
|
||||
startupTrace.mark("http.bound");
|
||||
let databaseVerifierHandle: { stop: () => void | Promise<void> } | null = null;
|
||||
const databaseIntegrityResident = residentRegistry.register({
|
||||
name: "database-integrity-verifier",
|
||||
start: async () => {
|
||||
if (minimalTestGateway) {
|
||||
return;
|
||||
}
|
||||
const { startOpenClawDatabaseIntegrityVerifier } =
|
||||
await import("../state/openclaw-database-verify.js");
|
||||
databaseVerifierHandle = startOpenClawDatabaseIntegrityVerifier({ env: process.env });
|
||||
kernel.addGatewayLifetimeSidecar(databaseVerifierHandle);
|
||||
},
|
||||
stop: async () => await databaseVerifierHandle?.stop(),
|
||||
});
|
||||
const sessionDeliveryRecoveryMaxEnqueuedAt = Date.now();
|
||||
let postAttachRuntimeReturned = false;
|
||||
let scheduledServicesActivated = false;
|
||||
@@ -205,43 +190,35 @@ export async function finishGatewayStartup(params: {
|
||||
() => import("./server-runtime-services.js"),
|
||||
{ cacheRejections: true },
|
||||
);
|
||||
const scheduledServicesResident = residentRegistry.register({
|
||||
name: "scheduled-services",
|
||||
start: () => {
|
||||
if (
|
||||
lifecycle.closePreludeStarted ||
|
||||
!postAttachRuntimeReturned ||
|
||||
!startupState.sidecarsReady ||
|
||||
scheduledServicesActivated
|
||||
) {
|
||||
const activateScheduledServicesWhenReady = () => {
|
||||
if (
|
||||
lifecycle.closePreludeStarted ||
|
||||
!postAttachRuntimeReturned ||
|
||||
!startupState.sidecarsReady ||
|
||||
scheduledServicesActivated
|
||||
) {
|
||||
return;
|
||||
}
|
||||
scheduledServicesActivated = true;
|
||||
void loadScheduledServicesModule().then((gatewayRuntimeServices) => {
|
||||
if (lifecycle.closePreludeStarted) {
|
||||
return;
|
||||
}
|
||||
scheduledServicesActivated = true;
|
||||
void loadScheduledServicesModule().then((gatewayRuntimeServices) => {
|
||||
if (lifecycle.closePreludeStarted) {
|
||||
return;
|
||||
}
|
||||
const activated = gatewayRuntimeServices.activateGatewayScheduledServices({
|
||||
minimalTestGateway,
|
||||
cfgAtStart,
|
||||
deps,
|
||||
sessionDeliveryRecoveryMaxEnqueuedAt,
|
||||
cronState: runtimeState.cronState,
|
||||
cronReconciliation,
|
||||
startCron: false,
|
||||
logCron,
|
||||
log,
|
||||
resolveGatewayContext: resolvePluginGatewayContext,
|
||||
});
|
||||
kernel.setScheduledServiceHandles(activated);
|
||||
const activated = gatewayRuntimeServices.activateGatewayScheduledServices({
|
||||
minimalTestGateway,
|
||||
cfgAtStart,
|
||||
deps,
|
||||
sessionDeliveryRecoveryMaxEnqueuedAt,
|
||||
cronState: runtimeState.cronState,
|
||||
cronReconciliation,
|
||||
startCron: false,
|
||||
logCron,
|
||||
log,
|
||||
resolveGatewayContext: resolvePluginGatewayContext,
|
||||
});
|
||||
},
|
||||
stop: async () => {
|
||||
await runtimeState.stopOutboundDeliveryRecovery();
|
||||
runtimeState.heartbeatRunner.stop();
|
||||
},
|
||||
});
|
||||
const activateScheduledServicesWhenReady = scheduledServicesResident.start;
|
||||
kernel.setScheduledServiceHandles(activated);
|
||||
});
|
||||
};
|
||||
const { createGatewayServerActiveWorkInspectors } = await import("./server-active-work.js");
|
||||
const postAttachHandles = await startupTrace.measure("runtime.post-attach", () =>
|
||||
loadGatewayStartupPostAttachModule().then(({ startGatewayPostAttachRuntime }) =>
|
||||
@@ -348,7 +325,6 @@ export async function finishGatewayStartup(params: {
|
||||
sidecarStartup,
|
||||
waitForPostReadyWork: params.waitForPostReadyWork,
|
||||
activeWorkInspectors: createGatewayServerActiveWorkInspectors(gatewayRequestContext),
|
||||
residentRegistry,
|
||||
providerAuthPrewarm: {
|
||||
getConfig: getRuntimeConfig,
|
||||
},
|
||||
@@ -363,7 +339,9 @@ export async function finishGatewayStartup(params: {
|
||||
}
|
||||
finishGatewayRestartTrace("restart.ready", collectGatewayProcessMemoryUsageMb());
|
||||
if (!minimalTestGateway) {
|
||||
await databaseIntegrityResident.start();
|
||||
const { startOpenClawDatabaseIntegrityVerifier } =
|
||||
await import("../state/openclaw-database-verify.js");
|
||||
kernel.addGatewayLifetimeSidecar(startOpenClawDatabaseIntegrityVerifier({ env: process.env }));
|
||||
}
|
||||
postAttachRuntimeReturned = true;
|
||||
activateScheduledServicesWhenReady();
|
||||
@@ -450,98 +428,68 @@ export async function finishGatewayStartup(params: {
|
||||
...(opts.hotReloadRecovery ? { requestRecoveryRestart: opts.hotReloadRecovery } : {}),
|
||||
restartRecoveryAvailable: opts.hotReloadRecovery !== undefined,
|
||||
};
|
||||
const configReloaderResident = residentRegistry.register({
|
||||
name: "config-reloader",
|
||||
start: () => startManagedGatewayConfigReloader(configReloaderParams),
|
||||
stop: async () => await runtimeState.configReloader.stop(),
|
||||
});
|
||||
kernel.setConfigReloaderHandle(configReloaderResident.start());
|
||||
kernel.setConfigReloaderHandle(startManagedGatewayConfigReloader(configReloaderParams));
|
||||
await promoteConfigSnapshotToLastKnownGood(startupLastGoodSnapshot).catch((err: unknown) => {
|
||||
log.warn(`gateway: failed to promote config last-known-good backup: ${String(err)}`);
|
||||
});
|
||||
if (!minimalTestGateway) {
|
||||
const gatewayRuntimeServices = await loadScheduledServicesModule();
|
||||
const maintenanceResident = residentRegistry.register({
|
||||
name: "post-ready-maintenance",
|
||||
start: () => {
|
||||
postReadyState.maintenanceTimer =
|
||||
gatewayRuntimeServices.scheduleGatewayPostReadyMaintenance({
|
||||
delayMs: POST_READY_MAINTENANCE_DELAY_MS,
|
||||
isClosing: () => lifecycle.closePreludeStarted,
|
||||
onStarted: () => {
|
||||
postReadyState.maintenanceTimer = null;
|
||||
},
|
||||
startMaintenance: async () => {
|
||||
if (lifecycle.closePreludeStarted) {
|
||||
return null;
|
||||
}
|
||||
return earlyRuntime.startMaintenance();
|
||||
},
|
||||
applyMaintenance: async (maintenance) => {
|
||||
if (lifecycle.closePreludeStarted) {
|
||||
clearInterval(maintenance.tickInterval);
|
||||
clearInterval(maintenance.healthInterval);
|
||||
clearInterval(maintenance.dedupeCleanup);
|
||||
await maintenance.stopMediaCleanup();
|
||||
clearInterval(maintenance.worktreeCleanup);
|
||||
maintenance.skillCuratorCleanup();
|
||||
return;
|
||||
}
|
||||
// Publish the stop owner before cleanup can touch SQLite or state paths;
|
||||
// shutdown may begin immediately after this synchronous handoff.
|
||||
kernel.setMaintenanceHandles(maintenance);
|
||||
maintenance.startMediaCleanup();
|
||||
},
|
||||
shouldStartCron: () => !lifecycle.closePreludeStarted && !cronStartState.handled,
|
||||
markCronStartHandled: () => {
|
||||
cronStartState.handled = true;
|
||||
},
|
||||
cronState: runtimeState.cronState,
|
||||
cronReconciliation,
|
||||
cronConfig: cfgAtStart,
|
||||
logCron,
|
||||
log,
|
||||
recordPostReadyMemory: () => {
|
||||
startupTrace.detail("memory.post-ready", collectGatewayProcessMemoryUsageMb());
|
||||
},
|
||||
});
|
||||
postReadyState.maintenanceTimer = gatewayRuntimeServices.scheduleGatewayPostReadyMaintenance({
|
||||
delayMs: POST_READY_MAINTENANCE_DELAY_MS,
|
||||
isClosing: () => lifecycle.closePreludeStarted,
|
||||
onStarted: () => {
|
||||
postReadyState.maintenanceTimer = null;
|
||||
},
|
||||
stop: () => {
|
||||
if (postReadyState.maintenanceTimer) {
|
||||
clearTimeout(postReadyState.maintenanceTimer);
|
||||
postReadyState.maintenanceTimer = null;
|
||||
startMaintenance: async () => {
|
||||
if (lifecycle.closePreludeStarted) {
|
||||
return null;
|
||||
}
|
||||
return earlyRuntime.startMaintenance();
|
||||
},
|
||||
applyMaintenance: async (maintenance) => {
|
||||
if (lifecycle.closePreludeStarted) {
|
||||
clearInterval(maintenance.tickInterval);
|
||||
clearInterval(maintenance.healthInterval);
|
||||
clearInterval(maintenance.dedupeCleanup);
|
||||
await maintenance.stopMediaCleanup();
|
||||
clearInterval(maintenance.worktreeCleanup);
|
||||
maintenance.skillCuratorCleanup();
|
||||
return;
|
||||
}
|
||||
// Publish the stop owner before cleanup can touch SQLite or state paths;
|
||||
// shutdown may begin immediately after this synchronous handoff.
|
||||
kernel.setMaintenanceHandles(maintenance);
|
||||
maintenance.startMediaCleanup();
|
||||
},
|
||||
shouldStartCron: () => !lifecycle.closePreludeStarted && !cronStartState.handled,
|
||||
markCronStartHandled: () => {
|
||||
cronStartState.handled = true;
|
||||
},
|
||||
cronState: runtimeState.cronState,
|
||||
cronReconciliation,
|
||||
cronConfig: cfgAtStart,
|
||||
logCron,
|
||||
log,
|
||||
recordPostReadyMemory: () => {
|
||||
startupTrace.detail("memory.post-ready", collectGatewayProcessMemoryUsageMb());
|
||||
},
|
||||
});
|
||||
maintenanceResident.start();
|
||||
// The loop closes the previous server before this generation starts, so retired
|
||||
// plugin installs are safe to remove. Wait for an idle window and resolve current
|
||||
// install paths at execution time so cleanup cannot remove active code or delay a turn.
|
||||
const retainedPluginCleanupResident = residentRegistry.register({
|
||||
name: "retained-plugin-cleanup",
|
||||
start: () => {
|
||||
postReadyState.retainedPluginCleanupHandle = gatewayRuntimeServices.scheduleGatewayIdleTask(
|
||||
{
|
||||
delayMs: RETAINED_PLUGIN_CLEANUP_DELAY_MS,
|
||||
retryDelayMs: RETAINED_PLUGIN_CLEANUP_DELAY_MS,
|
||||
isClosing: () => lifecycle.closePreludeStarted,
|
||||
isBusy: () => getActiveGatewayRootWorkCount({ excludeCurrent: true }) > 0,
|
||||
run: async () => {
|
||||
const { cleanupRetainedPluginInstallGenerations } =
|
||||
await import("./server-retained-plugin-cleanup.js");
|
||||
await cleanupRetainedPluginInstallGenerations({ log });
|
||||
},
|
||||
log,
|
||||
errorMessage: "retained npm generation cleanup failed",
|
||||
},
|
||||
);
|
||||
},
|
||||
stop: () => {
|
||||
postReadyState.retainedPluginCleanupHandle?.stop();
|
||||
postReadyState.retainedPluginCleanupHandle = null;
|
||||
postReadyState.retainedPluginCleanupHandle = gatewayRuntimeServices.scheduleGatewayIdleTask({
|
||||
delayMs: RETAINED_PLUGIN_CLEANUP_DELAY_MS,
|
||||
retryDelayMs: RETAINED_PLUGIN_CLEANUP_DELAY_MS,
|
||||
isClosing: () => lifecycle.closePreludeStarted,
|
||||
isBusy: () => getActiveGatewayRootWorkCount({ excludeCurrent: true }) > 0,
|
||||
run: async () => {
|
||||
const { cleanupRetainedPluginInstallGenerations } =
|
||||
await import("./server-retained-plugin-cleanup.js");
|
||||
await cleanupRetainedPluginInstallGenerations({ log });
|
||||
},
|
||||
log,
|
||||
errorMessage: "retained npm generation cleanup failed",
|
||||
});
|
||||
retainedPluginCleanupResident.start();
|
||||
} else {
|
||||
startupTrace.detail("memory.post-ready", collectGatewayProcessMemoryUsageMb());
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
} from "../process/gateway-work-admission.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { createGatewayResidentRegistry } from "./server-resident-registry.js";
|
||||
import "./server-startup-outcomes.test-support.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => {
|
||||
@@ -3850,7 +3849,6 @@ function createPostAttachParams(overrides: Partial<PostAttachParams> = {}): Post
|
||||
error: vi.fn(),
|
||||
},
|
||||
unlockStartupMethods: vi.fn(),
|
||||
residentRegistry: createGatewayResidentRegistry(),
|
||||
providerAuthPrewarm: { enabled: false },
|
||||
unregisterGatewayLifetimeSidecar: vi.fn(),
|
||||
stopRegisteredPostReadySidecars: () => stopTrackedSidecars(publishedPostReadySidecars),
|
||||
|
||||
@@ -40,7 +40,6 @@ import type { GatewayControlUiRootLifecycle } from "./server-control-ui-root.js"
|
||||
import type { GatewayRecoveryRuntime } from "./server-instance-runtime.types.js";
|
||||
import type { GatewayClient, GatewayContextResolver } from "./server-methods/shared-types.js";
|
||||
import type { GatewayPluginRuntimeClaim } from "./server-plugin-runtime-generation.js";
|
||||
import type { GatewayResidentRegistry } from "./server-resident-registry.js";
|
||||
import type { refreshLatestUpdateRestartSentinel } from "./server-restart-sentinel.js";
|
||||
import type { GatewaySidecarStartupMode } from "./server-sidecar-startup-mode.js";
|
||||
import { scheduleContextCachePrewarm } from "./server-startup-context-cache-prewarm.js";
|
||||
@@ -1206,36 +1205,26 @@ export async function startGatewayPostAttachRuntime(
|
||||
};
|
||||
waitForPostReadyWork?: () => Promise<void>;
|
||||
activeWorkInspectors?: Partial<GatewayActiveWorkInspectors>;
|
||||
residentRegistry: GatewayResidentRegistry;
|
||||
},
|
||||
runtimeDeps: GatewayPostAttachRuntimeDeps = defaultGatewayPostAttachRuntimeDeps,
|
||||
) {
|
||||
const controlUiRootLifecycle = params.controlUiRootLifecycle;
|
||||
const mainSessionRecoveryStartupCheckedStorePaths = new Set<string>();
|
||||
let controlUiAssetsSidecar: GatewayPostReadySidecarHandle | undefined;
|
||||
const controlUiAssetsResident = params.residentRegistry.register({
|
||||
name: "control-ui-assets",
|
||||
start: () => {
|
||||
controlUiAssetsSidecar =
|
||||
!params.minimalTestGateway && controlUiRootLifecycle?.state?.kind === "preparing"
|
||||
? schedulePostReadySidecarTask({
|
||||
name: "sidecars.control-ui-assets",
|
||||
startupTrace: params.startupTrace,
|
||||
log: params.log,
|
||||
run: controlUiRootLifecycle.start,
|
||||
stop: controlUiRootLifecycle.stop,
|
||||
})
|
||||
: undefined;
|
||||
if (controlUiAssetsSidecar) {
|
||||
// Publish before the first await: slow CA/plugin startup must not strand
|
||||
// the dashboard or hide its running builder from Gateway shutdown.
|
||||
params.onGatewayLifetimeSidecars?.([controlUiAssetsSidecar]);
|
||||
}
|
||||
return controlUiAssetsSidecar;
|
||||
},
|
||||
stop: async () => await controlUiAssetsSidecar?.stop(),
|
||||
});
|
||||
controlUiAssetsResident.start();
|
||||
const controlUiAssetsSidecar =
|
||||
!params.minimalTestGateway && controlUiRootLifecycle?.state?.kind === "preparing"
|
||||
? schedulePostReadySidecarTask({
|
||||
name: "sidecars.control-ui-assets",
|
||||
startupTrace: params.startupTrace,
|
||||
log: params.log,
|
||||
run: controlUiRootLifecycle.start,
|
||||
stop: controlUiRootLifecycle.stop,
|
||||
})
|
||||
: undefined;
|
||||
if (controlUiAssetsSidecar) {
|
||||
// Publish before the first await: slow CA/plugin startup must not strand
|
||||
// the dashboard or hide its running builder from Gateway shutdown.
|
||||
params.onGatewayLifetimeSidecars?.([controlUiAssetsSidecar]);
|
||||
}
|
||||
|
||||
if (!params.minimalTestGateway) {
|
||||
// The HTTP server is already attached, so keep health probes responsive while the worker
|
||||
@@ -1287,12 +1276,6 @@ export async function startGatewayPostAttachRuntime(
|
||||
})();
|
||||
return await startupPluginsLoadPromise;
|
||||
};
|
||||
const startupPluginsResident = params.residentRegistry.register({
|
||||
name: "startup-plugin-load",
|
||||
start: loadStartupPluginsIfNeeded,
|
||||
stop: () => {},
|
||||
});
|
||||
|
||||
let startupLogPromise: Promise<void> | undefined;
|
||||
const startupLogSettled = createDeferredCore();
|
||||
// Tailscale and sidecar work can delay the public readiness handle past log failure.
|
||||
@@ -1348,11 +1331,6 @@ export async function startGatewayPostAttachRuntime(
|
||||
activeWorkInspectors: params.activeWorkInspectors,
|
||||
});
|
||||
|
||||
const updateCheckResident = params.residentRegistry.register({
|
||||
name: "update-check",
|
||||
start: updateCheck.start,
|
||||
stop: updateCheck.stop,
|
||||
});
|
||||
let pluginServicesReported = false;
|
||||
let reportedPluginServices: PluginServicesHandle | null = null;
|
||||
const reportPluginServices = (pluginServices: PluginServicesHandle | null) => {
|
||||
@@ -1394,7 +1372,7 @@ export async function startGatewayPostAttachRuntime(
|
||||
skipStartupLog();
|
||||
return emptySidecarResult();
|
||||
}
|
||||
await startupPluginsResident.start();
|
||||
await loadStartupPluginsIfNeeded();
|
||||
if (params.isClosing?.()) {
|
||||
skipStartupLog();
|
||||
return emptySidecarResult();
|
||||
@@ -1606,31 +1584,7 @@ export async function startGatewayPostAttachRuntime(
|
||||
params.log.info("gateway ready");
|
||||
return { ...result, postReadySidecars, gatewayLifetimeSidecars, pluginRegistry };
|
||||
});
|
||||
let startedSidecars: ReturnType<typeof startSidecars> | undefined;
|
||||
const sidecarSequenceResident = params.residentRegistry.register({
|
||||
name: "sidecar-sequence",
|
||||
start: () => {
|
||||
startedSidecars ??= startSidecars();
|
||||
return startedSidecars;
|
||||
},
|
||||
stop: async () => {
|
||||
const result = await startedSidecars;
|
||||
for (const sidecar of result?.postReadySidecars ?? []) {
|
||||
await sidecar.stop();
|
||||
}
|
||||
},
|
||||
});
|
||||
const perConfigSidecarsResident = params.residentRegistry.register({
|
||||
name: "per-config-sidecars",
|
||||
start: sidecarSequenceResident.start,
|
||||
stop: async () => {
|
||||
const result = await startedSidecars;
|
||||
for (const sidecar of result?.gatewayLifetimeSidecars ?? []) {
|
||||
await sidecar.stop();
|
||||
}
|
||||
},
|
||||
});
|
||||
const sidecarsPromise = perConfigSidecarsResident.start();
|
||||
const sidecarsPromise = startSidecars();
|
||||
|
||||
void sidecarsPromise
|
||||
.then(async (sidecarsResult) => {
|
||||
@@ -1684,15 +1638,15 @@ export async function startGatewayPostAttachRuntime(
|
||||
|
||||
if (params.sidecarStartup !== "defer") {
|
||||
const sidecarsResult = await sidecarsPromise;
|
||||
updateCheckResident.start();
|
||||
updateCheck.start();
|
||||
return {
|
||||
stopGatewayUpdateCheck: updateCheckResident.stop,
|
||||
stopGatewayUpdateCheck: updateCheck.stop,
|
||||
pluginServices: sidecarsResult.pluginServices,
|
||||
startupSettled: Promise.resolve(),
|
||||
};
|
||||
}
|
||||
|
||||
updateCheckResident.start();
|
||||
updateCheck.start();
|
||||
const startupSettled = Promise.all([sidecarsPromise, startupLogSettled.promise]).then(
|
||||
() => undefined,
|
||||
);
|
||||
@@ -1701,7 +1655,7 @@ export async function startGatewayPostAttachRuntime(
|
||||
void startupSettled.catch(() => {});
|
||||
|
||||
return {
|
||||
stopGatewayUpdateCheck: updateCheckResident.stop,
|
||||
stopGatewayUpdateCheck: updateCheck.stop,
|
||||
pluginServices: reportedPluginServices,
|
||||
startupSettled,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user