Bug Description
When DNSCaching is enabled and a hostname resolves to both IPv4 and IPv6 addresses, the DialContext closure dials both concurrently (happy-eyeballs style) and uses the first to succeed. However, when both dials succeed, the losing connection is silently discarded without being closed:
for i := 0; i < cap(ch); i++ {
if r := <-ch; conn == nil {
conn, err = r.conn, r.err
}
// BUG: if conn != nil and r.conn != nil, r.conn is leaked
}
The cancel() call on line 389 attempts to cancel the other dial, but there's a race: if both TCP connections have already been established before either goroutine calls cancel(), the second connection will be leaked.
Impact
On dual-stack hosts (very common), every DNS-cached request leaks one TCP connection. Under high load, this exhausts file descriptors and can cause too many open files errors.
Fix
Close the losing connection when the winning connection has already been selected:
for i := 0; i < cap(ch); i++ {
r := <-ch
if conn == nil {
conn, err = r.conn, r.err
} else if r.conn != nil {
r.conn.Close()
}
}
PR forthcoming.
Bug Description
When
DNSCachingis enabled and a hostname resolves to both IPv4 and IPv6 addresses, theDialContextclosure dials both concurrently (happy-eyeballs style) and uses the first to succeed. However, when both dials succeed, the losing connection is silently discarded without being closed:The
cancel()call on line 389 attempts to cancel the other dial, but there's a race: if both TCP connections have already been established before either goroutine callscancel(), the second connection will be leaked.Impact
On dual-stack hosts (very common), every DNS-cached request leaks one TCP connection. Under high load, this exhausts file descriptors and can cause
too many open fileserrors.Fix
Close the losing connection when the winning connection has already been selected:
PR forthcoming.