Skip to content

tailcat: Server.Start and Client.initLocked leak resources on partial startup failures #18

Description

@awdemos

Problem

Server.Start and Client.initLocked allocate several resources (netMon, the WireGuard engine, and the gVisor netstack) before assigning s.lb / c.lb. If any intermediate step fails, Server.Close / Client.Close see a nil backend and return early, so the already-created resources leak. In Client.ensureStarted, a failing lb.Start() also leaves a broken backend behind instead of retrying cleanly on the next call.

This is the underlying issue that PR #11 fixes.

Reproduction

I added a small test hook plus TestServerStartCleansUpOnFailure to simulate lb.Start() failing after Server.Start has already assigned s.lb. On main (commit c04c5afee) the test fails because s.lb is leaked:

func TestServerStartCleansUpOnFailure(t *testing.T) {
    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")
    }

    SetStartBackendHookForTest(func(*locoBackend) error {
        return errors.New("injected backend start failure")
    })
    defer SetStartBackendHookForTest(nil)

    s := &Server{Logf: logger.Discard, Region: reg}
    if err := s.Start(); err == nil {
        t.Fatal("expected Server.Start to fail")
    }

    if s.lb != nil {
        t.Fatal("Server.Start leaked a locoBackend after failure")
    }
}

Test run on main:

=== RUN   TestServerStartCleansUpOnFailure
    tailcat_test.go:468: Server.Start leaked a locoBackend after failure
--- FAIL: TestServerStartCleansUpOnFailure (0.01s)

Verification that PR #11 fixes it

Checking out PR #11 (commit 9d20738db) and running the same test:

=== RUN   TestServerStartCleansUpOnFailure
--- PASS: TestServerStartCleansUpOnFailure (0.01s)

Suggested fix

Apply the cleanup from PR #11:

  • Use named return values in Server.Start / Client.initLocked.
  • Add deferred cleanup that closes netMon, the engine, and netstack if startup returns an error.
  • Reset s.lb = nil in Server.Start on failure.
  • In Client.ensureStarted, close and clear c.lb when lb.Start() fails so the next call retries from a clean state.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions