Skip to content

Commit a03c7f1

Browse files
authored
client: always enable TCP keepalives with OS defaults (#6834)
1 parent c2398ce commit a03c7f1

File tree

6 files changed

+81
-25
lines changed

6 files changed

+81
-25
lines changed

benchmark/benchmain/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import (
6767
"google.golang.org/grpc/credentials/insecure"
6868
"google.golang.org/grpc/experimental"
6969
"google.golang.org/grpc/grpclog"
70+
"google.golang.org/grpc/internal"
7071
"google.golang.org/grpc/internal/channelz"
7172
"google.golang.org/grpc/keepalive"
7273
"google.golang.org/grpc/metadata"
@@ -373,7 +374,7 @@ func makeClients(bf stats.Features) ([]testgrpc.BenchmarkServiceClient, func())
373374
logger.Fatalf("Failed to listen: %v", err)
374375
}
375376
opts = append(opts, grpc.WithContextDialer(func(ctx context.Context, address string) (net.Conn, error) {
376-
return nw.ContextDialer((&net.Dialer{KeepAlive: time.Duration(-1)}).DialContext)(ctx, "tcp", lis.Addr().String())
377+
return nw.ContextDialer((internal.NetDialerWithTCPKeepalive().DialContext))(ctx, "tcp", lis.Addr().String())
377378
}))
378379
}
379380
lis = nw.Listener(lis)

dialoptions.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -401,10 +401,12 @@ func WithTimeout(d time.Duration) DialOption {
401401
// returned by f, gRPC checks the error's Temporary() method to decide if it
402402
// should try to reconnect to the network address.
403403
//
404-
// Note: As of Go 1.21, the standard library overrides the OS defaults for
405-
// TCP keepalive time and interval to 15s.
406-
// To retain OS defaults, use a net.Dialer with the KeepAlive field set to a
407-
// negative value.
404+
// Note: All supported releases of Go (as of December 2023) override the OS
405+
// defaults for TCP keepalive time and interval to 15s. To enable TCP keepalive
406+
// with OS defaults for keepalive time and interval, use a net.Dialer that sets
407+
// the KeepAlive field to a negative value, and sets the SO_KEEPALIVE socket
408+
// option to true from the Control field. For a concrete example of how to do
409+
// this, see internal.NetDialerWithTCPKeepalive().
408410
//
409411
// For more information, please see [issue 23459] in the Go github repo.
410412
//

internal/tcp_keepalive.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2023 gRPC authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package internal
19+
20+
import (
21+
"net"
22+
"syscall"
23+
"time"
24+
)
25+
26+
// NetDialerWithTCPKeepalive returns a net.Dialer that enables TCP keepalives on
27+
// the underlying connection with OS default values for keepalive parameters.
28+
//
29+
// TODO: Once https://github.com/golang/go/issues/62254 lands, and the
30+
// appropriate Go version becomes less than our least supported Go version, we
31+
// should look into using the new API to make things more straightforward.
32+
func NetDialerWithTCPKeepalive() *net.Dialer {
33+
return &net.Dialer{
34+
// Setting a negative value here prevents the Go stdlib from overriding
35+
// the values of TCP keepalive time and interval. It also prevents the
36+
// Go stdlib from enabling TCP keepalives by default.
37+
KeepAlive: time.Duration(-1),
38+
// This method is called after the underlying network socket is created,
39+
// but before dialing the socket (or calling its connect() method). The
40+
// combination of unconditionally enabling TCP keepalives here, and
41+
// disabling the overriding of TCP keepalive parameters by setting the
42+
// KeepAlive field to a negative value above, results in OS defaults for
43+
// the TCP keealive interval and time parameters.
44+
Control: func(_, _ string, c syscall.RawConn) error {
45+
return c.Control(func(fd uintptr) {
46+
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1)
47+
})
48+
},
49+
}
50+
}

internal/transport/http2_client.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ import (
3636
"golang.org/x/net/http2/hpack"
3737
"google.golang.org/grpc/codes"
3838
"google.golang.org/grpc/credentials"
39+
"google.golang.org/grpc/internal"
3940
"google.golang.org/grpc/internal/channelz"
4041
icredentials "google.golang.org/grpc/internal/credentials"
4142
"google.golang.org/grpc/internal/grpclog"
4243
"google.golang.org/grpc/internal/grpcsync"
4344
"google.golang.org/grpc/internal/grpcutil"
4445
imetadata "google.golang.org/grpc/internal/metadata"
4546
istatus "google.golang.org/grpc/internal/status"
46-
"google.golang.org/grpc/internal/syscall"
47+
isyscall "google.golang.org/grpc/internal/syscall"
4748
"google.golang.org/grpc/internal/transport/networktype"
4849
"google.golang.org/grpc/keepalive"
4950
"google.golang.org/grpc/metadata"
@@ -176,9 +177,7 @@ func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error
176177
if networkType == "tcp" && useProxy {
177178
return proxyDial(ctx, address, grpcUA)
178179
}
179-
// KeepAlive is set to a negative value to prevent Go's override of the TCP
180-
// keepalive time and interval; retain the OS default.
181-
return (&net.Dialer{KeepAlive: time.Duration(-1)}).DialContext(ctx, networkType, address)
180+
return internal.NetDialerWithTCPKeepalive().DialContext(ctx, networkType, address)
182181
}
183182

184183
func isTemporary(err error) bool {
@@ -264,7 +263,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts
264263
}
265264
keepaliveEnabled := false
266265
if kp.Time != infinity {
267-
if err = syscall.SetTCPUserTimeout(conn, kp.Timeout); err != nil {
266+
if err = isyscall.SetTCPUserTimeout(conn, kp.Timeout); err != nil {
268267
return nil, connectionErrorf(false, err, "transport: failed to set TCP_USER_TIMEOUT: %v", err)
269268
}
270269
keepaliveEnabled = true

internal/transport/proxy.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import (
2828
"net/http"
2929
"net/http/httputil"
3030
"net/url"
31-
"time"
31+
32+
"google.golang.org/grpc/internal"
3233
)
3334

3435
const proxyAuthHeaderKey = "Proxy-Authorization"
@@ -113,7 +114,7 @@ func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, backendAddr stri
113114
// proxyDial dials, connecting to a proxy first if necessary. Checks if a proxy
114115
// is necessary, dials, does the HTTP CONNECT handshake, and returns the
115116
// connection.
116-
func proxyDial(ctx context.Context, addr string, grpcUA string) (conn net.Conn, err error) {
117+
func proxyDial(ctx context.Context, addr string, grpcUA string) (net.Conn, error) {
117118
newAddr := addr
118119
proxyURL, err := mapAddress(addr)
119120
if err != nil {
@@ -123,15 +124,15 @@ func proxyDial(ctx context.Context, addr string, grpcUA string) (conn net.Conn,
123124
newAddr = proxyURL.Host
124125
}
125126

126-
conn, err = (&net.Dialer{KeepAlive: time.Duration(-1)}).DialContext(ctx, "tcp", newAddr)
127+
conn, err := internal.NetDialerWithTCPKeepalive().DialContext(ctx, "tcp", newAddr)
127128
if err != nil {
128-
return
129+
return nil, err
129130
}
130-
if proxyURL != nil {
131+
if proxyURL == nil {
131132
// proxy is disabled if proxyURL is nil.
132-
conn, err = doHTTPConnectHandshake(ctx, conn, addr, proxyURL, grpcUA)
133+
return conn, err
133134
}
134-
return
135+
return doHTTPConnectHandshake(ctx, conn, addr, proxyURL, grpcUA)
135136
}
136137

137138
func sendHTTPRequest(ctx context.Context, req *http.Request, conn net.Conn) error {

server.go

+11-8
Original file line numberDiff line numberDiff line change
@@ -814,14 +814,17 @@ func (l *listenSocket) Close() error {
814814
// this method returns.
815815
// Serve will return a non-nil error unless Stop or GracefulStop is called.
816816
//
817-
// Note: As of Go 1.21, the standard library overrides the OS defaults for
818-
// TCP keepalive time and interval to 15s.
819-
// To retain OS defaults, pass a net.Listener created by calling the Listen method
820-
// on a net.ListenConfig with the `KeepAlive` field set to a negative value.
821-
//
822-
// For more information, please see [issue 23459] in the Go github repo.
823-
//
824-
// [issue 23459]: https://github.com/golang/go/issues/23459
817+
// Note: All supported releases of Go (as of December 2023) override the OS
818+
// defaults for TCP keepalive time and interval to 15s. To enable TCP keepalive
819+
// with OS defaults for keepalive time and interval, callers need to do the
820+
// following two things:
821+
// - pass a net.Listener created by calling the Listen method on a
822+
// net.ListenConfig with the `KeepAlive` field set to a negative value. This
823+
// will result in the Go standard library not overriding OS defaults for TCP
824+
// keepalive interval and time. But this will also result in the Go standard
825+
// library not enabling TCP keepalives by default.
826+
// - override the Accept method on the passed in net.Listener and set the
827+
// SO_KEEPALIVE socket option to enable TCP keepalives, with OS defaults.
825828
func (s *Server) Serve(lis net.Listener) error {
826829
s.mu.Lock()
827830
s.printf("serving")

0 commit comments

Comments
 (0)