diff --git a/src/gateway/server-close.test.ts b/src/gateway/server-close.test.ts index 04fa3580fc4d..d5e75b11acc5 100644 --- a/src/gateway/server-close.test.ts +++ b/src/gateway/server-close.test.ts @@ -182,6 +182,26 @@ describe("createGatewayCloseHandler", () => { } }); + it("still runs later teardown when cron.stopAndDrain() rejects (no listener strand)", async () => { + const stopAndDrain = vi.fn().mockRejectedValue(new Error("stream watcher stop failed")); + const httpClose = vi.fn((cb: (err?: Error | null) => void) => cb(null)); + const deps = createGatewayCloseTestDeps({ + cron: { stop: vi.fn(), stopAndDrain } as never, + httpServer: { close: httpClose, closeIdleConnections: vi.fn() } as never, + }); + const close = createGatewayCloseHandler(deps); + + const result = await close({ reason: "test" }); + + // A rejecting stopAndDrain must be swallowed (recorded as a warning) and must NOT skip the + // remaining teardown -- otherwise the HTTP/WS listeners and timers strand and the next + // start hits EADDRINUSE. + expect(stopAndDrain).toHaveBeenCalledTimes(1); + expect(deps.heartbeatRunner.stop).toHaveBeenCalledTimes(1); + expect(httpClose).toHaveBeenCalled(); + expect(result.warnings.length).toBeGreaterThan(0); + }); + it("completes a clean shutdown with a ShutdownResult", async () => { const deps = createGatewayCloseTestDeps(); const close = createGatewayCloseHandler(deps); diff --git a/src/gateway/server-close.ts b/src/gateway/server-close.ts index a27f6d09477a..ee9a7b28c5e1 100644 --- a/src/gateway/server-close.ts +++ b/src/gateway/server-close.ts @@ -883,12 +883,12 @@ export function createGatewayCloseHandler( await measureCloseStep("gmail-watcher", () => shutdownStep("gmail-watcher", () => stopGmailWatcherOnDemand(), warnings), ); - if (params.cron.stopAndDrain) { - await params.cron.stopAndDrain(); - } else { - params.cron.stop(); - } - params.heartbeatRunner.stop(); + await shutdownStep( + "cron", + () => (params.cron.stopAndDrain ? params.cron.stopAndDrain() : params.cron.stop()), + warnings, + ); + await shutdownStep("heartbeat-runner", () => params.heartbeatRunner.stop(), warnings); await shutdownStep( "task-registry-maintenance", () => params.stopTaskRegistryMaintenance?.(),