github.com/core-coin/go-core/v2@v2.1.9/common/mclock/simclock.go (about)

     1  // Copyright 2018 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package mclock
    18  
    19  import (
    20  	"container/heap"
    21  	"sync"
    22  	"time"
    23  )
    24  
    25  // Simulated implements a virtual Clock for reproducible time-sensitive tests. It
    26  // simulates a scheduler on a virtual timescale where actual processing takes zero time.
    27  //
    28  // The virtual clock doesn't advance on its own, call Run to advance it and execute timers.
    29  // Since there is no way to influence the Go scheduler, testing timeout behaviour involving
    30  // goroutines needs special care. A good way to test such timeouts is as follows: First
    31  // perform the action that is supposed to time out. Ensure that the timer you want to test
    32  // is created. Then run the clock until after the timeout. Finally observe the effect of
    33  // the timeout using a channel or semaphore.
    34  type Simulated struct {
    35  	now       AbsTime
    36  	scheduled simTimerHeap
    37  	mu        sync.RWMutex
    38  	cond      *sync.Cond
    39  }
    40  
    41  // simTimer implements ChanTimer on the virtual clock.
    42  type simTimer struct {
    43  	at    AbsTime
    44  	index int // position in s.scheduled
    45  	s     *Simulated
    46  	do    func()
    47  	ch    <-chan AbsTime
    48  }
    49  
    50  func (s *Simulated) init() {
    51  	if s.cond == nil {
    52  		s.cond = sync.NewCond(&s.mu)
    53  	}
    54  }
    55  
    56  // Run moves the clock by the given duration, executing all timers before that duration.
    57  func (s *Simulated) Run(d time.Duration) {
    58  	s.mu.Lock()
    59  	s.init()
    60  
    61  	end := s.now + AbsTime(d)
    62  	var do []func()
    63  	for len(s.scheduled) > 0 && s.scheduled[0].at <= end {
    64  		ev := heap.Pop(&s.scheduled).(*simTimer)
    65  		do = append(do, ev.do)
    66  	}
    67  	s.now = end
    68  	s.mu.Unlock()
    69  
    70  	for _, fn := range do {
    71  		fn()
    72  	}
    73  }
    74  
    75  // ActiveTimers returns the number of timers that haven't fired.
    76  func (s *Simulated) ActiveTimers() int {
    77  	s.mu.RLock()
    78  	defer s.mu.RUnlock()
    79  
    80  	return len(s.scheduled)
    81  }
    82  
    83  // WaitForTimers waits until the clock has at least n scheduled timers.
    84  func (s *Simulated) WaitForTimers(n int) {
    85  	s.mu.Lock()
    86  	defer s.mu.Unlock()
    87  	s.init()
    88  
    89  	for len(s.scheduled) < n {
    90  		s.cond.Wait()
    91  	}
    92  }
    93  
    94  // Now returns the current virtual time.
    95  func (s *Simulated) Now() AbsTime {
    96  	s.mu.RLock()
    97  	defer s.mu.RUnlock()
    98  
    99  	return s.now
   100  }
   101  
   102  // Sleep blocks until the clock has advanced by d.
   103  func (s *Simulated) Sleep(d time.Duration) {
   104  	<-s.After(d)
   105  }
   106  
   107  // NewTimer creates a timer which fires when the clock has advanced by d.
   108  func (s *Simulated) NewTimer(d time.Duration) ChanTimer {
   109  	s.mu.Lock()
   110  	defer s.mu.Unlock()
   111  
   112  	ch := make(chan AbsTime, 1)
   113  	var timer *simTimer
   114  	timer = s.schedule(d, func() { ch <- timer.at })
   115  	timer.ch = ch
   116  	return timer
   117  }
   118  
   119  // After returns a channel which receives the current time after the clock
   120  // has advanced by d.
   121  func (s *Simulated) After(d time.Duration) <-chan AbsTime {
   122  	return s.NewTimer(d).C()
   123  }
   124  
   125  // AfterFunc runs fn after the clock has advanced by d. Unlike with the system
   126  // clock, fn runs on the goroutine that calls Run.
   127  func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer {
   128  	s.mu.Lock()
   129  	defer s.mu.Unlock()
   130  
   131  	return s.schedule(d, fn)
   132  }
   133  
   134  func (s *Simulated) schedule(d time.Duration, fn func()) *simTimer {
   135  	s.init()
   136  
   137  	at := s.now + AbsTime(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  	ev.s.mu.Lock()
   146  	defer ev.s.mu.Unlock()
   147  
   148  	if ev.index < 0 {
   149  		return false
   150  	}
   151  	heap.Remove(&ev.s.scheduled, ev.index)
   152  	ev.s.cond.Broadcast()
   153  	ev.index = -1
   154  	return true
   155  }
   156  
   157  func (ev *simTimer) Reset(d time.Duration) {
   158  	if ev.ch == nil {
   159  		panic("mclock: Reset() on timer created by AfterFunc")
   160  	}
   161  
   162  	ev.s.mu.Lock()
   163  	defer ev.s.mu.Unlock()
   164  	ev.at = ev.s.now.Add(d)
   165  	if ev.index < 0 {
   166  		heap.Push(&ev.s.scheduled, ev) // already expired
   167  	} else {
   168  		heap.Fix(&ev.s.scheduled, ev.index) // hasn't fired yet, reschedule
   169  	}
   170  	ev.s.cond.Broadcast()
   171  }
   172  
   173  func (ev *simTimer) C() <-chan AbsTime {
   174  	if ev.ch == nil {
   175  		panic("mclock: C() on timer created by AfterFunc")
   176  	}
   177  	return ev.ch
   178  }
   179  
   180  type simTimerHeap []*simTimer
   181  
   182  func (h *simTimerHeap) Len() int {
   183  	return len(*h)
   184  }
   185  
   186  func (h *simTimerHeap) Less(i, j int) bool {
   187  	return (*h)[i].at < (*h)[j].at
   188  }
   189  
   190  func (h *simTimerHeap) Swap(i, j int) {
   191  	(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
   192  	(*h)[i].index = i
   193  	(*h)[j].index = j
   194  }
   195  
   196  func (h *simTimerHeap) Push(x interface{}) {
   197  	t := x.(*simTimer)
   198  	t.index = len(*h)
   199  	*h = append(*h, t)
   200  }
   201  
   202  func (h *simTimerHeap) Pop() interface{} {
   203  	end := len(*h) - 1
   204  	t := (*h)[end]
   205  	t.index = -1
   206  	(*h)[end] = nil
   207  	*h = (*h)[:end]
   208  	return t
   209  }