mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
refactor(gateway): unify maintenance lifecycle ownership (#128766)
This commit is contained in:
committed by
GitHub
parent
a6a9f553d0
commit
3dec4e3472
@@ -3052,7 +3052,7 @@ src/gateway/server-plugins.ts 2
|
||||
src/gateway/server-reload-restart.ts 2
|
||||
src/gateway/server-reload-utils.ts 1
|
||||
src/gateway/server-restart-sentinel-agent-delivery.ts 2
|
||||
src/gateway/server-runtime-handles.ts 11
|
||||
src/gateway/server-runtime-handles.ts 9
|
||||
src/gateway/server-runtime-state-prepare.ts 2
|
||||
src/gateway/server-session-events.ts 1
|
||||
src/gateway/server-startup-bootstrap.ts 1
|
||||
|
||||
@@ -164,12 +164,16 @@ function createGatewayCloseTestDeps(
|
||||
stopTaskRegistryMaintenance: null,
|
||||
nodePresenceTimers: new Map(),
|
||||
broadcast: vi.fn(),
|
||||
tickInterval: setInterval(() => undefined, 60_000),
|
||||
healthInterval: setInterval(() => undefined, 60_000),
|
||||
dedupeCleanup: setInterval(() => undefined, 60_000),
|
||||
maintenance: {
|
||||
tickInterval: setInterval(() => undefined, 60_000),
|
||||
healthInterval: setInterval(() => undefined, 60_000),
|
||||
dedupeCleanup: setInterval(() => undefined, 60_000),
|
||||
startMediaCleanup: vi.fn(),
|
||||
stopMediaCleanup: vi.fn(async () => "drained" as const),
|
||||
worktreeCleanup: setInterval(() => undefined, 60_000),
|
||||
skillCuratorCleanup: vi.fn(),
|
||||
},
|
||||
stopMediaCleanup: vi.fn(async () => "drained" as const),
|
||||
worktreeCleanup: null,
|
||||
skillCuratorCleanup: vi.fn(),
|
||||
agentUnsub: null,
|
||||
taskUnsub: null,
|
||||
heartbeatUnsub: null,
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
} from "./server-chat-state.js";
|
||||
import type { MediaCleanupStopResult } from "./server-media-cleanup-lifecycle.js";
|
||||
import { clearSessionTypingState } from "./server-methods/session-typing-state.js";
|
||||
import type { GatewayMaintenanceHandles } from "./server-runtime-services.js";
|
||||
|
||||
const shutdownLog = createSubsystemLogger("gateway/shutdown");
|
||||
const GATEWAY_SHUTDOWN_HOOK_TIMEOUT_MS = 5_000;
|
||||
@@ -728,12 +729,8 @@ export function createGatewayCloseHandler(
|
||||
updateCheckStop?: (() => void) | null;
|
||||
stopTaskRegistryMaintenance?: (() => Promise<void> | void) | null;
|
||||
nodePresenceTimers: Map<string, ReturnType<typeof setInterval>>;
|
||||
tickInterval: ReturnType<typeof setInterval>;
|
||||
healthInterval: ReturnType<typeof setInterval>;
|
||||
dedupeCleanup: ReturnType<typeof setInterval>;
|
||||
maintenance: GatewayMaintenanceHandles | null;
|
||||
stopMediaCleanup: () => Promise<MediaCleanupStopResult>;
|
||||
worktreeCleanup: ReturnType<typeof setInterval> | null;
|
||||
skillCuratorCleanup: () => void;
|
||||
agentUnsub: (() => Promise<void> | void) | null;
|
||||
heartbeatUnsub: (() => void) | null;
|
||||
transcriptUnsub: (() => void) | null;
|
||||
@@ -976,13 +973,13 @@ export function createGatewayCloseHandler(
|
||||
reason,
|
||||
restartExpectedMs,
|
||||
});
|
||||
clearInterval(params.tickInterval);
|
||||
clearInterval(params.healthInterval);
|
||||
clearInterval(params.dedupeCleanup);
|
||||
if (params.worktreeCleanup) {
|
||||
clearInterval(params.worktreeCleanup);
|
||||
if (params.maintenance) {
|
||||
clearInterval(params.maintenance.tickInterval);
|
||||
clearInterval(params.maintenance.healthInterval);
|
||||
clearInterval(params.maintenance.dedupeCleanup);
|
||||
clearInterval(params.maintenance.worktreeCleanup);
|
||||
params.maintenance.skillCuratorCleanup();
|
||||
}
|
||||
params.skillCuratorCleanup();
|
||||
if (params.agentUnsub) {
|
||||
await shutdownStep("agent-unsub", () => params.agentUnsub!(), warnings);
|
||||
}
|
||||
|
||||
@@ -324,20 +324,9 @@ export async function prepareGatewayLifecycle(params: {
|
||||
addGatewayLifetimeSidecar: (sidecar: (typeof runtimeState.gatewayLifetimeSidecars)[number]) => {
|
||||
runtimeState.gatewayLifetimeSidecars.push(sidecar);
|
||||
},
|
||||
setMaintenanceHandles: (handles: {
|
||||
tickInterval: typeof runtimeState.tickInterval;
|
||||
healthInterval: typeof runtimeState.healthInterval;
|
||||
dedupeCleanup: typeof runtimeState.dedupeCleanup;
|
||||
stopMediaCleanup: typeof runtimeState.stopMediaCleanup;
|
||||
worktreeCleanup: typeof runtimeState.worktreeCleanup;
|
||||
skillCuratorCleanup: typeof runtimeState.skillCuratorCleanup;
|
||||
}) => {
|
||||
runtimeState.tickInterval = handles.tickInterval;
|
||||
runtimeState.healthInterval = handles.healthInterval;
|
||||
runtimeState.dedupeCleanup = handles.dedupeCleanup;
|
||||
setMaintenanceHandles: (handles: NonNullable<typeof runtimeState.maintenance>) => {
|
||||
runtimeState.maintenance = handles;
|
||||
runtimeState.stopMediaCleanup = handles.stopMediaCleanup;
|
||||
runtimeState.worktreeCleanup = handles.worktreeCleanup;
|
||||
runtimeState.skillCuratorCleanup = handles.skillCuratorCleanup;
|
||||
},
|
||||
};
|
||||
runtimeState.controlUiSessionPullRequests = createControlUiSessionPullRequestSubscriptions({
|
||||
@@ -524,12 +513,8 @@ export async function prepareGatewayLifecycle(params: {
|
||||
stopTaskRegistryMaintenance: shutdownRuntime.stopTaskRegistryMaintenance,
|
||||
nodePresenceTimers,
|
||||
broadcast,
|
||||
tickInterval: runtimeState.tickInterval,
|
||||
healthInterval: runtimeState.healthInterval,
|
||||
dedupeCleanup: runtimeState.dedupeCleanup,
|
||||
maintenance: runtimeState.maintenance,
|
||||
stopMediaCleanup: stopMediaCleanupForClose,
|
||||
worktreeCleanup: runtimeState.worktreeCleanup,
|
||||
skillCuratorCleanup: runtimeState.skillCuratorCleanup,
|
||||
agentUnsub: runtimeState.agentUnsub,
|
||||
heartbeatUnsub: runtimeState.heartbeatUnsub,
|
||||
transcriptUnsub: runtimeState.transcriptUnsub,
|
||||
|
||||
@@ -30,10 +30,5 @@ describe("createGatewayServerMutableState", () => {
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
const restartedState = createGatewayServerMutableState();
|
||||
await expect(restartedState.stopMediaCleanup()).resolves.toBe("drained");
|
||||
for (const runtimeState of [state, restartedState]) {
|
||||
clearInterval(runtimeState.tickInterval);
|
||||
clearInterval(runtimeState.healthInterval);
|
||||
clearInterval(runtimeState.dedupeCleanup);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
waitForMediaCleanupDrains,
|
||||
} from "./server-media-cleanup-lifecycle.js";
|
||||
import { createNoopHeartbeatRunner } from "./server-runtime-service-shared.js";
|
||||
import type { GatewayMaintenanceHandles } from "./server-runtime-services.js";
|
||||
import type { GatewayPostReadySidecarHandle } from "./server-startup-post-attach.js";
|
||||
|
||||
// Mutable server handles track timers, sidecars, subscriptions, and service
|
||||
@@ -25,12 +26,8 @@ export type GatewayConfigReloaderHandle = {
|
||||
/** Mutable handles owned by a running gateway server process. */
|
||||
export type GatewayServerMutableState = {
|
||||
bonjourStop: (() => Promise<void>) | null;
|
||||
tickInterval: ReturnType<typeof setInterval>;
|
||||
healthInterval: ReturnType<typeof setInterval>;
|
||||
dedupeCleanup: ReturnType<typeof setInterval>;
|
||||
maintenance: GatewayMaintenanceHandles | null;
|
||||
stopMediaCleanup: () => Promise<MediaCleanupStopResult>;
|
||||
worktreeCleanup: ReturnType<typeof setInterval> | null;
|
||||
skillCuratorCleanup: () => void;
|
||||
heartbeatRunner: HeartbeatRunner;
|
||||
stopOutboundDeliveryRecovery: () => Promise<void>;
|
||||
stopGatewayUpdateCheck: () => void;
|
||||
@@ -41,7 +38,6 @@ export type GatewayServerMutableState = {
|
||||
skillsRefreshDelayMs: number;
|
||||
skillsChangeUnsub: () => Promise<void>;
|
||||
channelHealthMonitor: ChannelHealthMonitor | null;
|
||||
mcpServer: { port: number; close: () => Promise<void> } | undefined;
|
||||
configReloader: GatewayConfigReloaderHandle;
|
||||
agentUnsub: (() => Promise<void> | void) | null;
|
||||
heartbeatUnsub: (() => void) | null;
|
||||
@@ -52,21 +48,10 @@ export type GatewayServerMutableState = {
|
||||
|
||||
/** Creates gateway mutable state with inert handles that are safe to stop before startup finishes. */
|
||||
export function createGatewayServerMutableState(): GatewayServerMutableState {
|
||||
const noopInterval = () => {
|
||||
// Dummy unref'd timers give shutdown code a concrete handle to clear even
|
||||
// when startup exits before real maintenance intervals are installed.
|
||||
const timer = setInterval(() => {}, 1 << 30);
|
||||
timer.unref?.();
|
||||
return timer;
|
||||
};
|
||||
return {
|
||||
bonjourStop: null as (() => Promise<void>) | null,
|
||||
tickInterval: noopInterval(),
|
||||
healthInterval: noopInterval(),
|
||||
dedupeCleanup: noopInterval(),
|
||||
maintenance: null,
|
||||
stopMediaCleanup: () => waitForMediaCleanupDrains({ timeoutMs: MEDIA_CLEANUP_STOP_TIMEOUT_MS }),
|
||||
worktreeCleanup: null as ReturnType<typeof setInterval> | null,
|
||||
skillCuratorCleanup: () => {},
|
||||
heartbeatRunner: createNoopHeartbeatRunner(),
|
||||
stopOutboundDeliveryRecovery: async () => {},
|
||||
stopGatewayUpdateCheck: () => {},
|
||||
@@ -77,7 +62,6 @@ export function createGatewayServerMutableState(): GatewayServerMutableState {
|
||||
skillsRefreshDelayMs: 30_000,
|
||||
skillsChangeUnsub: async () => {},
|
||||
channelHealthMonitor: null as ChannelHealthMonitor | null,
|
||||
mcpServer: undefined as { port: number; close: () => Promise<void> } | undefined,
|
||||
configReloader: {
|
||||
stop: async () => {},
|
||||
notifyPluginMetadataChanged: () => {},
|
||||
|
||||
@@ -76,7 +76,7 @@ export function startGatewayCronWithLogging(params: {
|
||||
}).catch((err: unknown) => params.logCron.error(`failed to enter start root: ${String(err)}`));
|
||||
}
|
||||
|
||||
async function clearGatewayMaintenanceHandles(
|
||||
export async function clearGatewayMaintenanceHandles(
|
||||
maintenance: GatewayMaintenanceHandles | null,
|
||||
): Promise<void> {
|
||||
if (!maintenance) {
|
||||
|
||||
@@ -448,12 +448,7 @@ export async function finishGatewayStartup(params: {
|
||||
},
|
||||
applyMaintenance: async (maintenance) => {
|
||||
if (lifecycle.closePreludeStarted) {
|
||||
clearInterval(maintenance.tickInterval);
|
||||
clearInterval(maintenance.healthInterval);
|
||||
clearInterval(maintenance.dedupeCleanup);
|
||||
await maintenance.stopMediaCleanup();
|
||||
clearInterval(maintenance.worktreeCleanup);
|
||||
maintenance.skillCuratorCleanup();
|
||||
await gatewayRuntimeServices.clearGatewayMaintenanceHandles(maintenance);
|
||||
return;
|
||||
}
|
||||
// Publish the stop owner before cleanup can touch SQLite or state paths;
|
||||
|
||||
Reference in New Issue
Block a user