




























@@ -54,6 +54,18 @@ const getInspectableActiveTaskRestartBlockers = vi.fn(
5454const markGatewayDraining = vi.fn();
5555const waitForActiveTasks = vi.fn(async (_timeoutMs?: number) => ({ drained: true }));
5656const resetAllLanes = vi.fn();
57+const advanceCronActiveJobGeneration = vi.fn();
58+const resetCronActiveJobs = vi.fn();
59+const abortActiveCronTaskRuns = vi.fn((_reason?: string) => 0);
60+const retireActiveCronTaskRunTracking = vi.fn();
61+const waitForActiveCronTaskRuns = vi.fn(async (_timeoutMs?: number) => ({
62+drained: true,
63+active: 0,
64+}));
65+const waitForActiveCronJobs = vi.fn(async (_timeoutMs?: number) => ({
66+drained: true,
67+active: 0,
68+}));
5769const reloadTaskRegistryFromStore = vi.fn();
5870const rotateAgentEventLifecycleGeneration = vi.fn();
5971const clearRuntimeConfigSnapshot = vi.fn();
@@ -151,6 +163,18 @@ vi.mock("../../process/command-queue.js", () => ({
151163resetAllLanes: () => resetAllLanes(),
152164}));
153165166+vi.mock("../../cron/active-jobs.js", () => ({
167+advanceCronActiveJobGeneration: () => advanceCronActiveJobGeneration(),
168+resetCronActiveJobs: () => resetCronActiveJobs(),
169+waitForActiveCronJobs: (timeoutMs: number) => waitForActiveCronJobs(timeoutMs),
170+}));
171+172+vi.mock("../../tasks/cron-task-cancel.js", () => ({
173+abortActiveCronTaskRuns: (reason?: string) => abortActiveCronTaskRuns(reason),
174+retireActiveCronTaskRunTracking: () => retireActiveCronTaskRunTracking(),
175+waitForActiveCronTaskRuns: (timeoutMs: number) => waitForActiveCronTaskRuns(timeoutMs),
176+}));
177+154178vi.mock("../../tasks/runtime-internal.js", () => ({
155179reloadTaskRegistryFromStore: () => reloadTaskRegistryFromStore(),
156180}));
@@ -876,14 +900,32 @@ describe("runGatewayLoop", () => {
876900expect(gatewayLog.warn).toHaveBeenCalledWith(DRAIN_TIMEOUT_LOG);
877901expectRestartCloseCall(closeFirst, 1_234);
878902expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(1);
903+expect(abortActiveCronTaskRuns).toHaveBeenCalledWith("Gateway restarting.");
904+expect(waitForActiveCronTaskRuns).toHaveBeenCalledWith(1_000);
905+expect(waitForActiveCronJobs).toHaveBeenCalledWith(1_000);
879906expect(resetAllLanes).toHaveBeenCalledTimes(1);
907+expect(advanceCronActiveJobGeneration).toHaveBeenCalledTimes(1);
908+expect(retireActiveCronTaskRunTracking).toHaveBeenCalledTimes(1);
909+expect(resetCronActiveJobs).toHaveBeenCalledTimes(1);
880910expect(clearRuntimeConfigSnapshot).toHaveBeenCalledTimes(1);
881911expect(resetGatewayRestartStateForInProcessRestart).toHaveBeenCalledTimes(1);
882912expect(rotateAgentEventLifecycleGeneration).toHaveBeenCalledTimes(1);
883913expect(reloadTaskRegistryFromStore).toHaveBeenCalledTimes(1);
884914expect(
885915rotateAgentEventLifecycleGeneration.mock.invocationCallOrder[0] ?? Infinity,
886916).toBeLessThan(resetAllLanes.mock.invocationCallOrder[0] ?? Infinity);
917+expect(advanceCronActiveJobGeneration.mock.invocationCallOrder[0] ?? Infinity).toBeLessThan(
918+abortActiveCronTaskRuns.mock.invocationCallOrder[0] ?? Infinity,
919+);
920+expect(waitForActiveCronJobs.mock.invocationCallOrder[0] ?? Infinity).toBeLessThan(
921+retireActiveCronTaskRunTracking.mock.invocationCallOrder[0] ?? Infinity,
922+);
923+expect(retireActiveCronTaskRunTracking.mock.invocationCallOrder[0] ?? Infinity).toBeLessThan(
924+resetCronActiveJobs.mock.invocationCallOrder[0] ?? Infinity,
925+);
926+expect(resetCronActiveJobs.mock.invocationCallOrder[0] ?? Infinity).toBeLessThan(
927+resetAllLanes.mock.invocationCallOrder[0] ?? Infinity,
928+);
887929888930sigusr1();
889931@@ -894,7 +936,13 @@ describe("runGatewayLoop", () => {
894936expectRestartCloseCall(closeSecond, 1_234);
895937expect(markGatewaySigusr1RestartHandled).toHaveBeenCalledTimes(2);
896938expect(markGatewayDraining).toHaveBeenCalledTimes(2);
939+expect(abortActiveCronTaskRuns).toHaveBeenCalledTimes(2);
940+expect(waitForActiveCronTaskRuns).toHaveBeenCalledTimes(2);
941+expect(waitForActiveCronJobs).toHaveBeenCalledTimes(2);
897942expect(resetAllLanes).toHaveBeenCalledTimes(2);
943+expect(advanceCronActiveJobGeneration).toHaveBeenCalledTimes(2);
944+expect(retireActiveCronTaskRunTracking).toHaveBeenCalledTimes(2);
945+expect(resetCronActiveJobs).toHaveBeenCalledTimes(2);
898946expect(clearRuntimeConfigSnapshot).toHaveBeenCalledTimes(2);
899947expect(resetGatewayRestartStateForInProcessRestart).toHaveBeenCalledTimes(2);
900948expect(rotateAgentEventLifecycleGeneration).toHaveBeenCalledTimes(2);
@@ -910,6 +958,42 @@ describe("runGatewayLoop", () => {
910958});
911959});
912960961+it("advances stale cron active markers after bounded restart cron-run drain", async () => {
962+vi.clearAllMocks();
963+waitForActiveCronJobs.mockResolvedValueOnce({ drained: false, active: 1 });
964+peekGatewaySigusr1RestartReason.mockReturnValue(undefined);
965+respawnGatewayProcessForUpdate.mockReturnValue({
966+mode: "disabled",
967+detail: "OPENCLAW_NO_RESPAWN",
968+});
969+970+await withIsolatedSignals(async ({ captureSignal }) => {
971+const { start, exited } = await createSignaledLoopHarness();
972+const sigusr1 = captureSignal("SIGUSR1");
973+const sigint = captureSignal("SIGINT");
974+975+sigusr1();
976+await waitForLoopCondition(
977+() => start.mock.calls.length >= 2,
978+"expected SIGUSR1 to trigger restart",
979+);
980+981+expect(abortActiveCronTaskRuns).toHaveBeenCalledWith("Gateway restarting.");
982+expect(waitForActiveCronTaskRuns).toHaveBeenCalledWith(1_000);
983+expect(waitForActiveCronJobs).toHaveBeenCalledWith(1_000);
984+expect(resetAllLanes).toHaveBeenCalledTimes(1);
985+expect(advanceCronActiveJobGeneration).toHaveBeenCalledTimes(1);
986+expect(retireActiveCronTaskRunTracking).toHaveBeenCalledTimes(1);
987+expect(resetCronActiveJobs).toHaveBeenCalledTimes(1);
988+expect(gatewayLog.warn).toHaveBeenCalledWith(
989+"cron run drain timed out during restart lifecycle reset after retiring old cron admission; 0 task handle(s) and 1 active marker(s) remain after aborting old cron runs",
990+);
991+992+sigint();
993+await expect(exited).resolves.toBe(0);
994+});
995+});
996+913997it("queues SIGUSR1 received before the run-loop installs its restart waiter", async () => {
914998vi.clearAllMocks();
915999peekGatewaySigusr1RestartReason.mockReturnValue(undefined);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。