diff --git a/src/cli/daemon-cli/lifecycle.test.ts b/src/cli/daemon-cli/lifecycle.test.ts index f026f81399f..7da1935c1e2 100644 --- a/src/cli/daemon-cli/lifecycle.test.ts +++ b/src/cli/daemon-cli/lifecycle.test.ts @@ -3,7 +3,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite type RestartHealthSnapshot = { healthy: boolean; staleGatewayPids: number[]; - runtime: { status?: string }; + runtime: { status?: string; pid?: number }; portUsage: { port: number; status: string; listeners: []; hints: []; errors?: string[] }; }; @@ -206,6 +206,30 @@ describe("runDaemonRestart health checks", () => { expect(waitForGatewayHealthyRestart).toHaveBeenCalledTimes(2); }); + it("does not kill the current running gateway pid when stale detection includes self", async () => { + const unhealthy: RestartHealthSnapshot = { + healthy: false, + staleGatewayPids: [1993, 2111], + runtime: { status: "running", pid: 1993 }, + portUsage: { port: 18789, status: "busy", listeners: [], hints: [] }, + }; + const healthy: RestartHealthSnapshot = { + healthy: true, + staleGatewayPids: [], + runtime: { status: "running", pid: 1993 }, + portUsage: { port: 18789, status: "busy", listeners: [], hints: [] }, + }; + waitForGatewayHealthyRestart.mockResolvedValueOnce(unhealthy).mockResolvedValueOnce(healthy); + terminateStaleGatewayPids.mockResolvedValue([2111]); + + const result = await runDaemonRestart({ json: true }); + + expect(result).toBe(true); + expect(terminateStaleGatewayPids).toHaveBeenCalledWith([2111]); + expect(service.restart).toHaveBeenCalledTimes(1); + expect(waitForGatewayHealthyRestart).toHaveBeenCalledTimes(2); + }); + it("skips stale-pid retry health checks when the retry restart is only scheduled", async () => { const unhealthy: RestartHealthSnapshot = { healthy: false, diff --git a/src/cli/daemon-cli/lifecycle.ts b/src/cli/daemon-cli/lifecycle.ts index d3e01f66412..df85ee3cc04 100644 --- a/src/cli/daemon-cli/lifecycle.ts +++ b/src/cli/daemon-cli/lifecycle.ts @@ -207,15 +207,22 @@ export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promi includeUnknownListenersAsStale: process.platform === "win32", }); - if (!health.healthy && health.staleGatewayPids.length > 0) { - const staleMsg = `Found stale gateway process(es): ${health.staleGatewayPids.join(", ")}.`; + const staleGatewayPids = health.staleGatewayPids.filter((pid) => { + if (health.runtime.status !== "running") { + return true; + } + return health.runtime.pid == null || pid !== health.runtime.pid; + }); + + if (!health.healthy && staleGatewayPids.length > 0) { + const staleMsg = `Found stale gateway process(es): ${staleGatewayPids.join(", ")}.`; warnings.push(staleMsg); if (!json) { defaultRuntime.log(theme.warn(staleMsg)); defaultRuntime.log(theme.muted("Stopping stale process(es) and retrying restart...")); } - await terminateStaleGatewayPids(health.staleGatewayPids); + await terminateStaleGatewayPids(staleGatewayPids); const retryRestart = await service.restart({ env: process.env, stdout }); if (retryRestart.outcome === "scheduled") { return retryRestart;