From 02d8bcac68ccf01f992c89bd89c8d1c7b9670945 Mon Sep 17 00:00:00 2001 From: Caleb Jasik Date: Tue, 27 Jan 2026 23:44:43 -0600 Subject: [PATCH] Remove lighthouse goroutine leaks in lighthouse_test.go (#1589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using + Claude, I was able to run nebula's unit tests and e2e tests with the leak detector enabled. Added a TestMain that queries pprof to see if there are any reported goroutine leaks. I'd love to get some form of this in CI whenever go 1.26 comes out, though I'd also like to prove this is properly useful past the just five detections it got here.
TestMain ```go package nebula import ( "fmt" "os" "runtime/pprof" "strings" "testing" ) // TestMain runs after all tests and checks for goroutine leaks func TestMain(m *testing.M) { // Run all tests exitCode := m.Run() // Check for goroutine leaks after all tests complete prof := pprof.Lookup("goroutineleak") if prof != nil { var sb strings.Builder if err := prof.WriteTo(&sb, 2); err != nil { fmt.Fprintf(os.Stderr, "Failed to write goroutineleak profile: %v\n", err) os.Exit(1) } content := sb.String() leakedCount := strings.Count(content, "(leaked)") if leakedCount > 0 { fmt.Fprintf(os.Stderr, "\n=== GOROUTINE LEAK DETECTED ===\n") fmt.Fprintf(os.Stderr, "Found %d leaked goroutine(s) in package nebula\n\n", leakedCount) goros := strings.Split(content, "\n\n") for _, goro := range goros { if strings.Contains(goro, "(leaked)") { fmt.Fprintln(os.Stderr, goro) fmt.Fprintln(os.Stderr) } } os.Exit(1) } else { fmt.Println("✓ No goroutine leaks detected in package nebula") } } os.Exit(exitCode) } ```
Also had to install go1.26rc2 and update the makefile to use that go binary + set ex: ```makefile test-goroutineleak: GOEXPERIMENT=goroutineleakprofile go1.26rc2 test -v ./... ``` --- lighthouse_test.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lighthouse_test.go b/lighthouse_test.go index fea1d1ed..c57c44ec 100644 --- a/lighthouse_test.go +++ b/lighthouse_test.go @@ -1,7 +1,6 @@ package nebula import ( - "context" "encoding/binary" "fmt" "net/netip" @@ -42,14 +41,14 @@ func Test_lhStaticMapping(t *testing.T) { c := config.NewC(l) c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1}} c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}} - _, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil) + _, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) lh2 := "10.128.0.3" c = config.NewC(l) c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1, lh2}} c.Settings["static_host_map"] = map[string]any{lh1: []any{"100.1.1.1:4242"}} - _, err = NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil) + _, err = NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry") } @@ -71,7 +70,7 @@ func TestReloadLighthouseInterval(t *testing.T) { } c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}} - lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil) + lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) lh.ifce = &mockEncWriter{} @@ -99,7 +98,7 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) { } c := config.NewC(l) - lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil) + lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil) require.NoError(b, err) hAddr := netip.MustParseAddrPort("4.5.6.7:12345") @@ -202,7 +201,7 @@ func TestLighthouse_Memory(t *testing.T) { myVpnNetworks: []netip.Prefix{myVpnNet}, myVpnNetworksTable: nt, } - lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil) + lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) lh.ifce = &mockEncWriter{} require.NoError(t, err) lhh := lh.NewRequestHandler() @@ -288,7 +287,7 @@ func TestLighthouse_reload(t *testing.T) { myVpnNetworksTable: nt, } - lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil) + lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) nc := map[string]any{ @@ -523,7 +522,7 @@ func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) { myVpnNetworks: []netip.Prefix{myVpnNet}, myVpnNetworksTable: nt, } - lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil) + lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) lh.ifce = &mockEncWriter{} @@ -589,7 +588,7 @@ func TestLighthouse_DeletesWork(t *testing.T) { myVpnNetworks: []netip.Prefix{myVpnNet}, myVpnNetworksTable: nt, } - lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil) + lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil) require.NoError(t, err) lh.ifce = &mockEncWriter{}