...

Source file src/github.com/dlclark/regexp2/fastclock.go

Documentation: github.com/dlclark/regexp2

     1  package regexp2
     2  
     3  import (
     4  	"sync"
     5  	"sync/atomic"
     6  	"time"
     7  )
     8  
     9  // fasttime holds a time value (ticks since clock initialization)
    10  type fasttime int64
    11  
    12  // fastclock provides a fast clock implementation.
    13  //
    14  // A background goroutine periodically stores the current time
    15  // into an atomic variable.
    16  //
    17  // A deadline can be quickly checked for expiration by comparing
    18  // its value to the clock stored in the atomic variable.
    19  //
    20  // The goroutine automatically stops once clockEnd is reached.
    21  // (clockEnd covers the largest deadline seen so far + some
    22  // extra time). This ensures that if regexp2 with timeouts
    23  // stops being used we will stop background work.
    24  type fastclock struct {
    25  	// instances of atomicTime must be at the start of the struct (or at least 64-bit aligned)
    26  	// otherwise 32-bit architectures will panic
    27  
    28  	current  atomicTime // Current time (approximate)
    29  	clockEnd atomicTime // When clock updater is supposed to stop (>= any existing deadline)
    30  
    31  	// current and clockEnd can be read via atomic loads.
    32  	// Reads and writes of other fields require mu to be held.
    33  	mu      sync.Mutex
    34  	start   time.Time // Time corresponding to fasttime(0)
    35  	running bool      // Is a clock updater running?
    36  }
    37  
    38  var fast fastclock
    39  
    40  // reached returns true if current time is at or past t.
    41  func (t fasttime) reached() bool {
    42  	return fast.current.read() >= t
    43  }
    44  
    45  // makeDeadline returns a time that is approximately time.Now().Add(d)
    46  func makeDeadline(d time.Duration) fasttime {
    47  	// Increase the deadline since the clock we are reading may be
    48  	// just about to tick forwards.
    49  	end := fast.current.read() + durationToTicks(d+clockPeriod)
    50  
    51  	// Start or extend clock if necessary.
    52  	if end > fast.clockEnd.read() {
    53  		extendClock(end)
    54  	}
    55  	return end
    56  }
    57  
    58  // extendClock ensures that clock is live and will run until at least end.
    59  func extendClock(end fasttime) {
    60  	fast.mu.Lock()
    61  	defer fast.mu.Unlock()
    62  
    63  	if fast.start.IsZero() {
    64  		fast.start = time.Now()
    65  	}
    66  
    67  	// Extend the running time to cover end as well as a bit of slop.
    68  	if shutdown := end + durationToTicks(time.Second); shutdown > fast.clockEnd.read() {
    69  		fast.clockEnd.write(shutdown)
    70  	}
    71  
    72  	// Start clock if necessary
    73  	if !fast.running {
    74  		fast.running = true
    75  		go runClock()
    76  	}
    77  }
    78  
    79  // stop the timeout clock in the background
    80  // should only used for unit tests to abandon the background goroutine
    81  func stopClock() {
    82  	fast.mu.Lock()
    83  	if fast.running {
    84  		fast.clockEnd.write(fasttime(0))
    85  	}
    86  	fast.mu.Unlock()
    87  
    88  	// pause until not running
    89  	// get and release the lock
    90  	isRunning := true
    91  	for isRunning {
    92  		time.Sleep(clockPeriod / 2)
    93  		fast.mu.Lock()
    94  		isRunning = fast.running
    95  		fast.mu.Unlock()
    96  	}
    97  }
    98  
    99  func durationToTicks(d time.Duration) fasttime {
   100  	// Downscale nanoseconds to approximately a millisecond so that we can avoid
   101  	// overflow even if the caller passes in math.MaxInt64.
   102  	return fasttime(d) >> 20
   103  }
   104  
   105  const DefaultClockPeriod = 100 * time.Millisecond
   106  
   107  // clockPeriod is the approximate interval between updates of approximateClock.
   108  var clockPeriod = DefaultClockPeriod
   109  
   110  func runClock() {
   111  	fast.mu.Lock()
   112  	defer fast.mu.Unlock()
   113  
   114  	for fast.current.read() <= fast.clockEnd.read() {
   115  		// Unlock while sleeping.
   116  		fast.mu.Unlock()
   117  		time.Sleep(clockPeriod)
   118  		fast.mu.Lock()
   119  
   120  		newTime := durationToTicks(time.Since(fast.start))
   121  		fast.current.write(newTime)
   122  	}
   123  	fast.running = false
   124  }
   125  
   126  type atomicTime struct{ v int64 } // Should change to atomic.Int64 when we can use go 1.19
   127  
   128  func (t *atomicTime) read() fasttime   { return fasttime(atomic.LoadInt64(&t.v)) }
   129  func (t *atomicTime) write(v fasttime) { atomic.StoreInt64(&t.v, int64(v)) }
   130  

View as plain text