etcd: increment port for embedded etcd server

Instead of letting the OS pick a random port for us in a way that wasn't
concurrency safe, we use an atomically incremented port to avoid
collisions.
This commit is contained in:
Oliver Gugger 2020-12-08 22:50:03 +01:00
parent c95c423703
commit c9ab713f59
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
"sync/atomic"
"time" "time"
"github.com/coreos/etcd/embed" "github.com/coreos/etcd/embed"
@ -15,23 +16,40 @@ import (
const ( const (
// readyTimeout is the time until the embedded etcd instance should start. // readyTimeout is the time until the embedded etcd instance should start.
readyTimeout = 10 * time.Second readyTimeout = 10 * time.Second
// defaultEtcdPort is the start of the range for listening ports of
// embedded etcd servers. Ports are monotonically increasing starting
// from this number and are determined by the results of getFreePort().
defaultEtcdPort = 2379
) )
// getFreePort returns a random open TCP port. var (
// lastPort is the last port determined to be free for use by a new
// embedded etcd server. It should be used atomically.
lastPort uint32 = defaultEtcdPort
)
// getFreePort returns the first port that is available for listening by a new
// embedded etcd server. It panics if no port is found and the maximum available
// TCP port is reached.
func getFreePort() int { func getFreePort() int {
ln, err := net.Listen("tcp", "[::]:0") port := atomic.AddUint32(&lastPort, 1)
if err != nil { for port < 65535 {
panic(err) // If there are no errors while attempting to listen on this
// port, close the socket and return it as available.
addr := fmt.Sprintf("127.0.0.1:%d", port)
l, err := net.Listen("tcp4", addr)
if err == nil {
err := l.Close()
if err == nil {
return int(port)
}
}
port = atomic.AddUint32(&lastPort, 1)
} }
port := ln.Addr().(*net.TCPAddr).Port // No ports available? Must be a mistake.
panic("no ports available for listening")
err = ln.Close()
if err != nil {
panic(err)
}
return port
} }
// NewEmbeddedEtcdInstance creates an embedded etcd instance for testing, // NewEmbeddedEtcdInstance creates an embedded etcd instance for testing,