From 01b81fe1441d83ea4bbb913f1e76eded79d19384 Mon Sep 17 00:00:00 2001 From: Intern Dev Date: Mon, 13 Apr 2026 00:48:59 -0400 Subject: [PATCH] fix(nim): bound curl health probes with connect and max timeouts The NIM health probe loop and nimStatusByName() use `curl -sf` without any timeout. On a hung or silently-dropping NIM endpoint the curl call can block indefinitely, stalling onboard/waitForNimHealth and CLI status. Add `--connect-timeout 5 --max-time 5` to both probe calls so each attempt fails fast on unresponsive endpoints and the outer retry loop can make progress. Signed-off-by: Intern Dev --- src/lib/nim.test.ts | 44 +++++++++++++++++++++++++++++++++++++++----- src/lib/nim.ts | 25 +++++++++++++++++++++---- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/lib/nim.test.ts b/src/lib/nim.test.ts index 85edca72308..a8db9e19477 100644 --- a/src/lib/nim.test.ts +++ b/src/lib/nim.test.ts @@ -32,6 +32,24 @@ function loadNimWithMockedRunner(runCapture: Mock) { }; } +/** Check if an argv array or legacy shell command contains a specific argument. */ +function hasArg(cmd: string | string[], arg: string): boolean { + return Array.isArray(cmd) ? cmd.includes(arg) : cmd.includes(arg); +} + +function hasCurlTimeoutArgs(cmd: string | string[]): boolean { + if (!Array.isArray(cmd)) { + return ( + cmd.includes("curl") && + cmd.includes("--connect-timeout 5") && + cmd.includes("--max-time 5") + ); + } + const connectTimeout = cmd.indexOf("--connect-timeout"); + const maxTime = cmd.indexOf("--max-time"); + return cmd[0] === "curl" && cmd[connectTimeout + 1] === "5" && cmd[maxTime + 1] === "5"; +} + describe("nim", () => { describe("listModels", () => { it("returns 5 models", () => { @@ -176,12 +194,27 @@ describe("nim", () => { }); }); - describe("nimStatusByName", () => { - /** Check if an argv array contains a specific element. */ - function hasArg(cmd: string | string[], arg: string): boolean { - return Array.isArray(cmd) ? cmd.includes(arg) : cmd.includes(arg); - } + describe("waitForNimHealth", () => { + it("bounds curl health probes with connect and total timeouts", () => { + const runCapture = vi.fn((cmd: string | string[]) => { + if (!Array.isArray(cmd)) throw new Error("expected argv array"); + if (cmd[0] === "curl" && hasArg(cmd, "http://localhost:9000/v1/models")) return '{"data":[]}'; + return ""; + }); + const { nimModule, restore } = loadNimWithMockedRunner(runCapture); + try { + expect(nimModule.waitForNimHealth(9000, 1)).toBe(true); + const commands = runCapture.mock.calls.map(([c]: [string | string[]]) => c); + + expect(commands.some((c) => c[0] === "curl" && hasCurlTimeoutArgs(c))).toBe(true); + } finally { + restore(); + } + }); + }); + + describe("nimStatusByName", () => { it("uses provided port directly", () => { const runCapture = vi.fn((cmd: string | string[]) => { if (!Array.isArray(cmd)) throw new Error("expected argv array"); @@ -205,6 +238,7 @@ describe("nim", () => { expect(commands.some((c) => c.includes("http://localhost:9000/v1/models"))).toBe( true, ); + expect(commands.some((c) => c[0] === "curl" && hasCurlTimeoutArgs(c))).toBe(true); } finally { restore(); } diff --git a/src/lib/nim.ts b/src/lib/nim.ts index 03f17f4ba24..2a3da454d77 100644 --- a/src/lib/nim.ts +++ b/src/lib/nim.ts @@ -220,9 +220,18 @@ export function waitForNimHealth(port = VLLM_PORT, timeout = 300): boolean { while ((Date.now() - start) / 1000 < timeout) { try { - const result = runCapture(["curl", "-sf", `http://localhost:${hostPort}/v1/models`], { - ignoreError: true, - }); + const result = runCapture( + [ + "curl", + "-sf", + "--connect-timeout", + "5", + "--max-time", + "5", + `http://localhost:${hostPort}/v1/models`, + ], + { ignoreError: true }, + ); if (result) { console.log(" NIM is healthy."); return true; @@ -272,7 +281,15 @@ export function nimStatusByName(name: string, port?: number): NimStatus { resolvedHostPort = m ? Number(m[1]) : VLLM_PORT; } const health = runCapture( - ["curl", "-sf", `http://localhost:${resolvedHostPort}/v1/models`], + [ + "curl", + "-sf", + "--connect-timeout", + "5", + "--max-time", + "5", + `http://localhost:${resolvedHostPort}/v1/models`, + ], { ignoreError: true }, ); healthy = !!health;