Bug Description
Pool and RoundRobinPool can hang requests when clientTtl is used together with a connections limit.
Reproducible By
import http from "node:http";
import { once } from "node:events";
import { setTimeout as delay } from "node:timers/promises";
import { Pool } from "undici";
const server = http.createServer((req, res) => res.end("ok"));
server.listen(0);
await once(server, "listening");
const pool = new Pool(`http://localhost:${server.address().port}`, {
connections: 1,
clientTtl: 1,
});
const request = () =>
pool.request({ path: "/", method: "GET" }).then(({ body }) => body.text());
try {
await request();
await delay(20);
await Promise.race([
request(),
delay(1000).then(() => {
throw new Error("second request hung");
}),
]);
} catch (err) {
console.error(err.message);
process.exitCode = 1;
} finally {
pool.destroy();
server.close();
}
Expected Behavior
When a stale client is removed because its TTL expired, the pool should be able to create a replacement client immediately when under the configured usable connection limit.
Logs & Screenshots
The second request times out
Environment
macOS 26.4.1
Node v24.15.0
undici v8.1.0
Bug Description
PoolandRoundRobinPoolcan hang requests whenclientTtlis used together with aconnectionslimit.Reproducible By
Expected Behavior
When a stale client is removed because its TTL expired, the pool should be able to create a replacement client immediately when under the configured usable connection limit.
Logs & Screenshots
The second request times out
second request hungEnvironment
macOS 26.4.1
Node v24.15.0
undici v8.1.0