Bug Description
When using a Pool or Client with pipelining > 1, a TLS connect error of type ERR_TLS_CERT_ALTNAME_INVALID causes an uncaught AssertionError that crashes the process.
Reproduction
import { Pool } from 'undici'
const pool = new Pool('https://wrong.host.example.com', {
pipelining: 10,
connections: 5,
})
// Send concurrent requests — crash occurs when TLS handshake fails
await Promise.allSettled(
Array.from({ length: 20 }, () =>
pool.request({ path: '/', method: 'GET' })
)
)
Error:
AssertionError [ERR_ASSERTION]: false == true
at connect (undici/lib/client.js:1313:7)
Root Cause
In lib/client.js, the ERR_TLS_CERT_ALTNAME_INVALID connect error handler contains:
if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
assert(client[kRunning] === 0) // crashes when pipelining > 1
while (client[kPending] > 0 ...) { ... }
}
With pipelining > 1, multiple requests can be in-flight (kRunning > 0) when the TLS error fires at connect time. The assertion assumes pipelining=1 and throws when that assumption is violated.
Every other error code routes through onError() which already handles kRunning > 0 correctly. The ERR_TLS_CERT_ALTNAME_INVALID path is the only one with this broken assumption.
Fix
Drain in-flight running requests before draining pending ones, mirroring the existing onError pattern:
if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
while (client[kRunning] > 0) {
const request = client[kQueue][client[kRunningIdx]]
client[kQueue][client[kRunningIdx]++] = null
errorRequest(client, request, err)
}
client[kPendingIdx] = client[kRunningIdx]
while (client[kPending] > 0 ...) { ... }
}
Versions
- undici: 5.29.0 (same assertion exists in v6
lib/dispatcher/client.js)
- node: 20.x
- Confirmed: crash does not occur with
pipelining: 1
Bug Description
When using a
PoolorClientwithpipelining > 1, a TLS connect error of typeERR_TLS_CERT_ALTNAME_INVALIDcauses an uncaughtAssertionErrorthat crashes the process.Reproduction
Error:
Root Cause
In
lib/client.js, theERR_TLS_CERT_ALTNAME_INVALIDconnect error handler contains:With
pipelining > 1, multiple requests can be in-flight (kRunning > 0) when the TLS error fires at connect time. The assertion assumespipelining=1and throws when that assumption is violated.Every other error code routes through
onError()which already handleskRunning > 0correctly. TheERR_TLS_CERT_ALTNAME_INVALIDpath is the only one with this broken assumption.Fix
Drain in-flight running requests before draining pending ones, mirroring the existing
onErrorpattern:Versions
lib/dispatcher/client.js)pipelining: 1