simclock.go 4.99 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package mclock

import (
20
	"container/heap"
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
	"sync"
	"time"
)

// Simulated implements a virtual Clock for reproducible time-sensitive tests. It
// simulates a scheduler on a virtual timescale where actual processing takes zero time.
//
// The virtual clock doesn't advance on its own, call Run to advance it and execute timers.
// Since there is no way to influence the Go scheduler, testing timeout behaviour involving
// goroutines needs special care. A good way to test such timeouts is as follows: First
// perform the action that is supposed to time out. Ensure that the timer you want to test
// is created. Then run the clock until after the timeout. Finally observe the effect of
// the timeout using a channel or semaphore.
type Simulated struct {
	now       AbsTime
36
	scheduled simTimerHeap
37 38 39 40
	mu        sync.RWMutex
	cond      *sync.Cond
}

41
// simTimer implements ChanTimer on the virtual clock.
42
type simTimer struct {
43 44 45 46 47 48 49 50 51 52 53
	at    AbsTime
	index int // position in s.scheduled
	s     *Simulated
	do    func()
	ch    <-chan AbsTime
}

func (s *Simulated) init() {
	if s.cond == nil {
		s.cond = sync.NewCond(&s.mu)
	}
54 55 56 57 58 59 60
}

// Run moves the clock by the given duration, executing all timers before that duration.
func (s *Simulated) Run(d time.Duration) {
	s.mu.Lock()
	s.init()

61
	end := s.now.Add(d)
62
	var do []func()
63 64
	for len(s.scheduled) > 0 && s.scheduled[0].at <= end {
		ev := heap.Pop(&s.scheduled).(*simTimer)
65
		do = append(do, ev.do)
66 67
	}
	s.now = end
68 69 70 71 72
	s.mu.Unlock()

	for _, fn := range do {
		fn()
	}
73 74
}

75
// ActiveTimers returns the number of timers that haven't fired.
76 77 78 79 80 81 82
func (s *Simulated) ActiveTimers() int {
	s.mu.RLock()
	defer s.mu.RUnlock()

	return len(s.scheduled)
}

83
// WaitForTimers waits until the clock has at least n scheduled timers.
84 85 86 87 88 89 90 91 92 93
func (s *Simulated) WaitForTimers(n int) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.init()

	for len(s.scheduled) < n {
		s.cond.Wait()
	}
}

94
// Now returns the current virtual time.
95 96 97 98 99 100 101
func (s *Simulated) Now() AbsTime {
	s.mu.RLock()
	defer s.mu.RUnlock()

	return s.now
}

102
// Sleep blocks until the clock has advanced by d.
103 104 105 106
func (s *Simulated) Sleep(d time.Duration) {
	<-s.After(d)
}

107 108 109 110 111 112 113 114 115 116 117 118
// NewTimer creates a timer which fires when the clock has advanced by d.
func (s *Simulated) NewTimer(d time.Duration) ChanTimer {
	s.mu.Lock()
	defer s.mu.Unlock()

	ch := make(chan AbsTime, 1)
	var timer *simTimer
	timer = s.schedule(d, func() { ch <- timer.at })
	timer.ch = ch
	return timer
}

119 120
// After returns a channel which receives the current time after the clock
// has advanced by d.
121 122
func (s *Simulated) After(d time.Duration) <-chan AbsTime {
	return s.NewTimer(d).C()
123 124
}

125 126 127
// AfterFunc runs fn after the clock has advanced by d. Unlike with the system
// clock, fn runs on the goroutine that calls Run.
func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer {
128 129
	s.mu.Lock()
	defer s.mu.Unlock()
130 131 132 133 134

	return s.schedule(d, fn)
}

func (s *Simulated) schedule(d time.Duration, fn func()) *simTimer {
135 136
	s.init()

137
	at := s.now.Add(d)
138
	ev := &simTimer{do: fn, at: at, s: s}
139
	heap.Push(&s.scheduled, ev)
140
	s.cond.Broadcast()
141
	return ev
142
}
143

144
func (ev *simTimer) Stop() bool {
145 146
	ev.s.mu.Lock()
	defer ev.s.mu.Unlock()
147

148 149
	if ev.index < 0 {
		return false
150
	}
151 152 153 154
	heap.Remove(&ev.s.scheduled, ev.index)
	ev.s.cond.Broadcast()
	ev.index = -1
	return true
155 156
}

157 158 159
func (ev *simTimer) Reset(d time.Duration) {
	if ev.ch == nil {
		panic("mclock: Reset() on timer created by AfterFunc")
160
	}
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208

	ev.s.mu.Lock()
	defer ev.s.mu.Unlock()
	ev.at = ev.s.now.Add(d)
	if ev.index < 0 {
		heap.Push(&ev.s.scheduled, ev) // already expired
	} else {
		heap.Fix(&ev.s.scheduled, ev.index) // hasn't fired yet, reschedule
	}
	ev.s.cond.Broadcast()
}

func (ev *simTimer) C() <-chan AbsTime {
	if ev.ch == nil {
		panic("mclock: C() on timer created by AfterFunc")
	}
	return ev.ch
}

type simTimerHeap []*simTimer

func (h *simTimerHeap) Len() int {
	return len(*h)
}

func (h *simTimerHeap) Less(i, j int) bool {
	return (*h)[i].at < (*h)[j].at
}

func (h *simTimerHeap) Swap(i, j int) {
	(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
	(*h)[i].index = i
	(*h)[j].index = j
}

func (h *simTimerHeap) Push(x interface{}) {
	t := x.(*simTimer)
	t.index = len(*h)
	*h = append(*h, t)
}

func (h *simTimerHeap) Pop() interface{} {
	end := len(*h) - 1
	t := (*h)[end]
	t.index = -1
	(*h)[end] = nil
	*h = (*h)[:end]
	return t
209
}