Avash's Portfolio

Preventing Goroutine Leaks- Best Practices and Tips for Go Developers


Preventing Goroutine Leaks- Best Practices and Tips for Go Developers

Before starting with the design pattern, here are some prerequisites.

Let’s brush up on a few concepts before discussing the design pattern. (If you know all of these feel free to skip to the design pattern)

Goroutines

To put it simply, a goroutine is a part of code that run concurrently alongside other code. We won’t go into much theory (we can leave that for some other blog).

To declare a goroutine you just have to use the go prefix while invoking a function.

Here’s a simple example:

func main(){

go func(){
    for i:= 0 ;i<5;i++{
        fmt.Print(i + " ")
    }
}()

go func(){
    for i:= 5 ;i<10;i++{
        fmt.Println(i + " ")
    }
}()
}

The output of this code snippet will be something like this:

0 1 5 2 6 7 3 8 9 4
# It does not have to be exactly same.

Channels

Channels are one of the most essential building blocks of our concurrency design patterns. To put it very simply

Channels are just queues that can be used to send data from one goroutine to another.

Here are a few important facts about channels:

// This is a read channel. You can only read values
var readChannel <-chan interface{} 

// This is a write channel. You can only write values to this channel
var writeChannel chan<- interface{}

//You can read as well as write to this channel
var channel chan interface{}

channel1 <- val // we are pushing a value to a channel
val <- channel2 // we are reading from a channel

Now let’s jump to the fun part

What is a goroutine leak?

We use a goroutine to perform some operations, and after some time send us the result and terminate. But what happens if it doesn’t terminate? well, this is what we call a goroutine leak.

Even though goroutines are lightweight, if we invoke a lot of goroutines then we will be wasting a lot of resources.

Before we discuss, how we are gonna prevent goroutine leaks, let’s check out an example:

func test() <-chan int {
    outStream := make(chan int)
    go func(outStream chan<- int) {
        for i := 0; i < 10; i-- {
            outStream <- i
        }
    }(outStream)
    return outStream
}

func main() {
    inStream := test()
    for i := 0; i < 10; i++ {
        val := <-inStream
        fmt.Println(val)
        time.Sleep(1 * time.Second)
    }
}

Here we have a test function that returns a read channel (we can only read values from this channel). It then invokes a goroutine and passes the channel as a write channel (we can only write to this channel). We are running a for loop and writing values to the channel. But here’s the interesting part, the for loop is infinite!!. We have done this to mimic the case where our goroutine does not terminate and keeps sending value.

When we run the code, we get the following output:

Image description

We stopped the loop after 10 iterations but it can go on forever.

We stopped the loop after 10 iterations but it can go on forever.

Let’s look at the code implementation to understand it better

func test(terminate <-chan bool) <-chan int {
    outStream := make(chan int)
    go func(terminate <-chan bool, outStream chan<- int) {
        defer close(outStream) // Channel is closed when the function is finishes executing
        for i := 0; i < 10; i-- {
            select {
            case <-terminate:
                fmt.Println("Child Terminating")
                return
            case outStream <- i:
            }
        }
    }(terminate, outStream)
    return outStream
}

func main() {
    terminate := make(chan bool)

    inStream := test(terminate)
    for i := 0; i < 10; i++ {
        val, ok := <-inStream

       // If !ok, then it means channel is closed and we won't receive new value
        if !ok {
            fmt.Println("Channel closed")
            break
        }

        fmt.Println(val)
        time.Sleep(1 * time.Second)
        if i == 5 {
            close(terminate)
        }
    }
}

Okay, let’s discuss how this code works

Now if we execute the code, it looks something like this:

Image description

Thanks for reading till the end. If you liked my blog then do subscribe to my newsletter for more awesome content.

Cover picture credit: https://www.storj.io/blog/finding-goroutine-leaks-in-tests