loopsgotimerdeferred-execution

Stop time.NewTimer initialized inside for loop


I have a program similar to below program:

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan string)
    go endProgram(ch)
    printFunc(ch)
}

func printFunc(ch chan string) {
    for {
        timeout := time.NewTimer(getTimeoutDuration())
        defer timeout.Stop()
        select {
        case s := <-ch:
            fmt.Println(s)
            return
        case <-timeout.C:
            fmt.Println("Current value")
        }
    }
}

func endProgram(ch chan string) {
    time.Sleep(time.Second * 8)
    ch <- "Exit function"
}

func getTimeoutDuration() time.Duration {
    return time.Second * 3
}

What is the best way to stop the timeout timer in this case?

I know that above is not the recommended way because it is a bad practice to use defer inside for loop. Alternative is to use time.After inside the for loop instead of time.NewTimer as we don't have to stop time.After. But time.After causes resource leak if function exits before the timer fires(Source).


Solution

  • if you use context instead of timer, Where cancel only called when exiting function case condition.

    package main
    
    import (
        "context"
        "fmt"
        "time"
    )
    
    func main() {
        ch := make(chan string)
        go endProgram(ch)
        printFunc(ch)
    }
    
    func printFunc(ch chan string) {
        for {
            ctx, cancel := context.WithTimeout(context.Background(), getTimeoutDuration())
            select {
            case s := <-ch:
                cancel()
                fmt.Println(s)
                return
            case <-ctx.Done():
                fmt.Println("Current value")
            }
        }
    }
    
    func endProgram(ch chan string) {
        time.Sleep(time.Second * 8)
        ch <- "Exit function"
    }
    
    func getTimeoutDuration() time.Duration {
        return time.Second * 3
    }