Lesson 8 / 25
Timeouts, Timers and Tickers
time.After, NewTimer, NewTicker and their pitfalls.
Putting time into select
time.After(d) returns a channel that receives once after d, which makes a one-line timeout: case <-time.After(2*time.Second):. Inside a loop, however, time.After creates a new timer every iteration, so the timeout restarts each time any other case fires, and in Go versions before 1.23 each un-fired timer stayed in memory until it expired. For a timeout that spans the whole loop, create one time.NewTimer before the loop; for periodic work use time.NewTicker and always defer t.Stop(). Go 1.23 changed timers so that unreferenced timers and tickers can be garbage-collected and Reset/Stop no longer leave stale values in the channel (check the release notes for your version). In request-handling code, prefer a context.WithTimeout, which also propagates to callees.
One overall deadline plus a periodic tick
A single timer for the whole loop, a ticker for progress.
package main
import (
"fmt"
"time"
)
func main() {
work := make(chan int)
go func() {
for i := range 5 {
time.Sleep(150 * time.Millisecond)
work <- i
}
}()
deadline := time.NewTimer(500 * time.Millisecond) // created once
defer deadline.Stop()
tick := time.NewTicker(200 * time.Millisecond)
defer tick.Stop()
for {
select {
case v := <-work:
fmt.Println("result", v)
case <-tick.C:
fmt.Println("still working...")
case <-deadline.C:
fmt.Println("overall timeout")
// In a long-running program the producer would now be stuck
// on its send forever: a goroutine leak (see section 8).
return
}
}
}Per-wait vs overall timeouts
time.After inside the loop means "no event for d"; a timer made once outside the loop means "the whole operation within d". Decide which one you need before writing the select.
Quick check: Why is time.After inside a busy for-select loop often a mistake for an overall timeout?
- time.After panics after the first use
- time.After cannot be used in select
- A new timer is created every iteration, so the timeout restarts whenever another case fires
- It makes the select choose cases in order
Answer
A new timer is created every iteration, so the timeout restarts whenever another case fires — Create one timer before the loop, or use a context with a deadline.