diff options
Diffstat (limited to 'libgo/go/time/tick.go')
-rw-r--r-- | libgo/go/time/tick.go | 188 |
1 files changed, 32 insertions, 156 deletions
diff --git a/libgo/go/time/tick.go b/libgo/go/time/tick.go index 92f9eb893e4..4440c2207b3 100644 --- a/libgo/go/time/tick.go +++ b/libgo/go/time/tick.go @@ -4,174 +4,50 @@ package time -import ( - "errors" - "sync" -) +import "errors" // 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 + C <-chan Time // The channel on which the ticks are delivered. + r runtimeTimer +} + +// NewTicker returns a new Ticker containing a channel that will send the +// time, in nanoseconds, with a period specified by the duration argument. +// It adjusts the intervals or drops ticks to make up for slow receivers. +// The duration d must be greater than zero; if not, NewTicker will panic. +func NewTicker(d Duration) *Ticker { + if d <= 0 { + panic(errors.New("non-positive interval for NewTicker")) + } + // Give the channel a 1-element time buffer. + // If the client falls behind while reading, we drop ticks + // on the floor until the client catches up. + c := make(chan Time, 1) + t := &Ticker{ + C: c, + r: runtimeTimer{ + when: nano() + int64(d), + period: int64(d), + f: sendTime, + arg: c, + }, + } + startTimer(&t.r) + return t } // Stop turns off a ticker. After Stop, no more ticks will be sent. func (t *Ticker) Stop() { - select { - case t.shutdown <- true: - // ok - default: - // Stop in progress already - } + stopTimer(&t.r) } // 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 { +func Tick(d Duration) <-chan Time { + if d <= 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, -// signaling 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 { - select { - case <-t.shutdown: - // Ticker is done; remove it from list. - if prev == nil { - tickers = t.next - } else { - prev.next = t.next - } - continue - default: - } - 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(errors.New("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 + return NewTicker(d).C } |