tendermint/libs/common/repeat_timer.go

233 lines
4.9 KiB
Go
Raw Normal View History

2015-10-21 12:15:19 -07:00
package common
import (
2017-12-19 14:16:16 -08:00
"sync"
"time"
)
2015-10-21 12:15:19 -07:00
2017-12-23 04:18:50 -08:00
// Used by RepeatTimer the first time,
// and every time it's Reset() after Stop().
type TickerMaker func(dur time.Duration) Ticker
// Ticker is a basic ticker interface.
type Ticker interface {
2017-12-23 04:18:50 -08:00
// Never changes, never closes.
Chan() <-chan time.Time
2017-12-23 04:18:50 -08:00
// Stopping a stopped Ticker will panic.
Stop()
}
2017-12-23 04:18:50 -08:00
//----------------------------------------
2018-02-12 16:12:24 -08:00
// defaultTicker
var _ Ticker = (*defaultTicker)(nil)
type defaultTicker time.Ticker
2017-12-23 04:18:50 -08:00
func defaultTickerMaker(dur time.Duration) Ticker {
ticker := time.NewTicker(dur)
return (*defaultTicker)(ticker)
}
// Implements Ticker
2017-12-23 04:18:50 -08:00
func (t *defaultTicker) Chan() <-chan time.Time {
return t.C
}
// Implements Ticker
2017-12-23 04:18:50 -08:00
func (t *defaultTicker) Stop() {
((*time.Ticker)(t)).Stop()
}
2017-12-23 04:18:50 -08:00
//----------------------------------------
// LogicalTickerMaker
2017-12-25 07:13:37 -08:00
// Construct a TickerMaker that always uses `source`.
2017-12-23 04:18:50 -08:00
// It's useful for simulating a deterministic clock.
2017-12-25 07:13:37 -08:00
func NewLogicalTickerMaker(source chan time.Time) TickerMaker {
2017-12-23 04:18:50 -08:00
return func(dur time.Duration) Ticker {
2017-12-25 07:13:37 -08:00
return newLogicalTicker(source, dur)
2017-12-23 04:18:50 -08:00
}
}
2017-12-23 04:18:50 -08:00
type logicalTicker struct {
source <-chan time.Time
ch chan time.Time
quit chan struct{}
}
2017-12-23 04:18:50 -08:00
func newLogicalTicker(source <-chan time.Time, interval time.Duration) Ticker {
lt := &logicalTicker{
source: source,
ch: make(chan time.Time),
quit: make(chan struct{}),
}
2017-12-23 04:18:50 -08:00
go lt.fireRoutine(interval)
return lt
}
2017-12-25 07:13:37 -08:00
// We need a goroutine to read times from t.source
// and fire on t.Chan() when `interval` has passed.
2017-12-23 04:18:50 -08:00
func (t *logicalTicker) fireRoutine(interval time.Duration) {
source := t.source
// Init `lasttime`
lasttime := time.Time{}
select {
case lasttime = <-source:
case <-t.quit:
return
}
// Init `lasttime` end
for {
select {
case newtime := <-source:
elapsed := newtime.Sub(lasttime)
if interval <= elapsed {
2017-12-23 04:18:50 -08:00
// Block for determinism until the ticker is stopped.
select {
case t.ch <- newtime:
case <-t.quit:
return
}
// Reset timeleft.
// Don't try to "catch up" by sending more.
// "Ticker adjusts the intervals or drops ticks to make up for
// slow receivers" - https://golang.org/pkg/time/#Ticker
lasttime = newtime
2017-12-23 04:18:50 -08:00
}
case <-t.quit:
return // done
}
}
}
// Implements Ticker
2017-12-23 04:18:50 -08:00
func (t *logicalTicker) Chan() <-chan time.Time {
return t.ch // immutable
}
// Implements Ticker
2017-12-23 04:18:50 -08:00
func (t *logicalTicker) Stop() {
close(t.quit) // it *should* panic when stopped twice.
}
//---------------------------------------------------------------------
2015-10-21 12:15:19 -07:00
/*
2017-12-23 04:18:50 -08:00
RepeatTimer repeatedly sends a struct{}{} to `.Chan()` after each `dur`
period. (It's good for keeping connections alive.)
A RepeatTimer must be stopped, or it will keep a goroutine alive.
2015-10-21 12:15:19 -07:00
*/
type RepeatTimer struct {
2017-12-23 04:18:50 -08:00
name string
ch chan time.Time
tm TickerMaker
2015-10-21 12:15:19 -07:00
2017-12-19 14:16:16 -08:00
mtx sync.Mutex
2017-12-23 04:18:50 -08:00
dur time.Duration
ticker Ticker
2017-12-19 14:16:16 -08:00
quit chan struct{}
2015-10-21 12:15:19 -07:00
}
2017-12-23 04:18:50 -08:00
// NewRepeatTimer returns a RepeatTimer with a defaultTicker.
2015-10-21 12:15:19 -07:00
func NewRepeatTimer(name string, dur time.Duration) *RepeatTimer {
2017-12-23 04:18:50 -08:00
return NewRepeatTimerWithTickerMaker(name, dur, defaultTickerMaker)
}
2017-12-23 04:18:50 -08:00
// NewRepeatTimerWithTicker returns a RepeatTimer with the given ticker
// maker.
func NewRepeatTimerWithTickerMaker(name string, dur time.Duration, tm TickerMaker) *RepeatTimer {
2015-10-21 12:15:19 -07:00
var t = &RepeatTimer{
2017-12-19 14:16:16 -08:00
name: name,
2017-12-23 04:18:50 -08:00
ch: make(chan time.Time),
tm: tm,
dur: dur,
ticker: nil,
quit: nil,
2015-10-21 12:15:19 -07:00
}
2017-12-23 04:18:50 -08:00
t.reset()
2017-12-07 01:15:38 -08:00
return t
2015-10-21 12:15:19 -07:00
}
2018-02-12 16:12:24 -08:00
// receive ticks on ch, send out on t.ch
2017-12-23 04:18:50 -08:00
func (t *RepeatTimer) fireRoutine(ch <-chan time.Time, quit <-chan struct{}) {
2017-12-19 14:16:16 -08:00
for {
select {
2018-02-12 16:12:24 -08:00
case tick := <-ch:
select {
2018-02-12 16:12:24 -08:00
case t.ch <- tick:
case <-quit:
return
}
2017-12-23 04:18:50 -08:00
case <-quit: // NOTE: `t.quit` races.
2017-12-19 14:16:16 -08:00
return
}
}
}
2017-12-23 04:18:50 -08:00
func (t *RepeatTimer) Chan() <-chan time.Time {
return t.ch
}
func (t *RepeatTimer) Stop() {
t.mtx.Lock()
defer t.mtx.Unlock()
t.stop()
}
2015-10-21 12:15:19 -07:00
// Wait the duration again before firing.
func (t *RepeatTimer) Reset() {
2017-12-23 04:18:50 -08:00
t.mtx.Lock()
2017-12-19 14:16:16 -08:00
defer t.mtx.Unlock()
2017-12-23 04:18:50 -08:00
t.reset()
2015-10-21 12:15:19 -07:00
}
2017-12-23 04:18:50 -08:00
//----------------------------------------
// Misc.
// CONTRACT: (non-constructor) caller should hold t.mtx.
func (t *RepeatTimer) reset() {
if t.ticker != nil {
t.stop()
2015-10-21 12:15:19 -07:00
}
2017-12-23 04:18:50 -08:00
t.ticker = t.tm(t.dur)
t.quit = make(chan struct{})
go t.fireRoutine(t.ticker.Chan(), t.quit)
}
// CONTRACT: caller should hold t.mtx.
func (t *RepeatTimer) stop() {
if t.ticker == nil {
/*
Similar to the case of closing channels twice:
https://groups.google.com/forum/#!topic/golang-nuts/rhxMiNmRAPk
Stopping a RepeatTimer twice implies that you do
not know whether you are done or not.
If you're calling stop on a stopped RepeatTimer,
you probably have race conditions.
*/
panic("Tried to stop a stopped RepeatTimer")
}
t.ticker.Stop()
t.ticker = nil
/*
From https://golang.org/pkg/time/#Ticker:
"Stop the ticker to release associated resources"
"After Stop, no more ticks will be sent"
So we shouldn't have to do the below.
2015-10-21 12:15:19 -07:00
select {
2017-12-23 04:18:50 -08:00
case <-t.ch:
2017-12-19 14:16:16 -08:00
// read off channel if there's anything there
default:
}
2017-12-23 04:18:50 -08:00
*/
close(t.quit)
2015-10-21 12:15:19 -07:00
}