goroutineownership
Checks that proven goroutine completion obligations are honored.
What it detects
Reports goroutines that signal when they finish, by closing or sending on a
channel or calling Done on a sync.WaitGroup, when the launching function
does not wait for that signal on every return path.
Waiting counts in a helper, in a select arm, or in a counted loop that
receives once from each worker. Work launched without such a signal is not
reported, and neither is a worker launched once by main.main, since program
exit stops it.
The full list of precision boundaries, with the fixtures that pin each one, is in the goroutineownership design notes.
Checks
| Check | Tier | Kind | What it detects |
|---|---|---|---|
unjoined |
core | hazard | Reports goroutines with a recognizable join or lifecycle mechanism that is not honored on every return path. |
Why this is flagged
A goroutine with an abandoned completion obligation can keep running after its caller returns, or block forever trying to send a result nobody will receive. These leaks hold memory and other resources, and they can make shutdown nondeterministic or hang the program outright.
Further reading: Go concurrency patterns: Pipelines and cancellation, Go concurrency patterns: Context.
How to fix it
Join the goroutine, transfer its lifecycle explicitly, or connect it to a bounded owner.
Examples
Flagged code
func refresh() { var group sync.WaitGroup group.Add(1) // gohawk: goroutine is not joined on every return path go func() { defer group.Done() updateCache() }()}Accepted code
func refreshSafely() { var group sync.WaitGroup group.Add(1) go func() { defer group.Done() updateCache() }() group.Wait()}