github.com/googgoog/go-ethereum@v1.9.7/common/mclock/simclock.go (about)

     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  
    17  package mclock
    18  
    19  import (
    20  	"sync"
    21  	"time"
    22  )
    23  
    24  // Simulated implements a virtual Clock for reproducible time-sensitive tests. It
    25  // simulates a scheduler on a virtual timescale where actual processing takes zero time.
    26  //
    27  // The virtual clock doesn't advance on its own, call Run to advance it and execute timers.
    28  // Since there is no way to influence the Go scheduler, testing timeout behaviour involving
    29  // goroutines needs special care. A good way to test such timeouts is as follows: First
    30  // perform the action that is supposed to time out. Ensure that the timer you want to test
    31  // is created. Then run the clock until after the timeout. Finally observe the effect of
    32  // the timeout using a channel or semaphore.
    33  type Simulated struct {
    34  	now       AbsTime
    35  	scheduled []*simTimer
    36  	mu        sync.RWMutex
    37  	cond      *sync.Cond
    38  	lastId    uint64
    39  }
    40  
    41  // simTimer implements Timer on the virtual clock.
    42  type simTimer struct {
    43  	do func()
    44  	at AbsTime
    45  	id uint64
    46  	s  *Simulated
    47  }
    48  
    49  // Run moves the clock by the given duration, executing all timers before that duration.
    50  func (s *Simulated) Run(d time.Duration) {
    51  	s.mu.Lock()
    52  	s.init()
    53  
    54  	end := s.now + AbsTime(d)
    55  	var do []func()
    56  	for len(s.scheduled) > 0 {
    57  		ev := s.scheduled[0]
    58  		if ev.at > end {
    59  			break
    60  		}
    61  		s.now = ev.at
    62  		do = append(do, ev.do)
    63  		s.scheduled = s.scheduled[1:]
    64  	}
    65  	s.now = end
    66  	s.mu.Unlock()
    67  
    68  	for _, fn := range do {
    69  		fn()
    70  	}
    71  }
    72  
    73  // ActiveTimers returns the number of timers that haven't fired.
    74  func (s *Simulated) ActiveTimers() int {
    75  	s.mu.RLock()
    76  	defer s.mu.RUnlock()
    77  
    78  	return len(s.scheduled)
    79  }
    80  
    81  // WaitForTimers waits until the clock has at least n scheduled timers.
    82  func (s *Simulated) WaitForTimers(n int) {
    83  	s.mu.Lock()
    84  	defer s.mu.Unlock()
    85  	s.init()
    86  
    87  	for len(s.scheduled) < n {
    88  		s.cond.Wait()
    89  	}
    90  }
    91  
    92  // Now returns the current virtual time.
    93  func (s *Simulated) Now() AbsTime {
    94  	s.mu.RLock()
    95  	defer s.mu.RUnlock()
    96  
    97  	return s.now
    98  }
    99  
   100  // Sleep blocks until the clock has advanced by d.
   101  func (s *Simulated) Sleep(d time.Duration) {
   102  	<-s.After(d)
   103  }
   104  
   105  // After returns a channel which receives the current time after the clock
   106  // has advanced by d.
   107  func (s *Simulated) After(d time.Duration) <-chan time.Time {
   108  	after := make(chan time.Time, 1)
   109  	s.AfterFunc(d, func() {
   110  		after <- (time.Time{}).Add(time.Duration(s.now))
   111  	})
   112  	return after
   113  }
   114  
   115  // AfterFunc runs fn after the clock has advanced by d. Unlike with the system
   116  // clock, fn runs on the goroutine that calls Run.
   117  func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer {
   118  	s.mu.Lock()
   119  	defer s.mu.Unlock()
   120  	s.init()
   121  
   122  	at := s.now + AbsTime(d)
   123  	s.lastId++
   124  	id := s.lastId
   125  	l, h := 0, len(s.scheduled)
   126  	ll := h
   127  	for l != h {
   128  		m := (l + h) / 2
   129  		if (at < s.scheduled[m].at) || ((at == s.scheduled[m].at) && (id < s.scheduled[m].id)) {
   130  			h = m
   131  		} else {
   132  			l = m + 1
   133  		}
   134  	}
   135  	ev := &simTimer{do: fn, at: at, s: s}
   136  	s.scheduled = append(s.scheduled, nil)
   137  	copy(s.scheduled[l+1:], s.scheduled[l:ll])
   138  	s.scheduled[l] = ev
   139  	s.cond.Broadcast()
   140  	return ev
   141  }
   142  
   143  func (ev *simTimer) Stop() bool {
   144  	s := ev.s
   145  	s.mu.Lock()
   146  	defer s.mu.Unlock()
   147  
   148  	for i := 0; i < len(s.scheduled); i++ {
   149  		if s.scheduled[i] == ev {
   150  			s.scheduled = append(s.scheduled[:i], s.scheduled[i+1:]...)
   151  			s.cond.Broadcast()
   152  			return true
   153  		}
   154  	}
   155  	return false
   156  }
   157  
   158  func (s *Simulated) init() {
   159  	if s.cond == nil {
   160  		s.cond = sync.NewCond(&s.mu)
   161  	}
   162  }