Merge pull request #14366 from ahrtr/keepalive_3.5_20220820

[3.5] Refactor the keepAliveListener and keepAliveConn
This commit is contained in:
Benjamin Wang 2022-08-24 10:14:26 +08:00 committed by GitHub
commit 74aa38ec10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 28 deletions

View File

@ -21,26 +21,29 @@ import (
"time" "time"
) )
type keepAliveConn interface {
SetKeepAlive(bool) error
SetKeepAlivePeriod(d time.Duration) error
}
// NewKeepAliveListener returns a listener that listens on the given address. // NewKeepAliveListener returns a listener that listens on the given address.
// Be careful when wrap around KeepAliveListener with another Listener if TLSInfo is not nil. // Be careful when wrap around KeepAliveListener with another Listener if TLSInfo is not nil.
// Some pkgs (like go/http) might expect Listener to return TLSConn type to start TLS handshake. // Some pkgs (like go/http) might expect Listener to return TLSConn type to start TLS handshake.
// http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html // http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
//
// Note(ahrtr):
// only `net.TCPConn` supports `SetKeepAlive` and `SetKeepAlivePeriod`
// by default, so if you want to wrap multiple layers of net.Listener,
// the `keepaliveListener` should be the one which is closest to the
// original `net.Listener` implementation, namely `TCPListener`.
func NewKeepAliveListener(l net.Listener, scheme string, tlscfg *tls.Config) (net.Listener, error) { func NewKeepAliveListener(l net.Listener, scheme string, tlscfg *tls.Config) (net.Listener, error) {
kal := &keepaliveListener{
Listener: l,
}
if scheme == "https" { if scheme == "https" {
if tlscfg == nil { if tlscfg == nil {
return nil, fmt.Errorf("cannot listen on TLS for given listener: KeyFile and CertFile are not presented") return nil, fmt.Errorf("cannot listen on TLS for given listener: KeyFile and CertFile are not presented")
} }
return newTLSKeepaliveListener(l, tlscfg), nil return newTLSKeepaliveListener(kal, tlscfg), nil
} }
return &keepaliveListener{ return kal, nil
Listener: l,
}, nil
} }
type keepaliveListener struct{ net.Listener } type keepaliveListener struct{ net.Listener }
@ -50,13 +53,43 @@ func (kln *keepaliveListener) Accept() (net.Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
kac := c.(keepAliveConn)
kac, err := createKeepaliveConn(c)
if err != nil {
return nil, fmt.Errorf("create keepalive connection failed, %w", err)
}
// detection time: tcp_keepalive_time + tcp_keepalive_probes + tcp_keepalive_intvl // detection time: tcp_keepalive_time + tcp_keepalive_probes + tcp_keepalive_intvl
// default on linux: 30 + 8 * 30 // default on linux: 30 + 8 * 30
// default on osx: 30 + 8 * 75 // default on osx: 30 + 8 * 75
kac.SetKeepAlive(true) if err := kac.SetKeepAlive(true); err != nil {
kac.SetKeepAlivePeriod(30 * time.Second) return nil, fmt.Errorf("SetKeepAlive failed, %w", err)
return c, nil }
if err := kac.SetKeepAlivePeriod(30 * time.Second); err != nil {
return nil, fmt.Errorf("SetKeepAlivePeriod failed, %w", err)
}
return kac, nil
}
func createKeepaliveConn(c net.Conn) (*keepAliveConn, error) {
tcpc, ok := c.(*net.TCPConn)
if !ok {
return nil, ErrNotTCP
}
return &keepAliveConn{tcpc}, nil
}
type keepAliveConn struct {
*net.TCPConn
}
// SetKeepAlive sets keepalive
func (l *keepAliveConn) SetKeepAlive(doKeepAlive bool) error {
return l.TCPConn.SetKeepAlive(doKeepAlive)
}
// SetKeepAlivePeriod sets keepalive period
func (l *keepAliveConn) SetKeepAlivePeriod(d time.Duration) error {
return l.TCPConn.SetKeepAlivePeriod(d)
} }
// A tlsKeepaliveListener implements a network listener (net.Listener) for TLS connections. // A tlsKeepaliveListener implements a network listener (net.Listener) for TLS connections.
@ -72,12 +105,6 @@ func (l *tlsKeepaliveListener) Accept() (c net.Conn, err error) {
if err != nil { if err != nil {
return return
} }
kac := c.(keepAliveConn)
// detection time: tcp_keepalive_time + tcp_keepalive_probes + tcp_keepalive_intvl
// default on linux: 30 + 8 * 30
// default on osx: 30 + 8 * 75
kac.SetKeepAlive(true)
kac.SetKeepAlivePeriod(30 * time.Second)
c = tls.Server(c, l.config) c = tls.Server(c, l.config)
return c, nil return c, nil
} }

View File

@ -40,6 +40,9 @@ func TestNewKeepAliveListener(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected Accept error: %v", err) t.Fatalf("unexpected Accept error: %v", err)
} }
if _, ok := conn.(*keepAliveConn); !ok {
t.Fatalf("Unexpected conn type: %T, wanted *keepAliveConn", conn)
}
conn.Close() conn.Close()
ln.Close() ln.Close()

View File

@ -63,6 +63,9 @@ func (l *limitListenerConn) Close() error {
return err return err
} }
// SetKeepAlive sets keepalive
//
// Deprecated: use (*keepAliveConn) SetKeepAlive instead.
func (l *limitListenerConn) SetKeepAlive(doKeepAlive bool) error { func (l *limitListenerConn) SetKeepAlive(doKeepAlive bool) error {
tcpc, ok := l.Conn.(*net.TCPConn) tcpc, ok := l.Conn.(*net.TCPConn)
if !ok { if !ok {
@ -71,6 +74,9 @@ func (l *limitListenerConn) SetKeepAlive(doKeepAlive bool) error {
return tcpc.SetKeepAlive(doKeepAlive) return tcpc.SetKeepAlive(doKeepAlive)
} }
// SetKeepAlivePeriod sets keepalive period
//
// Deprecated: use (*keepAliveConn) SetKeepAlivePeriod instead.
func (l *limitListenerConn) SetKeepAlivePeriod(d time.Duration) error { func (l *limitListenerConn) SetKeepAlivePeriod(d time.Duration) error {
tcpc, ok := l.Conn.(*net.TCPConn) tcpc, ok := l.Conn.(*net.TCPConn)
if !ok { if !ok {

View File

@ -68,7 +68,7 @@ func newListener(addr, scheme string, opts ...ListenerOption) (net.Listener, err
fallthrough fallthrough
case lnOpts.IsTimeout(), lnOpts.IsSocketOpts(): case lnOpts.IsTimeout(), lnOpts.IsSocketOpts():
// timeout listener with socket options. // timeout listener with socket options.
ln, err := lnOpts.ListenConfig.Listen(context.TODO(), "tcp", addr) ln, err := newKeepAliveListener(&lnOpts.ListenConfig, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -78,7 +78,7 @@ func newListener(addr, scheme string, opts ...ListenerOption) (net.Listener, err
writeTimeout: lnOpts.writeTimeout, writeTimeout: lnOpts.writeTimeout,
} }
case lnOpts.IsTimeout(): case lnOpts.IsTimeout():
ln, err := net.Listen("tcp", addr) ln, err := newKeepAliveListener(nil, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -88,7 +88,7 @@ func newListener(addr, scheme string, opts ...ListenerOption) (net.Listener, err
writeTimeout: lnOpts.writeTimeout, writeTimeout: lnOpts.writeTimeout,
} }
default: default:
ln, err := net.Listen("tcp", addr) ln, err := newKeepAliveListener(nil, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -102,6 +102,19 @@ func newListener(addr, scheme string, opts ...ListenerOption) (net.Listener, err
return wrapTLS(scheme, lnOpts.tlsInfo, lnOpts.Listener) return wrapTLS(scheme, lnOpts.tlsInfo, lnOpts.Listener)
} }
func newKeepAliveListener(cfg *net.ListenConfig, addr string) (ln net.Listener, err error) {
if cfg != nil {
ln, err = cfg.Listen(context.TODO(), "tcp", addr)
} else {
ln, err = net.Listen("tcp", addr)
}
if err != nil {
return
}
return NewKeepAliveListener(ln, "tcp", nil)
}
func wrapTLS(scheme string, tlsinfo *TLSInfo, l net.Listener) (net.Listener, error) { func wrapTLS(scheme string, tlsinfo *TLSInfo, l net.Listener) (net.Listener, error) {
if scheme != "https" && scheme != "unixs" { if scheme != "https" && scheme != "unixs" {
return l, nil return l, nil

View File

@ -213,6 +213,15 @@ func TestNewListenerWithSocketOpts(t *testing.T) {
if !test.expectedErr && err != nil { if !test.expectedErr && err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if test.scheme == "http" {
lnOpts := newListenOpts(test.opts...)
if !lnOpts.IsSocketOpts() && !lnOpts.IsTimeout() {
if _, ok := ln.(*keepaliveListener); !ok {
t.Fatalf("ln: unexpected listener type: %T, wanted *keepaliveListener", ln)
}
}
}
}) })
} }
} }

View File

@ -650,12 +650,6 @@ func configureClientListeners(cfg *Config) (sctxs map[string]*serveCtx, err erro
sctx.l = transport.LimitListener(sctx.l, int(fdLimit-reservedInternalFDNum)) sctx.l = transport.LimitListener(sctx.l, int(fdLimit-reservedInternalFDNum))
} }
if network == "tcp" {
if sctx.l, err = transport.NewKeepAliveListener(sctx.l, network, nil); err != nil {
return nil, err
}
}
defer func(u url.URL) { defer func(u url.URL) {
if err == nil { if err == nil {
return return