mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-30 07:59:40 -08:00
162 lines
3.9 KiB
Go
162 lines
3.9 KiB
Go
|
package clockwork
|
||
|
|
||
|
import (
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// Clock provides an interface that packages can use instead of directly
|
||
|
// using the time module, so that chronology-related behavior can be tested
|
||
|
type Clock interface {
|
||
|
After(d time.Duration) <-chan time.Time
|
||
|
Sleep(d time.Duration)
|
||
|
Now() time.Time
|
||
|
}
|
||
|
|
||
|
// FakeClock provides an interface for a clock which can be
|
||
|
// manually advanced through time
|
||
|
type FakeClock interface {
|
||
|
Clock
|
||
|
// Advance advances the FakeClock to a new point in time, ensuring any existing
|
||
|
// sleepers are notified appropriately before returning
|
||
|
Advance(d time.Duration)
|
||
|
// BlockUntil will block until the FakeClock has the given number of
|
||
|
// sleepers (callers of Sleep or After)
|
||
|
BlockUntil(n int)
|
||
|
}
|
||
|
|
||
|
// NewRealClock returns a Clock which simply delegates calls to the actual time
|
||
|
// package; it should be used by packages in production.
|
||
|
func NewRealClock() Clock {
|
||
|
return &realClock{}
|
||
|
}
|
||
|
|
||
|
// NewFakeClock returns a FakeClock implementation which can be
|
||
|
// manually advanced through time for testing.
|
||
|
func NewFakeClock() FakeClock {
|
||
|
return &fakeClock{
|
||
|
l: sync.RWMutex{},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type realClock struct{}
|
||
|
|
||
|
func (rc *realClock) After(d time.Duration) <-chan time.Time {
|
||
|
return time.After(d)
|
||
|
}
|
||
|
|
||
|
func (rc *realClock) Sleep(d time.Duration) {
|
||
|
time.Sleep(d)
|
||
|
}
|
||
|
|
||
|
func (rc *realClock) Now() time.Time {
|
||
|
return time.Now()
|
||
|
}
|
||
|
|
||
|
type fakeClock struct {
|
||
|
sleepers []*sleeper
|
||
|
blockers []*blocker
|
||
|
time time.Time
|
||
|
|
||
|
l sync.RWMutex
|
||
|
}
|
||
|
|
||
|
// sleeper represents a caller of After or Sleep
|
||
|
type sleeper struct {
|
||
|
until time.Time
|
||
|
done chan time.Time
|
||
|
}
|
||
|
|
||
|
// blocker represents a caller of BlockUntil
|
||
|
type blocker struct {
|
||
|
count int
|
||
|
ch chan struct{}
|
||
|
}
|
||
|
|
||
|
// After mimics time.After; it waits for the given duration to elapse on the
|
||
|
// fakeClock, then sends the current time on the returned channel.
|
||
|
func (fc *fakeClock) After(d time.Duration) <-chan time.Time {
|
||
|
fc.l.Lock()
|
||
|
defer fc.l.Unlock()
|
||
|
now := fc.time
|
||
|
done := make(chan time.Time, 1)
|
||
|
if d.Nanoseconds() == 0 {
|
||
|
// special case - trigger immediately
|
||
|
done <- now
|
||
|
} else {
|
||
|
// otherwise, add to the set of sleepers
|
||
|
s := &sleeper{
|
||
|
until: now.Add(d),
|
||
|
done: done,
|
||
|
}
|
||
|
fc.sleepers = append(fc.sleepers, s)
|
||
|
// and notify any blockers
|
||
|
fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
|
||
|
}
|
||
|
return done
|
||
|
}
|
||
|
|
||
|
// notifyBlockers notifies all the blockers waiting until the
|
||
|
// given number of sleepers are waiting on the fakeClock. It
|
||
|
// returns an updated slice of blockers (i.e. those still waiting)
|
||
|
func notifyBlockers(blockers []*blocker, count int) (newBlockers []*blocker) {
|
||
|
for _, b := range blockers {
|
||
|
if b.count == count {
|
||
|
close(b.ch)
|
||
|
} else {
|
||
|
newBlockers = append(newBlockers, b)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Sleep blocks until the given duration has passed on the fakeClock
|
||
|
func (fc *fakeClock) Sleep(d time.Duration) {
|
||
|
<-fc.After(d)
|
||
|
}
|
||
|
|
||
|
// Time returns the current time of the fakeClock
|
||
|
func (fc *fakeClock) Now() time.Time {
|
||
|
fc.l.Lock()
|
||
|
defer fc.l.Unlock()
|
||
|
return fc.time
|
||
|
}
|
||
|
|
||
|
// Advance advances fakeClock to a new point in time, ensuring channels from any
|
||
|
// previous invocations of After are notified appropriately before returning
|
||
|
func (fc *fakeClock) Advance(d time.Duration) {
|
||
|
fc.l.Lock()
|
||
|
defer fc.l.Unlock()
|
||
|
end := fc.time.Add(d)
|
||
|
var newSleepers []*sleeper
|
||
|
for _, s := range fc.sleepers {
|
||
|
if end.Sub(s.until) >= 0 {
|
||
|
s.done <- end
|
||
|
} else {
|
||
|
newSleepers = append(newSleepers, s)
|
||
|
}
|
||
|
}
|
||
|
fc.sleepers = newSleepers
|
||
|
fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
|
||
|
fc.time = end
|
||
|
}
|
||
|
|
||
|
// BlockUntil will block until the fakeClock has the given number of sleepers
|
||
|
// (callers of Sleep or After)
|
||
|
func (fc *fakeClock) BlockUntil(n int) {
|
||
|
fc.l.Lock()
|
||
|
// Fast path: current number of sleepers is what we're looking for
|
||
|
if len(fc.sleepers) == n {
|
||
|
fc.l.Unlock()
|
||
|
return
|
||
|
}
|
||
|
// Otherwise, set up a new blocker
|
||
|
b := &blocker{
|
||
|
count: n,
|
||
|
ch: make(chan struct{}),
|
||
|
}
|
||
|
fc.blockers = append(fc.blockers, b)
|
||
|
fc.l.Unlock()
|
||
|
<-b.ch
|
||
|
}
|