package common import ( "time" ) /* RepeatTimer repeatedly sends a struct{}{} to .Ch after each "dur" period. It's good for keeping connections alive. A RepeatTimer must be Stop()'d or it will keep a goroutine alive. */ type RepeatTimer struct { Name string Ch <-chan time.Time output chan<- time.Time input chan repeatCommand dur time.Duration ticker *time.Ticker stopped bool } type repeatCommand int8 const ( Reset repeatCommand = iota RQuit ) func NewRepeatTimer(name string, dur time.Duration) *RepeatTimer { c := make(chan time.Time) var t = &RepeatTimer{ Name: name, Ch: c, output: c, input: make(chan repeatCommand), dur: dur, ticker: time.NewTicker(dur), } go t.run() return t } // Wait the duration again before firing. func (t *RepeatTimer) Reset() { t.input <- Reset } // For ease of .Stop()'ing services before .Start()'ing them, // we ignore .Stop()'s on nil RepeatTimers. func (t *RepeatTimer) Stop() bool { // use t.stopped to gracefully handle many Stop() without blocking if t == nil || t.stopped { return false } t.input <- RQuit t.stopped = true return true } func (t *RepeatTimer) run() { done := false for !done { select { case cmd := <-t.input: // stop goroutine if the input says so // don't close channels, as closed channels mess up select reads done = t.processInput(cmd) case tick := <-t.ticker.C: t.send(tick) } } } // send performs blocking send on t.Ch func (t *RepeatTimer) send(tick time.Time) { // XXX: possibly it is better to not block: // https://golang.org/src/time/sleep.go#L132 // select { // case t.output <- tick: // default: // } t.output <- tick } // all modifications of the internal state of ThrottleTimer // happen in this method. It is only called from the run goroutine // so we avoid any race conditions func (t *RepeatTimer) processInput(cmd repeatCommand) (shutdown bool) { switch cmd { case Reset: t.ticker.Stop() t.ticker = time.NewTicker(t.dur) case RQuit: t.ticker.Stop() shutdown = true default: panic("unknown command!") } return shutdown }