

























@@ -19,10 +19,76 @@ import {
1919} from "./restart.js";
2020import { listTailnetAddresses } from "./tailnet.js";
212122+const relaunchGatewayScheduledTaskMock = vi.hoisted(() => vi.fn());
23+const cleanStaleGatewayProcessesSyncMock = vi.hoisted(() => vi.fn());
24+const findGatewayPidsOnPortSyncMock = vi.hoisted(() => vi.fn());
25+26+vi.mock("./restart-stale-pids.js", () => ({
27+cleanStaleGatewayProcessesSync: (...args: unknown[]) =>
28+cleanStaleGatewayProcessesSyncMock(...args),
29+findGatewayPidsOnPortSync: (...args: unknown[]) => findGatewayPidsOnPortSyncMock(...args),
30+}));
31+32+vi.mock("./windows-task-restart.js", () => ({
33+relaunchGatewayScheduledTask: (...args: unknown[]) => relaunchGatewayScheduledTaskMock(...args),
34+}));
35+36+const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
37+38+function setPlatform(platform: NodeJS.Platform): void {
39+if (!originalPlatformDescriptor) {
40+return;
41+}
42+Object.defineProperty(process, "platform", {
43+ ...originalPlatformDescriptor,
44+value: platform,
45+});
46+}
47+48+function withoutSigusr1Listeners(fn: () => void): void {
49+const listeners = process.listeners("SIGUSR1");
50+process.removeAllListeners("SIGUSR1");
51+try {
52+fn();
53+} finally {
54+process.removeAllListeners("SIGUSR1");
55+for (const listener of listeners) {
56+process.on("SIGUSR1", listener);
57+}
58+}
59+}
60+61+function withRestartSupervisorEnabled(fn: () => void): void {
62+const originalVitest = process.env.VITEST;
63+const originalNodeEnv = process.env.NODE_ENV;
64+delete process.env.VITEST;
65+delete process.env.NODE_ENV;
66+try {
67+fn();
68+} finally {
69+if (originalVitest === undefined) {
70+delete process.env.VITEST;
71+} else {
72+process.env.VITEST = originalVitest;
73+}
74+if (originalNodeEnv === undefined) {
75+delete process.env.NODE_ENV;
76+} else {
77+process.env.NODE_ENV = originalNodeEnv;
78+}
79+}
80+}
81+2282describe("infra runtime", () => {
2383function setupRestartSignalSuite() {
2484beforeEach(() => {
2585__testing.resetSigusr1State();
86+relaunchGatewayScheduledTaskMock.mockReset();
87+relaunchGatewayScheduledTaskMock.mockReturnValue({ ok: true, method: "schtasks" });
88+cleanStaleGatewayProcessesSyncMock.mockReset();
89+cleanStaleGatewayProcessesSyncMock.mockReturnValue([]);
90+findGatewayPidsOnPortSyncMock.mockReset();
91+findGatewayPidsOnPortSyncMock.mockReturnValue([]);
2692vi.useFakeTimers();
2793vi.spyOn(process, "kill").mockImplementation(() => true);
2894});
@@ -33,6 +99,9 @@ describe("infra runtime", () => {
3399clearConfigCache();
34100await vi.runOnlyPendingTimersAsync();
35101vi.useRealTimers();
102+if (originalPlatformDescriptor) {
103+Object.defineProperty(process, "platform", originalPlatformDescriptor);
104+}
36105vi.restoreAllMocks();
37106});
38107}
@@ -80,6 +149,53 @@ describe("infra runtime", () => {
80149}
81150});
82151152+it("uses the SIGUSR1 listener path on Windows when the run loop is active", () => {
153+setPlatform("win32");
154+const emitSpy = vi.spyOn(process, "emit");
155+const handler = () => {};
156+process.on("SIGUSR1", handler);
157+try {
158+expect(emitGatewayRestart()).toBe(true);
159+expect(emitSpy).toHaveBeenCalledWith("SIGUSR1");
160+expect(relaunchGatewayScheduledTaskMock).not.toHaveBeenCalled();
161+} finally {
162+process.removeListener("SIGUSR1", handler);
163+}
164+});
165+166+it("uses the Windows supervisor fallback without leaving a restart cycle in flight", () => {
167+setPlatform("win32");
168+withoutSigusr1Listeners(() => {
169+withRestartSupervisorEnabled(() => {
170+relaunchGatewayScheduledTaskMock.mockReturnValueOnce({ ok: true, method: "schtasks" });
171+172+expect(emitGatewayRestart("windows-fallback")).toBe(true);
173+174+expect(relaunchGatewayScheduledTaskMock).toHaveBeenCalledTimes(1);
175+expect(consumeGatewaySigusr1RestartAuthorization()).toBe(false);
176+const next = scheduleGatewaySigusr1Restart({ delayMs: 0, reason: "next" });
177+expect(next.coalesced).toBe(false);
178+expect(next.mode).toBe("supervisor");
179+});
180+});
181+});
182+183+it("rolls back the Windows supervisor fallback when scheduling fails", () => {
184+setPlatform("win32");
185+withoutSigusr1Listeners(() => {
186+withRestartSupervisorEnabled(() => {
187+relaunchGatewayScheduledTaskMock
188+.mockReturnValueOnce({ ok: false, method: "schtasks", detail: "denied" })
189+.mockReturnValueOnce({ ok: true, method: "schtasks" });
190+191+expect(emitGatewayRestart("windows-fallback")).toBe(false);
192+expect(consumeGatewaySigusr1RestartAuthorization()).toBe(false);
193+expect(emitGatewayRestart("windows-retry")).toBe(true);
194+expect(relaunchGatewayScheduledTaskMock).toHaveBeenCalledTimes(2);
195+});
196+});
197+});
198+83199it("coalesces duplicate scheduled restarts into a single pending timer", async () => {
84200const emitSpy = vi.spyOn(process, "emit");
85201const handler = () => {};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。