From 3867774702c31c221caf797d97f715d26ae8067e Mon Sep 17 00:00:00 2001 From: Periyasamy Palanisamy Date: Thu, 9 Nov 2023 12:28:09 +0530 Subject: [PATCH] Set user timeout for tcp connection This commit sets TCP_USER_TIMEOUT socket option for tcp connection so that channel write doesn't block indefinitely on network disconnect. Signed-off-by: Periyasamy Palanisamy --- client/client.go | 21 +++++++++++++++++++-- client/options.go | 1 + internal/syscall_linux.go | 31 +++++++++++++++++++++++++++++++ internal/syscall_nonlinux.go | 14 ++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 internal/syscall_linux.go create mode 100644 internal/syscall_nonlinux.go diff --git a/client/client.go b/client/client.go index 53f368fc..5e92b8ff 100644 --- a/client/client.go +++ b/client/client.go @@ -21,6 +21,7 @@ import ( "github.com/go-logr/logr" "github.com/go-logr/stdr" "github.com/ovn-org/libovsdb/cache" + syscall "github.com/ovn-org/libovsdb/internal" "github.com/ovn-org/libovsdb/mapper" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" @@ -352,7 +353,10 @@ func (o *ovsdbClient) tryEndpoint(ctx context.Context, u *url.URL) (string, erro return "", fmt.Errorf("failed to open connection: %w", err) } - o.createRPC2Client(c) + err = o.createRPC2Client(c) + if err != nil { + return "", err + } serverDBNames, err := o.listDbs(ctx) if err != nil { @@ -423,12 +427,24 @@ func (o *ovsdbClient) tryEndpoint(ctx context.Context, u *url.URL) (string, erro // createRPC2Client creates an rpcClient using the provided connection // It is also responsible for setting up go routines for client-side event handling // Should only be called when the mutex is held -func (o *ovsdbClient) createRPC2Client(conn net.Conn) { +func (o *ovsdbClient) createRPC2Client(conn net.Conn) error { o.stopCh = make(chan struct{}) if o.options.inactivityTimeout > 0 { o.trafficSeen = make(chan struct{}) } o.conn = conn + // set TCP_USER_TIMEOUT socket option for connection so that + // channel write doesn't block indefinitely on network disconnect. + var userTimeout time.Duration + if o.options.timeout > 0 { + userTimeout = o.options.timeout * 3 + } else { + userTimeout = defaultTimeout + } + err := syscall.SetTCPUserTimeout(conn, userTimeout) + if err != nil { + return err + } o.rpcClient = rpc2.NewClientWithCodec(jsonrpc.NewJSONCodec(conn)) o.rpcClient.SetBlocking(true) o.rpcClient.Handle("echo", func(_ *rpc2.Client, args []interface{}, reply *[]interface{}) error { @@ -444,6 +460,7 @@ func (o *ovsdbClient) createRPC2Client(conn net.Conn) { return o.update3(args, reply) }) go o.rpcClient.Run() + return nil } // isEndpointLeader returns true if the currently connected endpoint is leader, diff --git a/client/options.go b/client/options.go index 6f9b7b17..66a60b5f 100644 --- a/client/options.go +++ b/client/options.go @@ -15,6 +15,7 @@ const ( defaultTCPEndpoint = "tcp:127.0.0.1:6640" defaultSSLEndpoint = "ssl:127.0.0.1:6640" defaultUnixEndpoint = "unix:/var/run/openvswitch/ovsdb.sock" + defaultTimeout = 60 * time.Second ) type options struct { diff --git a/internal/syscall_linux.go b/internal/syscall_linux.go new file mode 100644 index 00000000..5138f22d --- /dev/null +++ b/internal/syscall_linux.go @@ -0,0 +1,31 @@ +package internal + +import ( + "fmt" + "net" + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +// SetTCPUserTimeout sets the TCP user timeout on a connection's socket +func SetTCPUserTimeout(conn net.Conn, timeout time.Duration) error { + tcpconn, ok := conn.(*net.TCPConn) + if !ok { + // not a TCP connection. exit early + return nil + } + rawConn, err := tcpconn.SyscallConn() + if err != nil { + return fmt.Errorf("error getting raw connection: %v", err) + } + err = rawConn.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(timeout/time.Millisecond)) + }) + if err != nil { + return fmt.Errorf("error setting option on socket: %v", err) + } + + return nil +} diff --git a/internal/syscall_nonlinux.go b/internal/syscall_nonlinux.go new file mode 100644 index 00000000..6e6a26d6 --- /dev/null +++ b/internal/syscall_nonlinux.go @@ -0,0 +1,14 @@ +//go:build !linux +// +build !linux + +package internal + +import ( + "net" + "time" +) + +// SetTCPUserTimeout is a no-op function under non-linux environments. +func SetTCPUserTimeout(conn net.Conn, timeout time.Duration) error { + return nil +}