I am trying to repro an issue and came to a minimum use case with the following code. If I close all the channels (bypassing the i == 0 test), things are working as expected. Wg state decrements and done is triggered, main exits fine. When I skip closing one of these channel (on purpose), I expect the main routine to wait while the waitgroup semaphore will block indefinitely in this case. Instead, I am getting an error: "fatal error: all goroutines are asleep - deadlock!". Why is that? I must have missed something fundamental or this the runtime being overzealous?
package main
import (
"fmt"
"sync"
)
const N int = 4
func main() {
done := make(chan struct{})
defer close(done)
fmt.Println("Beginning...")
chans := make([]chan int, N)
var wg sync.WaitGroup
for i := 0; i < N; i++ {
wg.Add(1)
chans[i] = make(chan int)
go func(i int) { // p0
defer wg.Done()
for m := range chans[i] {
fmt.Println("Received ", m)
}
fmt.Println("Ending p", i)
}(i)
}
go func() {
wg.Wait()
done <- struct{}{} // signal main that we are done
}()
for i := 0; i < N; i++ {
fmt.Println("Closing c", i)
if i != 0 { // Skip #0 so wg doesn't reach '0'
close(chans[i])
}
}
<-done // wait to receive signal from anonymous join function
fmt.Println("Ending.")
}
UPDATE: I edited the code to avoid the race condition. Still getting this error.
The if i != 0
is there because it's intentional. I want the wg.Wait to block forever (with its semaphore never reaching 0.) Why can't I do that? It seems the same as if I were using <-done
without a matching done <- struct{}{}
somewhere else. Would the compiler complain too in that case?