Levey
Levey

Reputation: 48

Is this func possible to cause goroutine leak

func startTimer(ctx context.Context, intervalTime int) {
    intervalChan := make(chan bool) 
    go func() {
        for {
            select {
            case <-ctx.Done():
                return
            case <-time.After(time.Second * time.Duration(intervalTime)):
                intervalChan <- true
            }
        }
    }()


    for {
        select {
        case <-ctx.Done():
            return
        case <-intervalChan:
            doSomething()
    }
}

Hi,I write a func as above and want to know is it possible to cause goroutine leak.

For example, the first select statement sends a true to intervalChan, then the second select statement receives Done flag from ctx.Done() and return. Will the goroutine be block forever?

Upvotes: 0

Views: 450

Answers (2)

Mr_Pink
Mr_Pink

Reputation: 109404

The only place your first goroutine could be blocked indefinitely is in intervalChan <- true. Put it in another select block to be able to cancel that send:

go func() {
    for {
        select {
        case <-ctx.Done():
            return
        case <-time.After(time.Second * time.Duration(intervalTime)):
            select {
            case <-ctx.Done():
                return
            case intervalChan <- true:
            }
        }
    }
}()

Upvotes: 1

s7anley
s7anley

Reputation: 2498

I cannot replicate this behaviour every time but could be some leak. If doSomething do some heavy computation, meanwhile goroutine is blocked on intervalChan <- true since it cannot push into the channel. After doSomething finish execution and context was cancelled, startTimer exists before goroutine and this will lead into blocked goroutine, because there isn't any consumer of intervalChan.

go version go1.8.3 darwin/amd64

package main

import (
    "context"
    "fmt"
    "time"
)

func startTimer(ctx context.Context, intervalTime int) {
    intervalChan := make(chan bool)
    go func() {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Done from inside of goroutine.")
                return
            case <-time.After(time.Second * time.Duration(intervalTime)):
                fmt.Println("Interval reached.")
                intervalChan <- true
            }
        }
    }()

    for {
        select {
        case <-ctx.Done():
            fmt.Println("Done from startTimer.")
            return
        case <-intervalChan:
            time.Sleep(10 * time.Second)
            fmt.Println("Done")
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    startTimer(ctx, 2)
}

Upvotes: 1

Related Questions