net/http: add pluggable HTTP/3 support for closing idle connections

This CL defines the closeIdleConectionser interface, allowing us to call
a CloseIdleConnections method that a pluggable HTTP/3 transport might
implement.

Concretely, this allows an HTTP/3 transport implementation to clean up
resources such as an open UDP socket that is no longer needed,
preventing resources from lingering around indefinitely until the entire
program exits.

For #77440

Change-Id: I7216caee58954c3651f96a56dbf27ec74c539ad3
Reviewed-on: https://go-review.googlesource.com/c/go/+/753161
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Nicholas S. Husin
2026-03-09 17:02:27 -04:00
committed by Nicholas Husin
parent e30e65f7a8
commit 96d6d38872

View File

@@ -388,9 +388,8 @@ type dialClientConner interface {
// If HTTP/3 proxies are not supported, DialClientConn should return
// an error wrapping [errors.ErrUnsupported].
//
// The RoundTripper returned by DialClientConn may implement
// any of the following methods to support the [ClientConn]
// method of the same name:
// The RoundTripper returned by DialClientConn must also implement the
// following methods to support [ClientConn] methods of the same name:
// Close() error
// Err() error
// Reserve() error
@@ -411,6 +410,16 @@ type dialClientConner interface {
DialClientConn(ctx context.Context, address string, proxy *url.URL, internalStateHook func()) (RoundTripper, error)
}
type closeIdleConnectionser interface {
// CloseIdleConnections is called by Transport.CloseIdleConnections.
//
// The transport will close idle connections created with DialClientConn
// before calling this method. The HTTP/3 transport should not attempt to
// close idle connections, but may clean up shared resources such as UDP
// sockets if no connections remain.
CloseIdleConnections()
}
// h2Transport is the interface we expect to be able to call from
// net/http against an *http2.Transport that's either bundled into
// h2_bundle.go or supplied by the user via x/net/http2.
@@ -956,6 +965,9 @@ func (t *Transport) CloseIdleConnections() {
if t2 := t.h2transport; t2 != nil {
t2.CloseIdleConnections()
}
if cc, ok := t.h3transport.(closeIdleConnectionser); ok {
cc.CloseIdleConnections()
}
}
// prepareTransportCancel sets up state to convert Transport.CancelRequest into context cancelation.
@@ -3088,11 +3100,16 @@ func (pc *persistConn) closeLocked(err error) {
pc.t.decConnsPerHost(pc.cacheKey)
// Close HTTP/1 (pc.alt == nil) connection.
// HTTP/2 closes its connection itself.
// Close HTTP/3 connection if it implements io.Closer.
if pc.alt == nil {
if err != errCallerOwnsConn {
pc.conn.Close()
}
close(pc.closech)
} else {
if cc, ok := pc.alt.(io.Closer); ok {
cc.Close()
}
}
}
pc.mutateHeaderFunc = nil