Goroutines don’t panic if channel they write to is gone


Couple of months ago I wrote an article about error handling in concurrent Go programs. One concern I had about it was if other goroutines would panic if some goroutine produces an error value which causes return from function when the channel is iterated with range construct.

So I made another program that mimics scenario. It spawns couple of goroutines, each taking a second more to complete than previous (for the sake of test being easier to observe). When done, they write to channel. 5th goroutine returns an error (after 5 seconds) via channel which is iterated using range. In case of an error, it’s returned from function.

package main

import (
    "errors"
    "sync"
    "time"
)

type data struct {
    err  error
    data int
}

func someWork(dataChan chan<- *data, collection []int) {
    var wg sync.WaitGroup
    l := len(collection)
    wg.Add(l)
    for i, e := range collection {
        go func(i, e int) {
            time.Sleep(time.Duration(i) * time.Second)
            var err error
            if i == 5 {
                err = errors.New("error")
            }
            dataChan <- &data{err, e}
            defer wg.Done()
        }(i, e)
    }
    wg.Wait()
    close(dataChan)
}

func someFunc() {
    collection := []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
    dataChan := make(chan *data)

    go someWork(dataChan, collection)

    for c := range dataChan {
        if c.err != nil {
            return
        }
    }

    time.Sleep(2 * time.Second)
}

func main() {
    someFunc()
    time.Sleep(20 * time.Second)
}

It surprised me a little other goroutines which still aren't finished don't cause panic because channel is gone even. For more realistic test I've also added some work after someFunc is finished.

Don't know why, it sure has to do with the way garbage collection works in Go (would be a great read if there's detailed explanation of it). I'm glad though I don't have to deal with goroutines panicking myself.

One thing I didn't do in example is cancelling the work running goroutines are still doing so they stop wasting additional memory. One idiomatic way to do that is with the use of context package from standard library, which provides cancellation and timeout mechanisms.