AssertionError in ERR_TLS_CERT_ALTNAME_INVALID handler when pipelining > 1 (original) (raw)
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