Go version

go version go1.24.1 linux/amd64

What did you do?

Using defer wg.Done() is dangerous, as the caller of wg.Wait() might get scheduled before the panic has a chance to complete.

https://go.dev/play/p/bd3XS0jWlxh

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        panic("test")
    }()
    wg.Wait()
    os.Exit(0)
}

Other scenarios can be that the caller of wg.Wait expects a resource/variable to be initialized and panicking when it isn't: https://go.dev/play/p/5tA0oEmAKSe, thus hiding the original panic/root problem.

What did you see happen?

The program can exit 0 (~3% of the time on my machine).

What did you expect to see?

People should ~never use defer wg.Done(), as this should never exit(0).

Comment From: gopherbot

Change https://go.dev/cl/689455 mentions this issue: sync: WaitGroup.Go: Don't call wg.Done() when panicking

Comment From: seankhliao

see https://github.com/golang/go/issues/63796#issuecomment-2825614606

We should never promise to panic.

Comment From: Jille

Your argument (which I disagree with, but let's discuss that in the CL) only applies to sync.WaitGroup.Do(). I wanted to talk about all (other) code doing defer wg.Done() in this issue.

Comment From: TapirLiu

Even if it is a problem, it is not the fault of wg.Done.

package main

import "sync"
import "os"

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer func() { select{} }()
        defer wg.Done()
        panic("test")
    }()
    wg.Wait()
    os.Exit(0)
}