mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-07-16 13:17:19 +02:00
fix(doq): share QUIC transport, close send side before read (RFC 9250)
DoQ pools now keep a single quic.Transport and UDP socket for all dials, so parallel dial and reconnect churn no longer allocate a new socket per attempt or leak the winner's UDP conn when the caller owns the packet conn. quicParallelDialer accepts an optional transport: when set, dials use Transport.DialEarly on that socket; when nil, behavior matches the old per-dial ListenUDP path (losers close their sockets). Per RFC 9250 §4.2, close the query stream's send side before reading the response so strict upstreams see STREAM FIN before answering. CloseIdleConnections closes the shared transport and underlying UDP conn so checked-out connections and the OS socket are torn down. Add a FIN-strict test server, coverage for bootstrap vs parallel-dial paths, and a Linux-only FD churn regression test.
This commit is contained in:
committed by
Cuong Manh Le
parent
f1309121ae
commit
35455eb0b9
+28
-6
@@ -77,7 +77,17 @@ type parallelDialerResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type quicParallelDialer struct{}
|
||||
// quicParallelDialer races DialEarly across a list of remote addresses and
|
||||
// returns the first successful connection. When transport is non-nil, all
|
||||
// dials share that transport's UDP socket, which removes both the per-dial
|
||||
// socket allocation and the winner-path socket leak that an owner-of-the-conn
|
||||
// receiver cannot clean up. When transport is nil, the dialer falls back to a
|
||||
// fresh UDP socket per attempt (compat path used where no shared transport is
|
||||
// available yet); the loser paths close their sockets, and the winner path's
|
||||
// socket is owned by quic.DialEarly's internal transport.
|
||||
type quicParallelDialer struct {
|
||||
transport *quic.Transport
|
||||
}
|
||||
|
||||
// Dial performs parallel dialing to the given address list.
|
||||
func (d *quicParallelDialer) Dial(ctx context.Context, addrs []string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
||||
@@ -105,12 +115,24 @@ func (d *quicParallelDialer) Dial(ctx context.Context, addrs []string, tlsCfg *t
|
||||
ch <- ¶llelDialerResult{conn: nil, err: err}
|
||||
return
|
||||
}
|
||||
udpConn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
ch <- ¶llelDialerResult{conn: nil, err: err}
|
||||
return
|
||||
var (
|
||||
conn *quic.Conn
|
||||
udpConn *net.UDPConn
|
||||
)
|
||||
if d.transport != nil {
|
||||
conn, err = d.transport.DialEarly(ctx, remoteAddr, tlsCfg, cfg)
|
||||
} else {
|
||||
udpConn, err = net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
ch <- ¶llelDialerResult{conn: nil, err: err}
|
||||
return
|
||||
}
|
||||
conn, err = quic.DialEarly(ctx, udpConn, remoteAddr, tlsCfg, cfg)
|
||||
if err != nil {
|
||||
udpConn.Close()
|
||||
udpConn = nil
|
||||
}
|
||||
}
|
||||
conn, err := quic.DialEarly(ctx, udpConn, remoteAddr, tlsCfg, cfg)
|
||||
select {
|
||||
case ch <- ¶llelDialerResult{conn: conn, err: err}:
|
||||
case <-done:
|
||||
|
||||
Reference in New Issue
Block a user