github.com/supragya/TendermintConnector@v0.0.0-20210619045051-113e32b84fb1/_deprecated_chains/cosmos/libs/common/repeat_timer.go (about)

     1  package common
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  )
     7  
     8  // Used by RepeatTimer the first time,
     9  // and every time it's Reset() after Stop().
    10  type TickerMaker func(dur time.Duration) Ticker
    11  
    12  // Ticker is a basic ticker interface.
    13  type Ticker interface {
    14  
    15  	// Never changes, never closes.
    16  	Chan() <-chan time.Time
    17  
    18  	// Stopping a stopped Ticker will panic.
    19  	Stop()
    20  }
    21  
    22  //----------------------------------------
    23  // defaultTicker
    24  
    25  var _ Ticker = (*defaultTicker)(nil)
    26  
    27  type defaultTicker time.Ticker
    28  
    29  func defaultTickerMaker(dur time.Duration) Ticker {
    30  	ticker := time.NewTicker(dur)
    31  	return (*defaultTicker)(ticker)
    32  }
    33  
    34  // Implements Ticker
    35  func (t *defaultTicker) Chan() <-chan time.Time {
    36  	return t.C
    37  }
    38  
    39  // Implements Ticker
    40  func (t *defaultTicker) Stop() {
    41  	((*time.Ticker)(t)).Stop()
    42  }
    43  
    44  //----------------------------------------
    45  // LogicalTickerMaker
    46  
    47  // Construct a TickerMaker that always uses `source`.
    48  // It's useful for simulating a deterministic clock.
    49  func NewLogicalTickerMaker(source chan time.Time) TickerMaker {
    50  	return func(dur time.Duration) Ticker {
    51  		return newLogicalTicker(source, dur)
    52  	}
    53  }
    54  
    55  type logicalTicker struct {
    56  	source <-chan time.Time
    57  	ch     chan time.Time
    58  	quit   chan struct{}
    59  }
    60  
    61  func newLogicalTicker(source <-chan time.Time, interval time.Duration) Ticker {
    62  	lt := &logicalTicker{
    63  		source: source,
    64  		ch:     make(chan time.Time),
    65  		quit:   make(chan struct{}),
    66  	}
    67  	go lt.fireRoutine(interval)
    68  	return lt
    69  }
    70  
    71  // We need a goroutine to read times from t.source
    72  // and fire on t.Chan() when `interval` has passed.
    73  func (t *logicalTicker) fireRoutine(interval time.Duration) {
    74  	source := t.source
    75  
    76  	// Init `lasttime`
    77  	lasttime := time.Time{}
    78  	select {
    79  	case lasttime = <-source:
    80  	case <-t.quit:
    81  		return
    82  	}
    83  	// Init `lasttime` end
    84  
    85  	for {
    86  		select {
    87  		case newtime := <-source:
    88  			elapsed := newtime.Sub(lasttime)
    89  			if interval <= elapsed {
    90  				// Block for determinism until the ticker is stopped.
    91  				select {
    92  				case t.ch <- newtime:
    93  				case <-t.quit:
    94  					return
    95  				}
    96  				// Reset timeleft.
    97  				// Don't try to "catch up" by sending more.
    98  				// "Ticker adjusts the intervals or drops ticks to make up for
    99  				// slow receivers" - https://golang.org/pkg/time/#Ticker
   100  				lasttime = newtime
   101  			}
   102  		case <-t.quit:
   103  			return // done
   104  		}
   105  	}
   106  }
   107  
   108  // Implements Ticker
   109  func (t *logicalTicker) Chan() <-chan time.Time {
   110  	return t.ch // immutable
   111  }
   112  
   113  // Implements Ticker
   114  func (t *logicalTicker) Stop() {
   115  	close(t.quit) // it *should* panic when stopped twice.
   116  }
   117  
   118  //---------------------------------------------------------------------
   119  
   120  /*
   121  	RepeatTimer repeatedly sends a struct{}{} to `.Chan()` after each `dur`
   122  	period. (It's good for keeping connections alive.)
   123  	A RepeatTimer must be stopped, or it will keep a goroutine alive.
   124  */
   125  type RepeatTimer struct {
   126  	name string
   127  	ch   chan time.Time
   128  	tm   TickerMaker
   129  
   130  	mtx    sync.Mutex
   131  	dur    time.Duration
   132  	ticker Ticker
   133  	quit   chan struct{}
   134  }
   135  
   136  // NewRepeatTimer returns a RepeatTimer with a defaultTicker.
   137  func NewRepeatTimer(name string, dur time.Duration) *RepeatTimer {
   138  	return NewRepeatTimerWithTickerMaker(name, dur, defaultTickerMaker)
   139  }
   140  
   141  // NewRepeatTimerWithTicker returns a RepeatTimer with the given ticker
   142  // maker.
   143  func NewRepeatTimerWithTickerMaker(name string, dur time.Duration, tm TickerMaker) *RepeatTimer {
   144  	var t = &RepeatTimer{
   145  		name:   name,
   146  		ch:     make(chan time.Time),
   147  		tm:     tm,
   148  		dur:    dur,
   149  		ticker: nil,
   150  		quit:   nil,
   151  	}
   152  	t.reset()
   153  	return t
   154  }
   155  
   156  // receive ticks on ch, send out on t.ch
   157  func (t *RepeatTimer) fireRoutine(ch <-chan time.Time, quit <-chan struct{}) {
   158  	for {
   159  		select {
   160  		case tick := <-ch:
   161  			select {
   162  			case t.ch <- tick:
   163  			case <-quit:
   164  				return
   165  			}
   166  		case <-quit: // NOTE: `t.quit` races.
   167  			return
   168  		}
   169  	}
   170  }
   171  
   172  func (t *RepeatTimer) Chan() <-chan time.Time {
   173  	return t.ch
   174  }
   175  
   176  func (t *RepeatTimer) Stop() {
   177  	t.mtx.Lock()
   178  	defer t.mtx.Unlock()
   179  
   180  	t.stop()
   181  }
   182  
   183  // Wait the duration again before firing.
   184  func (t *RepeatTimer) Reset() {
   185  	t.mtx.Lock()
   186  	defer t.mtx.Unlock()
   187  
   188  	t.reset()
   189  }
   190  
   191  //----------------------------------------
   192  // Misc.
   193  
   194  // CONTRACT: (non-constructor) caller should hold t.mtx.
   195  func (t *RepeatTimer) reset() {
   196  	if t.ticker != nil {
   197  		t.stop()
   198  	}
   199  	t.ticker = t.tm(t.dur)
   200  	t.quit = make(chan struct{})
   201  	go t.fireRoutine(t.ticker.Chan(), t.quit)
   202  }
   203  
   204  // CONTRACT: caller should hold t.mtx.
   205  func (t *RepeatTimer) stop() {
   206  	if t.ticker == nil {
   207  		/*
   208  			Similar to the case of closing channels twice:
   209  			https://groups.google.com/forum/#!topic/golang-nuts/rhxMiNmRAPk
   210  			Stopping a RepeatTimer twice implies that you do
   211  			not know whether you are done or not.
   212  			If you're calling stop on a stopped RepeatTimer,
   213  			you probably have race conditions.
   214  		*/
   215  		panic("Tried to stop a stopped RepeatTimer")
   216  	}
   217  	t.ticker.Stop()
   218  	t.ticker = nil
   219  	/*
   220  		From https://golang.org/pkg/time/#Ticker:
   221  		"Stop the ticker to release associated resources"
   222  		"After Stop, no more ticks will be sent"
   223  		So we shouldn't have to do the below.
   224  
   225  		select {
   226  		case <-t.ch:
   227  			// read off channel if there's anything there
   228  		default:
   229  		}
   230  	*/
   231  	close(t.quit)
   232  }