mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
Fixes CLI-9 The `goleak` check in `cli` has been flaking on an idle HTTP connection. It turns out that's coming from `coder server` shutting down, not the OIDC links command. `API.Close` cancels the API context well before it closes the server tailnet, and since #14680 the embedded DERP region dialer returns `nil` once that context is done. Unfortunately, `nil` tells `derphttp` to dial the region over the network instead. So the server dials its own access URL (`example.com` in tests), gets a 404, falls back to WebSocket, and our tailscale fork leaves that failed WebSocket connection open forever. This has only been flaking since #29462, which has the server tailnet fetch the DERP map straight away rather than after the first 5 second tick. Before that, most server tests had finished before the tailnet ever connected to DERP, so there was nothing to reconnect on shutdown. To fix, the embedded region now gets an already closed conn once shutdown starts, so the DERP handshake fails and magicsock just backs off until the tailnet closes. I reproduced the leak by pointing a server tailnet's embedded region at `example.com` and cancelling the context; it fails `goleak` without this change and passes with it.
54 lines
1.5 KiB
Go
54 lines
1.5 KiB
Go
package coderd
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"tailscale.com/derp"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/key"
|
|
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/quartz"
|
|
)
|
|
|
|
func TestEmbeddedDERPRegionDialer(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
srv := derp.NewServer(key.NewNode(), func(format string, args ...any) { t.Logf(format, args...) })
|
|
t.Cleanup(func() { _ = srv.Close() })
|
|
ctx, cancel := context.WithCancel(testutil.Context(t, testutil.WaitShort))
|
|
dial := embeddedDERPRegionDialer(ctx, testutil.Logger(t), srv)
|
|
|
|
require.Nil(t, dial(ctx, &tailcfg.DERPRegion{EmbeddedRelay: false}))
|
|
|
|
cancel()
|
|
conn := dial(ctx, &tailcfg.DERPRegion{EmbeddedRelay: true})
|
|
require.NotNil(t, conn, "an embedded region must never fall back to a network dial")
|
|
_, err := conn.Read(make([]byte, 1))
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestPollingDERPClient_FirstRecvDoesNotWaitForTick(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
derpMap := &tailcfg.DERPMap{Regions: map[int]*tailcfg.DERPRegion{1: {RegionID: 1}}}
|
|
// The mock clock never advances, so a poll loop that waits for the
|
|
// first tick would block forever.
|
|
client := newPollingDERPClient(func() *tailcfg.DERPMap { return derpMap }, testutil.Logger(t), quartz.NewMock(t))
|
|
defer client.Close()
|
|
|
|
got := make(chan *tailcfg.DERPMap, 1)
|
|
go func() {
|
|
dm, err := client.Recv()
|
|
if err != nil {
|
|
t.Error("recv derp map:", err)
|
|
return
|
|
}
|
|
got <- dm
|
|
}()
|
|
require.Equal(t, derpMap, testutil.RequireReceive(ctx, t, got))
|
|
}
|