go.mway.dev/chrono@v0.6.1-0.20240126030049-189c5aef20d2/clock/throttled_clock.go (about) 1 // Copyright (c) 2023 Matt Way 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to 5 // deal in the Software without restriction, including without limitation the 6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 // sell copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 // IN THE THE SOFTWARE. 20 21 package clock 22 23 import ( 24 "sync" 25 "time" 26 27 "go.uber.org/atomic" 28 ) 29 30 var _ Clock = (*ThrottledClock)(nil) 31 32 // DefaultWallNanotimeFunc returns a new, default [NanotimeFunc] that reports wall 33 // time as nanoseconds. 34 func DefaultWallNanotimeFunc() NanotimeFunc { 35 return func() int64 { 36 return time.Now().UnixNano() 37 } 38 } 39 40 // ThrottledClock provides a simple interface to memoize repeated time syscalls 41 // within a given threshold. 42 type ThrottledClock struct { 43 nowfn NanotimeFunc 44 done chan struct{} 45 now atomic.Int64 46 stopped atomic.Bool 47 interval time.Duration 48 wg sync.WaitGroup 49 } 50 51 // NewThrottledClock creates a new ThrottledClock that uses the given NanoFunc 52 // to update its internal time at the given interval. A ThrottledClock should 53 // be stopped via ThrottledClock.Stop once it is no longer used. 54 // 55 // Note that interval should be tuned to be greater than the actual frequency 56 // of calls to ThrottledClock.Nanos or ThrottledClock.Now (otherwise the clock 57 // will generate more time calls than it is saving). 58 func NewThrottledClock( 59 nowfn NanotimeFunc, 60 interval time.Duration, 61 ) *ThrottledClock { 62 c := &ThrottledClock{ 63 nowfn: nowfn, 64 done: make(chan struct{}), 65 interval: interval, 66 } 67 68 // Set the clock to an initial time value. 69 c.now.Store(c.nowfn()) 70 71 c.wg.Add(1) 72 go func() { 73 defer c.wg.Done() 74 c.run(interval) 75 }() 76 77 return c 78 } 79 80 // NewThrottledMonotonicClock creates a new ThrottledClock that uses 81 // NewMonotonicNanoFunc as its backing time function. See NewThrottledClock for 82 // more information. 83 func NewThrottledMonotonicClock(interval time.Duration) *ThrottledClock { 84 return NewThrottledClock(DefaultNanotimeFunc(), interval) 85 } 86 87 // NewThrottledWallClock creates a new ThrottledClock that uses NewWallNanoFunc 88 // as its backing time function. See NewThrottledClock for more information. 89 func NewThrottledWallClock(interval time.Duration) *ThrottledClock { 90 return NewThrottledClock(DefaultWallNanotimeFunc(), interval) 91 } 92 93 // After returns a channel that receives the current time after d has elapsed. 94 // This method is not throttled and uses Go's runtime timers. 95 func (c *ThrottledClock) After(d time.Duration) <-chan time.Time { 96 return time.After(d) 97 } 98 99 // AfterFunc returns a timer that will invoke the given function after d has 100 // elapsed. The timer may be stopped and reset. This method is not throttled 101 // and uses Go's runtime timers. 102 func (c *ThrottledClock) AfterFunc(d time.Duration, fn func()) *Timer { 103 x := time.AfterFunc(d, fn) 104 return &Timer{ 105 C: x.C, 106 timer: x, 107 } 108 } 109 110 // Interval returns the interval at which the clock updates its internal time. 111 func (c *ThrottledClock) Interval() time.Duration { 112 return c.interval 113 } 114 115 // Nanotime returns the current time as integer nanoseconds. 116 func (c *ThrottledClock) Nanotime() int64 { 117 return c.now.Load() 118 } 119 120 // NewStopwatch returns a new Stopwatch that uses the current clock for 121 // measuring time. The clock's current time is used as the stopwatch's epoch. 122 func (c *ThrottledClock) NewStopwatch() *Stopwatch { 123 return newStopwatch(c) 124 } 125 126 // NewTicker returns a new Ticker that receives time ticks every d. This method 127 // is not throttled and uses Go's runtime timers. If d is not greater than 128 // zero, NewTicker will panic. 129 func (c *ThrottledClock) NewTicker(d time.Duration) *Ticker { 130 x := time.NewTicker(d) 131 return &Ticker{ 132 C: x.C, 133 ticker: x, 134 } 135 } 136 137 // NewTimer returns a new Timer that receives a time tick after d. This method 138 // is not throttled and uses Go's runtime timers. 139 func (c *ThrottledClock) NewTimer(d time.Duration) *Timer { 140 x := time.NewTimer(d) 141 return &Timer{ 142 C: x.C, 143 timer: x, 144 } 145 } 146 147 // Now returns the current time as time.Time. 148 func (c *ThrottledClock) Now() time.Time { 149 return time.Unix(0, c.now.Load()) 150 } 151 152 // Since returns the amount of time that elapsed between the clock's internal 153 // time and t. 154 func (c *ThrottledClock) Since(t time.Time) time.Duration { 155 return c.SinceNanotime(t.UnixNano()) 156 } 157 158 // SinceNanotime returns the amount of time that elapsed between the clock's 159 // internal time and ns. 160 func (c *ThrottledClock) SinceNanotime(ns int64) time.Duration { 161 return time.Duration(c.Nanotime() - ns) 162 } 163 164 // Sleep puts the current goroutine to sleep for d. This method is not 165 // throttled and uses Go's runtime timers. 166 func (c *ThrottledClock) Sleep(d time.Duration) { 167 time.Sleep(d) 168 } 169 170 // Stop stops the clock. Note that this has no effect on currently-running 171 // timers. 172 func (c *ThrottledClock) Stop() { 173 if c.stopped.CAS(false, true) { 174 close(c.done) 175 } 176 c.wg.Wait() 177 } 178 179 // Tick returns a new channel that receives time ticks every d. It is 180 // equivalent to writing c.NewTicker(d).C(). 181 func (c *ThrottledClock) Tick(d time.Duration) <-chan time.Time { 182 //nolint:staticcheck 183 return time.Tick(d) 184 } 185 186 func (c *ThrottledClock) run(interval time.Duration) { 187 ticker := time.NewTicker(interval) 188 defer ticker.Stop() 189 190 for { 191 select { 192 case <-c.done: 193 return 194 case <-ticker.C: 195 c.now.Store(c.nowfn()) 196 } 197 } 198 }