diff --git a/ticker/ticker.go b/ticker/ticker.go new file mode 100644 index 00000000..29e0c04b --- /dev/null +++ b/ticker/ticker.go @@ -0,0 +1,123 @@ +package ticker + +import "time" + +// Ticker defines a resumable ticker interface, whose activity can be toggled to +// free up resources during periods of inactivity. +// +// Example of resuming ticker: +// +// ticker.Resume() // can remove to keep inactive at first +// defer ticker.Stop() +// for { +// select { +// case <-ticker.Tick(): +// if shouldGoInactive { +// ticker.Pause() +// continue +// } +// ... +// +// case <-otherEvent: +// ... +// if shouldGoActive { +// ticker.Resume() +// } +// } +// +// NOTE: ONE DOES NOT SIMPLY assume that Tickers are safe for concurrent access. +type Ticker interface { + // Ticks returns a read-only channel delivering ticks according to a + // prescribed interval. The value returned does not need to be the same + // channel, and may be nil. + // + // NOTE: Callers should assume that reads from Ticks() are stale after + // any invocations of Resume, Pause, or Stop. + Ticks() <-chan time.Time + + // Resume starts or resumes the underlying ticker, such that Ticks() + // will fire at regular intervals. After calling Resume, Ticks() should + // minimally send ticks at the prescribed interval. + // + // NOTE: It MUST be safe to call Resume at any time, and more than once + // successively. + Resume() + + // Pause suspends the underlying ticker, such that Ticks() stops + // signaling at regular intervals. After calling Pause, the ticker + // should not send any ticks scheduled with the chosen interval. Forced + // ticks are still permissible, as in the case of the Mock Ticker. + // + // NOTE: It MUST be safe to call Pause at any time, and more than once + // successively. + Pause() + + // Stop suspends the underlying ticker, such that Ticks() stops + // signaling at regular intervals, and permanently frees up any + // remaining resources. + // + // NOTE: The behavior of a Ticker is undefined after calling Stop. + Stop() +} + +// ticker is the production implementation of the resumable Ticker interface. +// This allows various components to toggle their need for tick events, which +// may vary depending on system load. +type ticker struct { + // interval is the desired duration between ticks when active. + interval time.Duration + + // ticker is the ephemeral, underlying time.Ticker. We keep a reference + // to this ticker so that it can be stopped and cleaned up on Pause or + // Stop. + ticker *time.Ticker +} + +// New returns a new ticker that signals with the given interval when not +// paused. The ticker starts off inactive. +func New(interval time.Duration) Ticker { + return &ticker{ + interval: interval, + } +} + +// Ticks returns a receive-only channel that delivers times at the ticker's +// prescribed interval. This method returns nil when the ticker is paused. +// +// NOTE: Part of the Ticker interface. +func (t *ticker) Ticks() <-chan time.Time { + if t.ticker == nil { + return nil + } + return t.ticker.C +} + +// Resumes starts underlying time.Ticker and causes the ticker to begin +// delivering scheduled events. +// +// NOTE: Part of the Ticker interface. +func (t *ticker) Resume() { + if t.ticker == nil { + t.ticker = time.NewTicker(t.interval) + } +} + +// Pause suspends the underlying ticker, such that Ticks() stops signaling at +// regular intervals. +// +// NOTE: Part of the Ticker interface. +func (t *ticker) Pause() { + if t.ticker != nil { + t.ticker.Stop() + t.ticker = nil + } +} + +// Stop suspends the underlying ticker, such that Ticks() stops signaling at +// regular intervals, and permanently frees up any resources. For this +// implementation, this is equivalent to Pause. +// +// NOTE: Part of the Ticker interface. +func (t *ticker) Stop() { + t.Pause() +}