Skip to content

Commit 5d6ac23

Browse files
committed
fix(agents): cancel local service probe bodies
1 parent f61ad70 commit 5d6ac23

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

src/agents/provider-local-service.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,60 @@ describe("provider local service", () => {
187187
await waitForProbeFailure(healthUrl);
188188
});
189189

190+
it("cancels local service health probe response bodies", async () => {
191+
let socketClosed = false;
192+
const sockets = new Set<net.Socket>();
193+
const server = net.createServer((socket) => {
194+
sockets.add(socket);
195+
socket.on("error", () => undefined);
196+
socket.on("close", () => {
197+
sockets.delete(socket);
198+
socketClosed = true;
199+
});
200+
socket.write(
201+
["HTTP/1.1 200 OK", "Content-Type: application/json", "", '{"ok":true}'].join("\r\n"),
202+
);
203+
});
204+
await new Promise<void>((resolve, reject) => {
205+
server.once("error", reject);
206+
server.listen(0, "127.0.0.1", () => {
207+
server.off("error", reject);
208+
resolve();
209+
});
210+
});
211+
const address = server.address();
212+
if (!address || typeof address === "string") {
213+
throw new Error("missing test server port");
214+
}
215+
const model = attachModelProviderLocalService(
216+
{
217+
id: "demo",
218+
provider: "local-body-cleanup",
219+
api: "openai-completions",
220+
baseUrl: `http://127.0.0.1:${address.port}/v1`,
221+
} as unknown as Model<"openai-completions">,
222+
{
223+
command: process.execPath,
224+
args: ["--version"],
225+
healthUrl: `http://127.0.0.1:${address.port}/v1/models`,
226+
readyTimeoutMs: 1_000,
227+
idleStopMs: 1,
228+
},
229+
);
230+
231+
try {
232+
await expect(ensureModelProviderLocalService(model)).resolves.toBeUndefined();
233+
await expect.poll(() => socketClosed, { timeout: 1000, interval: 20 }).toBe(true);
234+
} finally {
235+
for (const socket of sockets) {
236+
socket.destroy();
237+
}
238+
await new Promise<void>((resolve, reject) => {
239+
server.close((error) => (error ? reject(error) : resolve()));
240+
});
241+
}
242+
});
243+
190244
it("serializes concurrent cold starts for the same local service", async () => {
191245
const port = await freePort();
192246
const healthUrl = `http://127.0.0.1:${port}/v1/models`;

src/agents/provider-local-service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,9 @@ async function probeHealth(
226226
timeout.unref?.();
227227
const onAbort = () => controller.abort(toAbortError(signal));
228228
signal?.addEventListener("abort", onAbort, { once: true });
229+
let response: Response | undefined;
229230
try {
230-
const response = await fetch(url, { headers, signal: controller.signal });
231+
response = await fetch(url, { headers, signal: controller.signal });
231232
return response.ok;
232233
} catch {
233234
if (signal?.aborted) {
@@ -237,6 +238,7 @@ async function probeHealth(
237238
} finally {
238239
clearTimeout(timeout);
239240
signal?.removeEventListener("abort", onAbort);
241+
await response?.body?.cancel?.().catch(() => undefined);
240242
}
241243
}
242244

0 commit comments

Comments
 (0)