github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/common/mclock/simclock.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar library is free software: you can redistribute it and/or modify
     6  //  it under the terms of the GNU Lesser General Public License as published by
     7  //  the Free Software Foundation, either version 3 of the License, or
     8  //  (at your option) any later version.
     9  //
    10  //  The go-aigar library is distributed in the hope that it will be useful,
    11  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  //  GNU Lesser General Public License for more details.
    14  //
    15  //  You should have received a copy of the GNU Lesser General Public License
    16  //  along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package mclock
    19  
    20  import (
    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 []*simTimer
    37  	mu        sync.RWMutex
    38  	cond      *sync.Cond
    39  	lastId    uint64
    40  }
    41  
    42  // simTimer implements Timer on the virtual clock.
    43  type simTimer struct {
    44  	do func()
    45  	at AbsTime
    46  	id uint64
    47  	s  *Simulated
    48  }
    49  
    50  // Run moves the clock by the given duration, executing all timers before that duration.
    51  func (s *Simulated) Run(d time.Duration) {
    52  	s.mu.Lock()
    53  	s.init()
    54  
    55  	end := s.now + AbsTime(d)
    56  	var do []func()
    57  	for len(s.scheduled) > 0 {
    58  		ev := s.scheduled[0]
    59  		if ev.at > end {
    60  			break
    61  		}
    62  		s.now = ev.at
    63  		do = append(do, ev.do)
    64  		s.scheduled = s.scheduled[1:]
    65  	}
    66  	s.now = end
    67  	s.mu.Unlock()
    68  
    69  	for _, fn := range do {
    70  		fn()
    71  	}
    72  }
    73  
    74  // ActiveTimers returns the number of timers that haven't fired.
    75  func (s *Simulated) ActiveTimers() int {
    76  	s.mu.RLock()
    77  	defer s.mu.RUnlock()
    78  
    79  	return len(s.scheduled)
    80  }
    81  
    82  // WaitForTimers waits until the clock has at least n scheduled timers.
    83  func (s *Simulated) WaitForTimers(n int) {
    84  	s.mu.Lock()
    85  	defer s.mu.Unlock()
    86  	s.init()
    87  
    88  	for len(s.scheduled) < n {
    89  		s.cond.Wait()
    90  	}
    91  }
    92  
    93  // Now returns the current virtual time.
    94  func (s *Simulated) Now() AbsTime {
    95  	s.mu.RLock()
    96  	defer s.mu.RUnlock()
    97  
    98  	return s.now
    99  }
   100  
   101  // Sleep blocks until the clock has advanced by d.
   102  func (s *Simulated) Sleep(d time.Duration) {
   103  	<-s.After(d)
   104  }
   105  
   106  // After returns a channel which receives the current time after the clock
   107  // has advanced by d.
   108  func (s *Simulated) After(d time.Duration) <-chan time.Time {
   109  	after := make(chan time.Time, 1)
   110  	s.AfterFunc(d, func() {
   111  		after <- (time.Time{}).Add(time.Duration(s.now))
   112  	})
   113  	return after
   114  }
   115  
   116  // AfterFunc runs fn after the clock has advanced by d. Unlike with the system
   117  // clock, fn runs on the goroutine that calls Run.
   118  func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer {
   119  	s.mu.Lock()
   120  	defer s.mu.Unlock()
   121  	s.init()
   122  
   123  	at := s.now + AbsTime(d)
   124  	s.lastId++
   125  	id := s.lastId
   126  	l, h := 0, len(s.scheduled)
   127  	ll := h
   128  	for l != h {
   129  		m := (l + h) / 2
   130  		if (at < s.scheduled[m].at) || ((at == s.scheduled[m].at) && (id < s.scheduled[m].id)) {
   131  			h = m
   132  		} else {
   133  			l = m + 1
   134  		}
   135  	}
   136  	ev := &simTimer{do: fn, at: at, s: s}
   137  	s.scheduled = append(s.scheduled, nil)
   138  	copy(s.scheduled[l+1:], s.scheduled[l:ll])
   139  	s.scheduled[l] = ev
   140  	s.cond.Broadcast()
   141  	return ev
   142  }
   143  
   144  func (ev *simTimer) Stop() bool {
   145  	s := ev.s
   146  	s.mu.Lock()
   147  	defer s.mu.Unlock()
   148  
   149  	for i := 0; i < len(s.scheduled); i++ {
   150  		if s.scheduled[i] == ev {
   151  			s.scheduled = append(s.scheduled[:i], s.scheduled[i+1:]...)
   152  			s.cond.Broadcast()
   153  			return true
   154  		}
   155  	}
   156  	return false
   157  }
   158  
   159  func (s *Simulated) init() {
   160  	if s.cond == nil {
   161  		s.cond = sync.NewCond(&s.mu)
   162  	}
   163  }