gotimer

How to get time.Tick to tick immediately


I have a loop that iterates until a job is up and running:

ticker := time.NewTicker(time.Second * 2)
defer ticker.Stop()

started := time.Now()
for now := range ticker.C {
    job, err := client.Job(jobID)
    switch err.(type) {
    case DoesNotExistError:
        continue
    case InternalError:
        return err
    }

    if job.State == "running" {
        break
    }

    if now.Sub(started) > time.Minute*2 {
        return fmt.Errorf("timed out waiting for job")
    }
}

Works great in production. The only problem is that it makes my tests slow. They all wait at least 2 seconds before completing. Is there anyway to get time.Tick to tick immediately?


Solution

  • Unfortunately, it seems that Go developers will not add such functionality in any foreseeable future, so we have to cope...

    There are two common ways to use tickers:

    for loop

    Given something like this:

    ticker := time.NewTicker(period)
    defer ticker.Stop()
    for <- ticker.C {
        ...
    }
    

    Use:

    ticker := time.NewTicker(period)
    defer ticker.Stop()
    for ; true; <- ticker.C {
        ...
    }
    

    for-select loop

    Given something like this:

    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, os.Interrupt)
    
    ticker := time.NewTicker(period)
    defer ticker.Stop()
    
    loop:
    for {
        select {
            case <- ticker.C: 
                f()
            case <- interrupt:
                break loop
        }
    }
    

    Use:

    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, os.Interrupt)
    
    ticker := time.NewTicker(period)
    defer ticker.Stop()
    
    loop:
    for {
        f()
    
        select {
            case <- ticker.C: 
                continue
            case <- interrupt:
                break loop
        }
    }
    

    Why not just use time.Tick()?

    If you're using Go 1.23+, you can safely use time.Tick() instead.

    Before Go 1.23:

    While Tick is useful for clients that have no need to shut down the Ticker, be aware that without a way to shut it down the underlying Ticker cannot be recovered by the garbage collector; it "leaks".

    After Go 1.23:

    Before Go 1.23, this documentation warned that the underlying Ticker would never be recovered by the garbage collector, and that if efficiency was a concern, code should use NewTicker instead and call Ticker.Stop when the ticker is no longer needed. As of Go 1.23, the garbage collector can recover unreferenced tickers, even if they haven't been stopped. The Stop method is no longer necessary to help the garbage collector. There is no longer any reason to prefer NewTicker when Tick will do.

    https://golang.org/pkg/time/#Tick