simclock.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // Copyright 2018 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package mclock
  17. import (
  18. "sync"
  19. "time"
  20. )
  21. // Simulated implements a virtual Clock for reproducible time-sensitive tests. It
  22. // simulates a scheduler on a virtual timescale where actual processing takes zero time.
  23. //
  24. // The virtual clock doesn't advance on its own, call Run to advance it and execute timers.
  25. // Since there is no way to influence the Go scheduler, testing timeout behaviour involving
  26. // goroutines needs special care. A good way to test such timeouts is as follows: First
  27. // perform the action that is supposed to time out. Ensure that the timer you want to test
  28. // is created. Then run the clock until after the timeout. Finally observe the effect of
  29. // the timeout using a channel or semaphore.
  30. type Simulated struct {
  31. now AbsTime
  32. scheduled []*simTimer
  33. mu sync.RWMutex
  34. cond *sync.Cond
  35. lastId uint64
  36. }
  37. // simTimer implements Timer on the virtual clock.
  38. type simTimer struct {
  39. do func()
  40. at AbsTime
  41. id uint64
  42. s *Simulated
  43. }
  44. // Run moves the clock by the given duration, executing all timers before that duration.
  45. func (s *Simulated) Run(d time.Duration) {
  46. s.mu.Lock()
  47. s.init()
  48. end := s.now + AbsTime(d)
  49. var do []func()
  50. for len(s.scheduled) > 0 {
  51. ev := s.scheduled[0]
  52. if ev.at > end {
  53. break
  54. }
  55. s.now = ev.at
  56. do = append(do, ev.do)
  57. s.scheduled = s.scheduled[1:]
  58. }
  59. s.now = end
  60. s.mu.Unlock()
  61. for _, fn := range do {
  62. fn()
  63. }
  64. }
  65. // ActiveTimers returns the number of timers that haven't fired.
  66. func (s *Simulated) ActiveTimers() int {
  67. s.mu.RLock()
  68. defer s.mu.RUnlock()
  69. return len(s.scheduled)
  70. }
  71. // WaitForTimers waits until the clock has at least n scheduled timers.
  72. func (s *Simulated) WaitForTimers(n int) {
  73. s.mu.Lock()
  74. defer s.mu.Unlock()
  75. s.init()
  76. for len(s.scheduled) < n {
  77. s.cond.Wait()
  78. }
  79. }
  80. // Now returns the current virtual time.
  81. func (s *Simulated) Now() AbsTime {
  82. s.mu.RLock()
  83. defer s.mu.RUnlock()
  84. return s.now
  85. }
  86. // Sleep blocks until the clock has advanced by d.
  87. func (s *Simulated) Sleep(d time.Duration) {
  88. <-s.After(d)
  89. }
  90. // After returns a channel which receives the current time after the clock
  91. // has advanced by d.
  92. func (s *Simulated) After(d time.Duration) <-chan time.Time {
  93. after := make(chan time.Time, 1)
  94. s.AfterFunc(d, func() {
  95. after <- (time.Time{}).Add(time.Duration(s.now))
  96. })
  97. return after
  98. }
  99. // AfterFunc runs fn after the clock has advanced by d. Unlike with the system
  100. // clock, fn runs on the goroutine that calls Run.
  101. func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer {
  102. s.mu.Lock()
  103. defer s.mu.Unlock()
  104. s.init()
  105. at := s.now + AbsTime(d)
  106. s.lastId++
  107. id := s.lastId
  108. l, h := 0, len(s.scheduled)
  109. ll := h
  110. for l != h {
  111. m := (l + h) / 2
  112. if (at < s.scheduled[m].at) || ((at == s.scheduled[m].at) && (id < s.scheduled[m].id)) {
  113. h = m
  114. } else {
  115. l = m + 1
  116. }
  117. }
  118. ev := &simTimer{do: fn, at: at, s: s}
  119. s.scheduled = append(s.scheduled, nil)
  120. copy(s.scheduled[l+1:], s.scheduled[l:ll])
  121. s.scheduled[l] = ev
  122. s.cond.Broadcast()
  123. return ev
  124. }
  125. func (ev *simTimer) Stop() bool {
  126. s := ev.s
  127. s.mu.Lock()
  128. defer s.mu.Unlock()
  129. for i := 0; i < len(s.scheduled); i++ {
  130. if s.scheduled[i] == ev {
  131. s.scheduled = append(s.scheduled[:i], s.scheduled[i+1:]...)
  132. s.cond.Broadcast()
  133. return true
  134. }
  135. }
  136. return false
  137. }
  138. func (s *Simulated) init() {
  139. if s.cond == nil {
  140. s.cond = sync.NewCond(&s.mu)
  141. }
  142. }