Search code examples
gogoroutinego-testing

Why is WaitGroup.Wait() hanging when using it with go test?


Here's a simple example of what I mean

package main

import (
    "sync"
    "testing"
    "time"
)

func TestWaitGroup(t *testing.T) {
    var wg sync.WaitGroup
    quitSig := make(chan struct{})
    go func(wg sync.WaitGroup, quitChan, chan struct{}) {
        defer func() {
            t.Log("Done...")
            wg.Done()
            t.Log("Done!")
        }()
        t.Log("waiting for quit channel signal...")
        <-quitChan
        t.Log("signal received")
    }(wg, quitSig)
    time.Sleep(5*time.Second)
    t.Log("Done sleeping")
    close(quitSig)
    t.Log("closed quit signal channel")
    wg.Wait()
    t.Log("goroutine shutdown")
}

When I run this, I get the following

=== RUN   TestWaitGroup
    main.go:18: waiting for quit channel signal...
    main.go:23: Done sleeping
    main.go:25: closed quit signal channel
    main.go:20: signal received
    main.go:14: Done...
    main.go:16: Done!
    

Where it just hangs until it timesout. If you just do defer wg.Done() the same behaviour is observed. I'm running go1.18. Is this a bug or am I using not using WaitGroups properly in this context?


Solution

  • Two issues:

    • don't copy sync.WaitGroup: from the docs:

      • A WaitGroup must not be copied after first use.
    • you need a wg.Add(1) before launching your work - to pair with the wg.Done()


    wg.Add(1) // <- add this
    
    go func (wg *sync.WaitGroup ...) { // <- pointer
    }(&wg, quitSig) // <- pointer to avoid WaitGroup copy
    

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