diff --git a/benchmark/benchmain/main.go b/benchmark/benchmain/main.go index ec26909ba79d..5711540cf87c 100644 --- a/benchmark/benchmain/main.go +++ b/benchmark/benchmain/main.go @@ -373,7 +373,7 @@ func makeClients(bf stats.Features) ([]testgrpc.BenchmarkServiceClient, func()) logger.Fatalf("Failed to listen: %v", err) } opts = append(opts, grpc.WithContextDialer(func(ctx context.Context, address string) (net.Conn, error) { - return nw.ContextDialer((&net.Dialer{}).DialContext)(ctx, "tcp", lis.Addr().String()) + return nw.ContextDialer((&net.Dialer{KeepAlive: time.Duration(-1)}).DialContext)(ctx, "tcp", lis.Addr().String()) })) } lis = nw.Listener(lis) diff --git a/dialoptions.go b/dialoptions.go index cfc9fd85e8dd..d46ed37db1ec 100644 --- a/dialoptions.go +++ b/dialoptions.go @@ -413,6 +413,15 @@ func WithTimeout(d time.Duration) DialOption { // connections. If FailOnNonTempDialError() is set to true, and an error is // returned by f, gRPC checks the error's Temporary() method to decide if it // should try to reconnect to the network address. +// +// Note: As of Go 1.21, the standard library overrides the OS defaults for +// TCP keepalive time and interval to 15s. +// To retain OS defaults, use a net.Dialer with the KeepAlive field set to a +// negative value. +// +// For more information, please see [issue 23459] in the Go github repo. +// +// [issue 23459]: https://github.com/golang/go/issues/23459 func WithContextDialer(f func(context.Context, string) (net.Conn, error)) DialOption { return newFuncDialOption(func(o *dialOptions) { o.copts.Dialer = f diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go index badab8acf3b1..19e419473d1a 100644 --- a/internal/transport/http2_client.go +++ b/internal/transport/http2_client.go @@ -176,7 +176,9 @@ func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error if networkType == "tcp" && useProxy { return proxyDial(ctx, address, grpcUA) } - return (&net.Dialer{}).DialContext(ctx, networkType, address) + // KeepAlive is set to a negative value to prevent Go's override of the TCP + // keepalive time and interval; retain the OS default. + return (&net.Dialer{KeepAlive: time.Duration(-1)}).DialContext(ctx, networkType, address) } func isTemporary(err error) bool { diff --git a/internal/transport/proxy.go b/internal/transport/proxy.go index 415961987870..599d8a1d6c64 100644 --- a/internal/transport/proxy.go +++ b/internal/transport/proxy.go @@ -28,6 +28,7 @@ import ( "net/http" "net/http/httputil" "net/url" + "time" ) const proxyAuthHeaderKey = "Proxy-Authorization" @@ -122,7 +123,7 @@ func proxyDial(ctx context.Context, addr string, grpcUA string) (conn net.Conn, newAddr = proxyURL.Host } - conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", newAddr) + conn, err = (&net.Dialer{KeepAlive: time.Duration(-1)}).DialContext(ctx, "tcp", newAddr) if err != nil { return } diff --git a/server.go b/server.go index a7b5532dac2d..dd2390f49313 100644 --- a/server.go +++ b/server.go @@ -813,6 +813,15 @@ func (l *listenSocket) Close() error { // Serve returns when lis.Accept fails with fatal errors. lis will be closed when // this method returns. // Serve will return a non-nil error unless Stop or GracefulStop is called. +// +// Note: As of Go 1.21, the standard library overrides the OS defaults for +// TCP keepalive time and interval to 15s. +// To retain OS defaults, pass a net.Listener created by calling the Listen method +// on a net.ListenConfig with the `KeepAlive` field set to a negative value. +// +// For more information, please see [issue 23459] in the Go github repo. +// +// [issue 23459]: https://github.com/golang/go/issues/23459 func (s *Server) Serve(lis net.Listener) error { s.mu.Lock() s.printf("serving")