liveBook · Manning

Time.After

As explained in 100 Go Mistakes and How to Avoid Them

time.After is a Go standard-library function that returns a channel receiving a time value after a specified duration elapses.

time.After(duration) returns a channel that receives a time value after the duration elapses. Use it in select statements for deadlines or inactivity warnings; use time.Sleep when the goal is only to pause execution. section 10.2

func consumer(ch <-chan Event) {
    for {
        select {
        case event := <-ch:               #1
            handle(event)
        case <-time.After(time.Hour):     #2
            log.Println("warning: no messages received")
        }
    }
}

This consumer waits for either a message or one hour of silence. Because time.After is evaluated on every iteration, each received message resets the timeout. section 10.2

The loop can nevertheless accumulate memory. Each call creates timer resources and a channel that remain allocated until the timeout expires; receiving a message does not release them. In Go 1.15, one call used about 200 bytes, so five million calls per hour could retain roughly 1 GB. The returned receive-only channel cannot be closed programmatically. section 10.2

Choose the replacement according to the loop’s lifetime and timer requirements:

  • Use context.WithTimeout when cancellation should be explicit, calling cancel after receiving a message. Recreating the context on every iteration is itself not lightweight. section 10.2
  • Use a reusable time.Timer when the loop repeatedly needs the same timeout; create it once and reset it for subsequent iterations. section 10.2
  • Keep time.After for cases where the timer lifetime and allocation pattern are not repeated heavily, rather than applying it blindly inside loops or handlers. section 10.8
package time
 
func After(d Duration) <-chan Time {
    return NewTimer(d).C                #1
}

The implementation shows why time.After creates timer resources: it returns the channel produced by time.NewTimer. section 10.2

func consumer(ch <-chan Event) {
    timerDuration := 1 * time.Hour
    timer := time.NewTimer(timerDuration)     #1
 
    for {                                     #2
        timer.Reset(timerDuration)            #3
        select {
        case event := <-ch:
            handle(event)
        case <-timer.C:                       #4
            log.Println("warning: no messages received")
        }
    }
}

A reusable timer avoids constructing a new time.After timer during every loop iteration. section 10.2

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Explains channel creation, timer lifetime, loop accumulation, and contextual alternatives, without detailing timer-reset synchronization.
Examples
Uses a channel consumer, one-hour inactivity warnings, five million hourly messages, and reusable timer code.
What it assumes
Assumes familiarity with channels, select, loops, and concurrent message consumption.
What you can do after
Diagnose/debug with it by identifying repeated timer allocation and choosing context or reusable timers.
How it connects
Connects time.Duration, channels, select, time.Sleep, context.WithTimeout, and time.NewTimer.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.