// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package time import ( "os" "sync" ) // A Ticker holds a synchronous channel that delivers `ticks' of a clock // at intervals. type Ticker struct { C <-chan int64 // The channel on which the ticks are delivered. c chan<- int64 // The same channel, but the end we use. ns int64 shutdown chan bool // Buffered channel used to signal shutdown. nextTick int64 next *Ticker } // Stop turns off a ticker. After Stop, no more ticks will be sent. func (t *Ticker) Stop() { // Make it non-blocking so multiple Stops don't block. _ = t.shutdown <- true } // Tick is a convenience wrapper for NewTicker providing access to the ticking // channel only. Useful for clients that have no need to shut down the ticker. func Tick(ns int64) <-chan int64 { if ns <= 0 { return nil } return NewTicker(ns).C } type alarmer struct { wakeUp chan bool // wakeup signals sent/received here wakeMeAt chan int64 wakeTime int64 } // Set alarm to go off at time ns, if not already set earlier. func (a *alarmer) set(ns int64) { switch { case a.wakeTime > ns: // Next tick we expect is too late; shut down the late runner // and (after fallthrough) start a new wakeLoop. close(a.wakeMeAt) fallthrough case a.wakeMeAt == nil: // There's no wakeLoop, start one. a.wakeMeAt = make(chan int64) a.wakeUp = make(chan bool, 1) go wakeLoop(a.wakeMeAt, a.wakeUp) fallthrough case a.wakeTime == 0: // Nobody else is waiting; it's just us. a.wakeTime = ns a.wakeMeAt <- ns default: // There's already someone scheduled. } } // Channel to notify tickerLoop of new Tickers being created. var newTicker chan *Ticker func startTickerLoop() { newTicker = make(chan *Ticker) go tickerLoop() } // wakeLoop delivers ticks at scheduled times, sleeping until the right moment. // If another, earlier Ticker is created while it sleeps, tickerLoop() will start a new // wakeLoop and signal that this one is done by closing the wakeMeAt channel. func wakeLoop(wakeMeAt chan int64, wakeUp chan bool) { for wakeAt := range wakeMeAt { Sleep(wakeAt - Nanoseconds()) wakeUp <- true } } // A single tickerLoop serves all ticks to Tickers. It waits for two events: // either the creation of a new Ticker or a tick from the alarm, // signalling a time to wake up one or more Tickers. func tickerLoop() { // Represents the next alarm to be delivered. var alarm alarmer var now, wakeTime int64 var tickers *Ticker for { select { case t := <-newTicker: // Add Ticker to list t.next = tickers tickers = t // Arrange for a new alarm if this one precedes the existing one. alarm.set(t.nextTick) case <-alarm.wakeUp: now = Nanoseconds() wakeTime = now + 1e15 // very long in the future var prev *Ticker = nil // Scan list of tickers, delivering updates to those // that need it and determining the next wake time. // TODO(r): list should be sorted in time order. for t := tickers; t != nil; t = t.next { if _, ok := <-t.shutdown; ok { // Ticker is done; remove it from list. if prev == nil { tickers = t.next } else { prev.next = t.next } continue } if t.nextTick <= now { if len(t.c) == 0 { // Only send if there's room. We must not block. // The channel is allocated with a one-element // buffer, which is sufficient: if he hasn't picked // up the last tick, no point in sending more. t.c <- now } t.nextTick += t.ns if t.nextTick <= now { // Still behind; advance in one big step. t.nextTick += (now - t.nextTick + t.ns) / t.ns * t.ns } } if t.nextTick < wakeTime { wakeTime = t.nextTick } prev = t } if tickers != nil { // Please send wakeup at earliest required time. // If there are no tickers, don't bother. alarm.wakeTime = wakeTime alarm.wakeMeAt <- wakeTime } else { alarm.wakeTime = 0 } } } } var onceStartTickerLoop sync.Once // NewTicker returns a new Ticker containing a channel that will // send the time, in nanoseconds, every ns nanoseconds. It adjusts the // intervals to make up for pauses in delivery of the ticks. The value of // ns must be greater than zero; if not, NewTicker will panic. func NewTicker(ns int64) *Ticker { if ns <= 0 { panic(os.ErrorString("non-positive interval for NewTicker")) } c := make(chan int64, 1) // See comment on send in tickerLoop t := &Ticker{ C: c, c: c, ns: ns, shutdown: make(chan bool, 1), nextTick: Nanoseconds() + ns, } onceStartTickerLoop.Do(startTickerLoop) // must be run in background so global Tickers can be created go func() { newTicker <- t }() return t }