From 2b19551314d85192de573db089228767d97970c9 Mon Sep 17 00:00:00 2001 From: Nick Tobey Date: Tue, 13 Jun 2023 16:40:57 -0700 Subject: [PATCH 1/4] Use SO_REUSEADDR and SO_REUSEPORT options when creating the sql server. --- server/listener.go | 2 +- server/net_listener_unix.go | 34 ++++++++++++++++++++++++++++++++++ server/net_listener_windows.go | 7 +++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 server/net_listener_unix.go create mode 100644 server/net_listener_windows.go diff --git a/server/listener.go b/server/listener.go index cee5fa3771..50aea3c62e 100644 --- a/server/listener.go +++ b/server/listener.go @@ -53,7 +53,7 @@ type Listener struct { // For unix socket connection, 'unixSocketPath' takes a path for the unix socket file. // If 'unixSocketPath' is empty, no need to create the second listener. func NewListener(protocol, address string, unixSocketPath string) (*Listener, error) { - netl, err := net.Listen(protocol, address) + netl, err := newNetListener(protocol, address) if err != nil { return nil, err } diff --git a/server/net_listener_unix.go b/server/net_listener_unix.go new file mode 100644 index 0000000000..d84cdfe41c --- /dev/null +++ b/server/net_listener_unix.go @@ -0,0 +1,34 @@ +//go:build !windows + +package server + +import ( + "context" + "golang.org/x/sys/unix" + "net" + "syscall" +) + +func newNetListener(protocol, address string) (net.Listener, error) { + lc := net.ListenConfig{ + Control: func(network, address string, c syscall.RawConn) error { + var socketErr error + err := c.Control(func(fd uintptr) { + err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + if err != nil { + socketErr = err + } + + err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + if err != nil { + socketErr = err + } + }) + if err != nil { + return err + } + return socketErr + }, + } + return lc.Listen(context.Background(), protocol, address) +} diff --git a/server/net_listener_windows.go b/server/net_listener_windows.go new file mode 100644 index 0000000000..71cc70b4fd --- /dev/null +++ b/server/net_listener_windows.go @@ -0,0 +1,7 @@ +package server + +import "net" + +func newNetListener(protocol, address string) (net.Listener, error) { + return net.Listen(protocol, address) +} From 8a5b9ac2842fe673322f52213c0635f7d4734163 Mon Sep 17 00:00:00 2001 From: Nick Tobey Date: Fri, 16 Jun 2023 14:28:35 -0700 Subject: [PATCH 2/4] Document new NetListener options. --- server/net_listener_unix.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/net_listener_unix.go b/server/net_listener_unix.go index d84cdfe41c..749b9f36db 100644 --- a/server/net_listener_unix.go +++ b/server/net_listener_unix.go @@ -9,6 +9,10 @@ import ( "syscall" ) +// Very rarely in our CI, the server fails to bind to the port with the error: "port already in use." +// This is odd because the server already confirms that the port is not in use before connecting. +// Using the SO_REUSEADDR and SO_REUSEPORT options prevents this spurious failure. +// This is safe to do because we have already checked that the func newNetListener(protocol, address string) (net.Listener, error) { lc := net.ListenConfig{ Control: func(network, address string, c syscall.RawConn) error { From 6a17104e583469f57e6d2c584559c67d76b9bc21 Mon Sep 17 00:00:00 2001 From: Nick Tobey Date: Fri, 16 Jun 2023 14:28:59 -0700 Subject: [PATCH 3/4] Check that port is not in use before binding port on server startup. --- server/server.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/server/server.go b/server/server.go index 5c4bd7c422..d3cf5b8975 100644 --- a/server/server.go +++ b/server/server.go @@ -16,6 +16,8 @@ package server import ( "errors" + "fmt" + "net" "time" "github.com/dolthub/vitess/go/mysql" @@ -97,6 +99,16 @@ func NewValidatingServer( return newServerFromHandler(cfg, e, sm, handler) } +func portInUse(hostPort string) bool { + timeout := time.Second + conn, _ := net.DialTimeout("tcp", hostPort, timeout) + if conn != nil { + defer conn.Close() + return true + } + return false +} + func newServerFromHandler(cfg Config, e *sqle.Engine, sm *SessionManager, handler mysql.Handler) (*Server, error) { if cfg.ConnReadTimeout < 0 { cfg.ConnReadTimeout = 0 @@ -109,6 +121,11 @@ func newServerFromHandler(cfg Config, e *sqle.Engine, sm *SessionManager, handle } var unixSocketInUse error + + if portInUse(cfg.Address) { + unixSocketInUse = fmt.Errorf("Port %s already in use.", cfg.Address) + } + l, err := NewListener(cfg.Protocol, cfg.Address, cfg.Socket) if err != nil { if errors.Is(err, UnixSocketInUseError) { From 7ab3c4968fe28c64758dbc9ca72d29ed5ae58301 Mon Sep 17 00:00:00 2001 From: nicktobey Date: Fri, 16 Jun 2023 21:31:33 +0000 Subject: [PATCH 4/4] [ga-format-pr] Run ./format_repo.sh to fix formatting --- server/net_listener_unix.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/net_listener_unix.go b/server/net_listener_unix.go index 749b9f36db..e35bf6688f 100644 --- a/server/net_listener_unix.go +++ b/server/net_listener_unix.go @@ -4,9 +4,10 @@ package server import ( "context" - "golang.org/x/sys/unix" "net" "syscall" + + "golang.org/x/sys/unix" ) // Very rarely in our CI, the server fails to bind to the port with the error: "port already in use."