dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

clockwork.go (8198B)


      1 package clockwork
      2 
      3 import (
      4 	"sync"
      5 	"sync/atomic"
      6 	"time"
      7 )
      8 
      9 // Clock provides an interface that packages can use instead of directly
     10 // using the time module, so that chronology-related behavior can be tested
     11 type Clock interface {
     12 	After(d time.Duration) <-chan time.Time
     13 	Sleep(d time.Duration)
     14 	Now() time.Time
     15 	Since(t time.Time) time.Duration
     16 	Until(t time.Time) time.Duration
     17 	NewTicker(d time.Duration) Ticker
     18 	NewTimer(d time.Duration) Timer
     19 	AfterFunc(d time.Duration, f func()) Timer
     20 	Location() *time.Location
     21 }
     22 
     23 // Timer provides an interface to a time.Timer which is testable.
     24 // See https://golang.org/pkg/time/#Timer for more details on how timers work.
     25 type Timer interface {
     26 	C() <-chan time.Time
     27 	Reset(d time.Duration) bool
     28 	Stop() bool
     29 	T() *time.Timer // underlying *time.Timer (nil when using a FakeClock)
     30 }
     31 
     32 func (rc *realClock) NewTimer(d time.Duration) Timer {
     33 	return &realTimer{time.NewTimer(d)}
     34 }
     35 func (rc *realClock) AfterFunc(d time.Duration, f func()) Timer {
     36 	return &realTimer{time.AfterFunc(d, f)}
     37 }
     38 
     39 type realTimer struct {
     40 	t *time.Timer
     41 }
     42 
     43 func (rt *realTimer) C() <-chan time.Time { return rt.t.C }
     44 func (rt *realTimer) T() *time.Timer      { return rt.t }
     45 func (rt *realTimer) Reset(d time.Duration) bool {
     46 	return rt.t.Reset(d)
     47 }
     48 func (rt *realTimer) Stop() bool {
     49 	return rt.t.Stop()
     50 }
     51 
     52 // FakeClock provides an interface for a clock which can be
     53 // manually advanced through time
     54 type FakeClock interface {
     55 	Clock
     56 	// Advance advances the FakeClock to a new point in time, ensuring any existing
     57 	// sleepers are notified appropriately before returning
     58 	Advance(d time.Duration)
     59 	// BlockUntil will block until the FakeClock has the given number of
     60 	// sleepers (callers of Sleep or After)
     61 	BlockUntil(n int)
     62 }
     63 
     64 // NewRealClock returns a Clock which simply delegates calls to the actual time
     65 // package; it should be used by packages in production.
     66 func NewRealClock() Clock {
     67 	return &realClock{}
     68 }
     69 
     70 // NewRealClockInLocation ...
     71 func NewRealClockInLocation(location *time.Location) Clock {
     72 	return &realClock{loc: location}
     73 }
     74 
     75 // NewFakeClock returns a FakeClock implementation which can be
     76 // manually advanced through time for testing. The initial time of the
     77 // FakeClock will be an arbitrary non-zero time.
     78 func NewFakeClock() FakeClock {
     79 	// use a fixture that does not fulfill Time.IsZero()
     80 	return NewFakeClockAt(time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC))
     81 }
     82 
     83 // NewFakeClockAt returns a FakeClock initialised at the given time.Time.
     84 func NewFakeClockAt(t time.Time) FakeClock {
     85 	return &fakeClock{
     86 		time: t,
     87 	}
     88 }
     89 
     90 type realClock struct {
     91 	loc *time.Location
     92 }
     93 
     94 func (rc *realClock) Location() *time.Location {
     95 	return time.Now().Location()
     96 }
     97 
     98 func (rc *realClock) After(d time.Duration) <-chan time.Time {
     99 	return time.After(d)
    100 }
    101 
    102 func (rc *realClock) Sleep(d time.Duration) {
    103 	time.Sleep(d)
    104 }
    105 
    106 func (rc *realClock) Now() time.Time {
    107 	if rc.loc != nil {
    108 		return time.Now().In(rc.loc)
    109 	}
    110 	return time.Now()
    111 }
    112 
    113 func (rc *realClock) Since(t time.Time) time.Duration {
    114 	return rc.Now().Sub(t)
    115 }
    116 
    117 func (rc *realClock) Until(t time.Time) time.Duration {
    118 	return t.Sub(rc.Now())
    119 }
    120 
    121 func (rc *realClock) NewTicker(d time.Duration) Ticker {
    122 	return &realTicker{time.NewTicker(d)}
    123 }
    124 
    125 type fakeClock struct {
    126 	sleepers []*sleeper
    127 	blockers []*blocker
    128 	time     time.Time
    129 
    130 	l sync.RWMutex
    131 }
    132 
    133 // sleeper represents a caller of After or Sleep
    134 // sleeper represents a waiting timer from NewTimer, Sleep, After, etc.
    135 type sleeper struct {
    136 	until    time.Time
    137 	done     uint32
    138 	callback func(interface{}, time.Time)
    139 	arg      interface{}
    140 	ch       chan time.Time
    141 	fc       *fakeClock // needed for Reset()
    142 }
    143 
    144 func (s *sleeper) awaken(now time.Time) {
    145 	if atomic.CompareAndSwapUint32(&s.done, 0, 1) {
    146 		s.callback(s.arg, now)
    147 	}
    148 }
    149 func (s *sleeper) C() <-chan time.Time { return s.ch }
    150 func (s *sleeper) T() *time.Timer      { return nil }
    151 func (s *sleeper) Reset(d time.Duration) bool {
    152 	active := s.Stop()
    153 	s.until = s.fc.Now().Add(d)
    154 	defer s.fc.addTimer(s)
    155 	defer atomic.StoreUint32(&s.done, 0)
    156 	return active
    157 }
    158 func (s *sleeper) Stop() bool {
    159 	stopped := atomic.CompareAndSwapUint32(&s.done, 0, 1)
    160 	if stopped {
    161 		// Expire the timer and notify blockers
    162 		s.until = s.fc.Now()
    163 		s.fc.Advance(0)
    164 	}
    165 	return stopped
    166 }
    167 
    168 // blocker represents a caller of BlockUntil
    169 type blocker struct {
    170 	count int
    171 	ch    chan struct{}
    172 }
    173 
    174 // After mimics time.After; it waits for the given duration to elapse on the
    175 // fakeClock, then sends the current time on the returned channel.
    176 func (fc *fakeClock) After(d time.Duration) <-chan time.Time {
    177 	return fc.NewTimer(d).C()
    178 }
    179 
    180 // NewTimer creates a new Timer that will send the current time on its channel
    181 // after the given duration elapses on the fake clock.
    182 func (fc *fakeClock) NewTimer(d time.Duration) Timer {
    183 	sendTime := func(c interface{}, now time.Time) {
    184 		c.(chan time.Time) <- now
    185 	}
    186 	done := make(chan time.Time, 1)
    187 	s := &sleeper{
    188 		fc:       fc,
    189 		until:    fc.time.Add(d),
    190 		callback: sendTime,
    191 		arg:      done,
    192 		ch:       done,
    193 	}
    194 	fc.addTimer(s)
    195 	return s
    196 }
    197 
    198 // AfterFunc waits for the duration to elapse on the fake clock and then calls f
    199 // in its own goroutine.
    200 // It returns a Timer that can be used to cancel the call using its Stop method.
    201 func (fc *fakeClock) AfterFunc(d time.Duration, f func()) Timer {
    202 	goFunc := func(fn interface{}, _ time.Time) {
    203 		go fn.(func())()
    204 	}
    205 	s := &sleeper{
    206 		fc:       fc,
    207 		until:    fc.time.Add(d),
    208 		callback: goFunc,
    209 		arg:      f,
    210 		// zero-valued ch, the same as it is in the `time` pkg
    211 	}
    212 	fc.addTimer(s)
    213 	return s
    214 }
    215 
    216 func (fc *fakeClock) addTimer(s *sleeper) {
    217 	fc.l.Lock()
    218 	defer fc.l.Unlock()
    219 	now := fc.time
    220 	if now.Sub(s.until) >= 0 {
    221 		// special case - trigger immediately
    222 		s.awaken(now)
    223 	} else {
    224 		// otherwise, add to the set of sleepers
    225 		fc.sleepers = append(fc.sleepers, s)
    226 		// and notify any blockers
    227 		fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
    228 	}
    229 }
    230 
    231 // notifyBlockers notifies all the blockers waiting until the
    232 // given number of sleepers are waiting on the fakeClock. It
    233 // returns an updated slice of blockers (i.e. those still waiting)
    234 func notifyBlockers(blockers []*blocker, count int) (newBlockers []*blocker) {
    235 	for _, b := range blockers {
    236 		if b.count == count {
    237 			close(b.ch)
    238 		} else {
    239 			newBlockers = append(newBlockers, b)
    240 		}
    241 	}
    242 	return
    243 }
    244 
    245 // Sleep blocks until the given duration has passed on the fakeClock
    246 func (fc *fakeClock) Sleep(d time.Duration) {
    247 	<-fc.After(d)
    248 }
    249 
    250 // Time returns the current time of the fakeClock
    251 func (fc *fakeClock) Now() time.Time {
    252 	fc.l.RLock()
    253 	t := fc.time
    254 	fc.l.RUnlock()
    255 	return t
    256 }
    257 
    258 // Since returns the duration that has passed since the given time on the fakeClock
    259 func (fc *fakeClock) Since(t time.Time) time.Duration {
    260 	return fc.Now().Sub(t)
    261 }
    262 
    263 // Until returns the duration until the given time on the fakeClock
    264 func (fc *fakeClock) Until(t time.Time) time.Duration {
    265 	return t.Sub(fc.Now())
    266 }
    267 
    268 func (fc *fakeClock) Location() *time.Location {
    269 	return fc.time.Location()
    270 }
    271 
    272 func (fc *fakeClock) NewTicker(d time.Duration) Ticker {
    273 	ft := &fakeTicker{
    274 		c:      make(chan time.Time, 1),
    275 		stop:   make(chan bool, 1),
    276 		clock:  fc,
    277 		period: d,
    278 	}
    279 	go ft.tick()
    280 	return ft
    281 }
    282 
    283 // Advance advances fakeClock to a new point in time, ensuring channels from any
    284 // previous invocations of After are notified appropriately before returning
    285 func (fc *fakeClock) Advance(d time.Duration) {
    286 	fc.l.Lock()
    287 	defer fc.l.Unlock()
    288 	end := fc.time.Add(d)
    289 	var newSleepers []*sleeper
    290 	for _, s := range fc.sleepers {
    291 		if end.Sub(s.until) >= 0 {
    292 			s.awaken(end)
    293 		} else {
    294 			newSleepers = append(newSleepers, s)
    295 		}
    296 	}
    297 	fc.sleepers = newSleepers
    298 	fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
    299 	fc.time = end
    300 }
    301 
    302 // BlockUntil will block until the fakeClock has the given number of sleepers
    303 // (callers of Sleep or After)
    304 func (fc *fakeClock) BlockUntil(n int) {
    305 	fc.l.Lock()
    306 	// Fast path: current number of sleepers is what we're looking for
    307 	if len(fc.sleepers) == n {
    308 		fc.l.Unlock()
    309 		return
    310 	}
    311 	// Otherwise, set up a new blocker
    312 	b := &blocker{
    313 		count: n,
    314 		ch:    make(chan struct{}),
    315 	}
    316 	fc.blockers = append(fc.blockers, b)
    317 	fc.l.Unlock()
    318 	<-b.ch
    319 }