Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions tailcat.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ type Server struct {

lb *locoBackend // non-nil once Start has been called

closeOnce sync.Once

// AllowProxy, if non-nil, reports whether
// a TCP or UDP proxy is allowed for that target.
AllowProxy func(netip.AddrPort) bool
Expand Down Expand Up @@ -519,7 +521,11 @@ func (s *Server) Close() error {
if s.lb == nil {
return nil // never started
}
return s.lb.Close()
var err error
s.closeOnce.Do(func() {
err = s.lb.Close()
})
return err
}

// DrainTCP waits until every TCP connection in the server's netstack
Expand Down Expand Up @@ -1411,6 +1417,8 @@ type Client struct {
key key.NodePrivate // the effective node identity; Key or generated
started bool

closeOnce sync.Once

upDone atomic.Bool // whether the server has meowed us at least once
}

Expand Down Expand Up @@ -1552,7 +1560,11 @@ func (c *Client) Close() error {
if c.lb == nil {
return nil // never used
}
return c.lb.Close()
var err error
c.closeOnce.Do(func() {
err = c.lb.Close()
})
return err
}

// PingResult is the result of a successful [Client.Ping] call.
Expand Down
34 changes: 34 additions & 0 deletions tailcat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,37 @@ func TestFetchDERPMapMemoryCache(t *testing.T) {
t.Errorf("fetches = %d; want 1", n)
}
}

// TestDoubleClose verifies that calling Close more than once on a
// [Server] or [Client] is harmless and does not panic or error.
func TestDoubleClose(t *testing.T) {
t.Parallel()

dm := integration.RunDERPAndSTUN(t, mkLogger(t, "derpstun"), "127.0.0.1")
reg := dm.Regions[1]
if reg == nil {
t.Fatal("no region 1 in derpmap")
}

s := &Server{Logf: mkLogger(t, "server"), Region: reg}
if err := s.Start(); err != nil {
t.Fatalf("server Start: %v", err)
}

c := &Client{Server: s.ConnBlob(), Logf: mkLogger(t, "client")}
PingForTest(t, s, c)

if err := c.Close(); err != nil {
t.Fatalf("first client Close: %v", err)
}
if err := c.Close(); err != nil {
t.Fatalf("second client Close: %v", err)
}

if err := s.Close(); err != nil {
t.Fatalf("first server Close: %v", err)
}
if err := s.Close(); err != nil {
t.Fatalf("second server Close: %v", err)
}
}