-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/vet: add check for sync.WaitGroup abuse #18022
Comments
I don't like the idea. It's IMHO perhaps a task for |
I think this has been proposed before (probably on the mailing lists)
and rejected.
|
A func main() {
var wg sync.WaitGroup
defer wg.Wait()
go func() {
wg.Add(1)
defer wg.Done()
}()
} and In terms of vet's requirements:
@dominikh , |
As far as the proposal goes, I don't like how it limits the func signature to |
@dsnet The check in staticcheck has no (known) false positives. It shouldn't have a significant number of false negatives, either. The implementation is a simple pattern-based check, detecting I'm -1 on the proposed |
@dominikh I don't see any extra level of nesting here, though (assuming any func signature is allowed). To be nitpicky, another thing that stands out from the proposal is how |
@mvdan The extra level of nesting would come from a predicted usage that looks something like this:
as opposed to
Admittedly the same level of indentation, but syntactically it's one extra level of nesting. |
Ah yes, I was thinking indentation there. |
The problematic case is that
|
@cznic if you mean without the extra |
@mvdan Do you mean by allowing something like the following?
IMHO that's way too much |
panic is recovered? |
@dominikh true; I was simply pointing at the issue without contemplating a solution :) |
The API change here has the problems identified above with argument evaluation. Also, in general the libraries do not typically phrase functionality in terms of callbacks. If we're going to start using callbacks broadly, that should be a separate decision (and not one to make today). For both these reasons, it seems like .Go is not a clear win. It would be nice to have a vet check that we trust (no false positives). Perhaps it is enough to look for two statements
back to back and reject that always. Thoughts about how to make vet catch this reliably? |
I agree that |
It sounds like we are deciding to make go vet check this and not add new API here. Any arguments against that? |
SGTM |
I've added this proposal to the proposal process bin, but it's blocked on someone figuring out how to implement a useful check. Is anyone interested in doing that? |
Staticcheck has a fairly trivial check: for a GoStmt of a FuncLit, if the first statement in the FuncLit is a call to The check could be trivially hardened by
Edit: which is pretty much what you have suggested in #18022 (comment) |
Thanks for the info @dominikh. |
ok, the improved linter will still false positive on this, until we use inspect() to recursively look into all sibling nodes (may not be worthy the machine cost)
I think it is true positive, given this pattern, the goroutine may not start yet, when the shutdownWaitGroup.Wait() is reached, leaving that goroutine leaky. Did I miss some subtlety here? A relevant question, what is the next step then? :) |
To be clear, I meant that the code here is wrong in more ways than I can count; your linter algorithm is absolutely right to flag it.
A WaitGroup is a counting semaphore. The most common use is to count unfinished goroutines, but in this case I think it is counting various "holds" that prevent the program from completing its shutdown (like electricians each padlocking the main breaker so that they know they are safe while they are working). That is, the goroutines are already running, and any of them may temporarily increment the counter, far from where they are created. Presumably at the end the main goroutine will wait for the counter to fall to zero. The clue to recognizing this pattern is that there is no nearby
We should probably reclassify the new positives in light of these observations. Ideally the false positive rate should be 5% or less. |
Since it's a semaphore that is incorrect to use to (obviously this is worth verifying, but it seems extremely unlikely to be guaranteed-safe to me) |
I agree with @Groxx on this, If the goroutines want to declare they are live, why do not they do so at the beginning or even better before goroutine starts. I am not sure why they do so in the middle. By flagging this, we suggest developers to move add to an earlier point. This can only improve quality, at least will not degrade quality. I may miss some points, please let me know. |
That's definitely a bug. (As other people already deduced). The easiest way to see this is to imagine a |
Thanks, you have all convinced me that this is indeed a bug (and thus a true positive of the new algorithm). @lpxz, would you like to send me a CL to incorporate the new algorithm into the waitgroup analyzer (currently in gopls, but eventually to be promoted to vet too)? The CL should included a test for each distinct case you encountered (and false positives too, since they document the limitations). Thanks for improving this analyzer! |
@adonovan great, happy to make the contributions! Will create a CL and add tests. Peng |
Based on the discussion above, this proposal seems like a likely accept. The proposal is to add a vet check for sync.WaitGroup misuse where var wg sync.WaitGroup
defer wg.Wait()
go func() {
wg.Add(1)
defer wg.Done()
}() While complex heuristics are certainly possible here, for now we're going to limit this to checking for |
I am in progress of creating the cl, which has no false positive, fewer false negatives and is also simple. Feel free to let me know if the final decision is to move on with the first line approach which is a fine approach). |
This proposal is about adding the basic functionality into vet, which can likely proceed for go1.25. Independent of that, you should feel free to improve the algorithm; any improvements can be used immediately by gopls, and eventually by vet (without needing a separate proposal process), assuming they meet the usual criteria for precision and frequency. |
No change in consensus, so accepted. 🎉 The proposal is to add a vet check for sync.WaitGroup misuse where var wg sync.WaitGroup
defer wg.Wait()
go func() {
wg.Add(1)
defer wg.Done()
}() While complex heuristics are certainly possible here, for now we're going to limit this to checking for |
Change https://go.dev/cl/661518 mentions this issue: |
Change https://go.dev/cl/661519 mentions this issue: |
…7356d5cc5 Also, sys@v0.31.1. Updates #18022 Change-Id: I15a6d1979cc1e71d3065bc50f09dc8d3f6c6cdc0 Reviewed-on: https://go-review.googlesource.com/c/go/+/661518 Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org> Commit-Queue: Alan Donovan <adonovan@google.com>
The API for WaitGroup is very easy for people to misuse. The documentation for
Add
says:However, it is very common to see this incorrect pattern:
This usage is fundamentally racy and probably does not do what the user wanted. Worse yet, is that it is not detectable by the race detector.
Since the above pattern is common, I propose that we add a method
Go
that essentially does theAdd(1)
and subsequent call toDone
in the correct way. That is:The text was updated successfully, but these errors were encountered: