ceth
ceth

Reputation: 45295

Why this goroutine leaks?

I am reading 'Concurrency in Go' and have found this example of goroutine leaks:

func main() {

    var wg sync.WaitGroup

    doWork := func(strings <-chan string) <-chan interface{} {
        completed := make(chan interface{})
        go func() {
            defer fmt.Println("doWork exited.")
            defer close(completed)
            defer wg.Done()
            fmt.Println("a")
            for s := range strings {
                fmt.Println(s)
            }
            fmt.Println("b")
        }()
        return completed
    }

    wg.Add(1)
    doWork(nil)
    fmt.Println("Waiting")
    wg.Wait()

    fmt.Println("Done.")
}

The strings channel will never gets any strings written onto it, and the goroutine containing doWork will remain in memory for the life time of process.

I don't understand - why ?

How I understand this code:

But I see it works like that. Why ?

Upvotes: 2

Views: 250

Answers (1)

helmbert
helmbert

Reputation: 38004

As strings is nil range-loop just skipped.

This assumption is not correct. In Go, reading from a nil channel will always block. This is defined in the language specification (thanks to @peterSO for digging out the link):

Receiving from a nil channel blocks forever.

There's also a post on the Go Design Patterns blog that further elaborates on this behaviour and highlights some cases in which it is useful.

Anyhow, this behaviour can easily be reproduced with a minimal example (playground):

func main() {
    var s chan string
    <- s
}

This program will never finish (in the playground, it will crash with all goroutines are asleep - deadlock).

Because reading from a nil channel (in the case of your example, strings) will block (forever, since nothing can be written into a nil channel), the doWork goroutine will never complete, and thus, leak.

Upvotes: 4

Related Questions