mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
Long story short: Once we create a server and start it, we can't start it again. We can't close it and restart it, and we can't just start it without closing it. Unfortunately, if the server's config needs to change, we have a Problem here. This ultimately means that the retry logic for GetListeners can't actually retry successfully; if we fail on the first attempt, we necessarily fail on any later attempts also, and if we try to fix that, we get panics. But! We don't actually NEED to retry. We just need to ensure that we can open a :0 port, extract the actual port number, and use that in places where the port number mattered, without having to rebind it. The only actual place we needed to rebind things was opening gRPC servers, so we introduce a gRPC Listener that can be used instead of trying to bind to a specified port. In a bunch of other cases where we had similar logic to try to allocate and then use a port, we can switch to just using a provided listener. For instance, net/http has `Serve(net.Listener, handler)`, not just ListenAndServe(addr, handler). This should eliminate the weird CI failures from eaddrinuse. NOT fixed: server/cluster_test.go/TestClusterResize_AddNode isn't working right now. The new node isn't actually being added to the existing cluster. I attempted this but was outsmarted by it, and I think fixing the rest of this is worth it as a separate thing.
130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
// Copyright 2020 Pilosa Corp.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pgtest
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
|
|
"github.com/pilosa/pilosa/v2/pg"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
// ShutdownFunc is a function to use to shut down a test fixture.
|
|
// This function will send a shutdown signal and then wait for completion.
|
|
type ShutdownFunc func() error
|
|
|
|
// Finish invokes the shutdown function and fails the test if an error occurs.
|
|
func (f ShutdownFunc) Finish(tb testing.TB, name string) {
|
|
err := f()
|
|
if err != nil {
|
|
tb.Errorf("failed to shut down %s: %v", name, err)
|
|
}
|
|
}
|
|
|
|
// ServeListener serves postgres wire protocol on a listener.
|
|
func ServeListener(listener net.Listener, server *pg.Server) (net.Addr, ShutdownFunc, error) {
|
|
laddr := listener.Addr()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
var eg errgroup.Group
|
|
eg.Go(func() error { return server.Serve(ctx, listener) })
|
|
|
|
return laddr,
|
|
func() error {
|
|
cancel()
|
|
return eg.Wait()
|
|
},
|
|
nil
|
|
}
|
|
|
|
// ServeTCP creates a TCP listener and serves postgres wire protocol on it.
|
|
func ServeTCP(addr string, server *pg.Server) (net.Addr, ShutdownFunc, error) {
|
|
listener, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "listening on TCP")
|
|
}
|
|
return ServeListener(listener, server)
|
|
}
|
|
|
|
// ServeTLSListener sets up TLS on the server and invokes ServeListener.
|
|
func ServeTLSListener(listener net.Listener, server *pg.Server) (net.Addr, ShutdownFunc, error) {
|
|
err := SetupTLS(server)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "server TLS setup failed")
|
|
}
|
|
|
|
var tries int = 5
|
|
var netAddr net.Addr
|
|
var shutdown ShutdownFunc
|
|
|
|
for i := 0; i < tries; i++ {
|
|
if i > 0 {
|
|
fmt.Printf("--- try serving TLS again: %d\n", i)
|
|
}
|
|
if netAddr, shutdown, err = ServeListener(listener, server); err == nil {
|
|
break
|
|
}
|
|
}
|
|
return netAddr, shutdown, err
|
|
}
|
|
|
|
// ServeTLS sets up TLS on the server and invokes ServeTCP.
|
|
func ServeTLS(addr string, server *pg.Server) (net.Addr, ShutdownFunc, error) {
|
|
err := SetupTLS(server)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "server TLS setup failed")
|
|
}
|
|
|
|
var tries int = 5
|
|
var netAddr net.Addr
|
|
var shutdown ShutdownFunc
|
|
|
|
for i := 0; i < tries; i++ {
|
|
if i > 0 {
|
|
fmt.Printf("--- try serving TLS again: %d\n", i)
|
|
}
|
|
if netAddr, shutdown, err = ServeTCP(addr, server); err == nil {
|
|
break
|
|
}
|
|
}
|
|
return netAddr, shutdown, err
|
|
}
|
|
|
|
// ConnectFunc is a function to connect to a server.
|
|
type ConnectFunc func() (net.Conn, error)
|
|
|
|
// ServeMem serves postgres on in-memory connections.
|
|
// TLS does not work here, as it relies on the OS to buffer and discard data.
|
|
func ServeMem(server *pg.Server) (ConnectFunc, ShutdownFunc, error) {
|
|
listener := &inMemoryListener{
|
|
ch: make(chan net.Conn),
|
|
closed: make(chan struct{}),
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
var eg errgroup.Group
|
|
eg.Go(func() error { return server.Serve(ctx, listener) })
|
|
|
|
return listener.Dial,
|
|
func() error {
|
|
cancel()
|
|
return eg.Wait()
|
|
},
|
|
nil
|
|
}
|