goroutineownership
Checks that explicit goroutines have a recognizable join handle or lifecycle owner.
What it detects
Give spawned goroutines a recognizable lifecycle owner. Local producer
goroutines must also be able to stop sending when their receiver leaves; use a
cancellation-aware select, drain the channel, or provide enough proven buffer
capacity for every send.
Checks
| Check | What it detects |
|---|---|
unjoined |
Reports goroutines with a recognizable join or lifecycle mechanism that is not honored on every return path. |
detached* |
Reports goroutines without a recognizable join handle or lifecycle owner. |
abandoned-send |
Reports producer goroutines that can block after their receiver stops waiting. |
* Opt-in; requires explicit selection.
Why this is flagged
A goroutine without a clear owner may keep running after its caller is done or block forever while sending a result nobody will receive. These leaks consume memory and can eventually make shutdowns or the whole program hang.
Further reading: Go concurrency patterns: Pipelines and cancellation, Go concurrency patterns: Context.
How to fix it
Give the goroutine a clear stopping rule: cancel it with a context, join it before returning, or hand it to a longer-lived owner. Make sends cancellation aware so the goroutine can stop if its receiver leaves.
Examples
Flagged code
func refresh() { // gohawk: goroutine is not joined on every return path go updateCache()}Accepted code
func refreshSafely() { var group sync.WaitGroup group.Add(1) go func() { defer group.Done() updateCache() }() group.Wait()}Options
| Knob | Default | Effect |
|---|---|---|
mode |
context |
Ownership policy: context, lifecycle, or join. |
By default, a context is enough to own a worker. Use
-goroutineownership.mode=lifecycle to require a lifecycle owner, or
-goroutineownership.mode=join to require an explicit join.