github.com/mailgun/holster/v4@v4.20.0/clock/frozen.go (about) 1 package clock 2 3 import ( 4 "sync" 5 "time" 6 7 "github.com/mailgun/holster/v4/errors" 8 ) 9 10 type frozenTime struct { 11 mu sync.Mutex 12 frozenAt time.Time 13 now time.Time 14 timers []*frozenTimer 15 waiter *waiter 16 } 17 18 type waiter struct { 19 count int 20 signalCh chan struct{} 21 } 22 23 func (ft *frozenTime) Now() time.Time { 24 ft.mu.Lock() 25 defer ft.mu.Unlock() 26 return ft.now 27 } 28 29 func (ft *frozenTime) Sleep(d time.Duration) { 30 <-ft.NewTimer(d).C() 31 } 32 33 func (ft *frozenTime) After(d time.Duration) <-chan time.Time { 34 return ft.NewTimer(d).C() 35 } 36 37 func (ft *frozenTime) NewTimer(d time.Duration) Timer { 38 return ft.AfterFunc(d, nil) 39 } 40 41 func (ft *frozenTime) AfterFunc(d time.Duration, f func()) Timer { 42 t := &frozenTimer{ 43 ft: ft, 44 when: ft.Now().Add(d), 45 f: f, 46 } 47 if f == nil { 48 t.c = make(chan time.Time, 1) 49 } 50 ft.startTimer(t) 51 return t 52 } 53 54 func (ft *frozenTime) advance(d time.Duration) { 55 ft.mu.Lock() 56 defer ft.mu.Unlock() 57 58 ft.now = ft.now.Add(d) 59 for t := ft.nextExpired(); t != nil; t = ft.nextExpired() { 60 // Send the timer expiration time to the timer channel if it is 61 // defined. But make sure not to block on the send if the channel is 62 // full. This behavior will make a ticker skip beats if it readers are 63 // not fast enough. 64 if t.c != nil { 65 select { 66 case t.c <- t.when: 67 default: 68 } 69 } 70 // If it is a ticking timer then schedule next tick, otherwise mark it 71 // as stopped. 72 if t.interval != 0 { 73 t.when = t.when.Add(t.interval) 74 t.stopped = false 75 ft.unlockedStartTimer(t) 76 } 77 // If a function is associated with the timer then call it, but make 78 // sure to release the lock for the time of call it is necessary 79 // because the lock is not re-entrant but the function may need to 80 // start another timer or ticker. 81 if t.f != nil { 82 func() { 83 ft.mu.Unlock() 84 defer ft.mu.Lock() 85 t.f() 86 }() 87 } 88 } 89 } 90 91 func (ft *frozenTime) stopTimer(t *frozenTimer) bool { 92 ft.mu.Lock() 93 defer ft.mu.Unlock() 94 95 if t.stopped { 96 return false 97 } 98 for i, curr := range ft.timers { 99 if curr != t { 100 continue 101 } 102 103 t.stopped = true 104 copy(ft.timers[i:], ft.timers[i+1:]) 105 lastIdx := len(ft.timers) - 1 106 ft.timers[lastIdx] = nil 107 ft.timers = ft.timers[:lastIdx] 108 return true 109 } 110 return false 111 } 112 113 func (ft *frozenTime) nextExpired() *frozenTimer { 114 if len(ft.timers) == 0 { 115 return nil 116 } 117 t := ft.timers[0] 118 if ft.now.Before(t.when) { 119 return nil 120 } 121 copy(ft.timers, ft.timers[1:]) 122 lastIdx := len(ft.timers) - 1 123 ft.timers[lastIdx] = nil 124 ft.timers = ft.timers[:lastIdx] 125 t.stopped = true 126 return t 127 } 128 129 func (ft *frozenTime) startTimer(t *frozenTimer) { 130 ft.mu.Lock() 131 defer ft.mu.Unlock() 132 133 ft.unlockedStartTimer(t) 134 135 if ft.waiter == nil { 136 return 137 } 138 if len(ft.timers) >= ft.waiter.count { 139 close(ft.waiter.signalCh) 140 } 141 } 142 143 func (ft *frozenTime) unlockedStartTimer(t *frozenTimer) { 144 pos := 0 145 for _, curr := range ft.timers { 146 if t.when.Before(curr.when) { 147 break 148 } 149 pos++ 150 } 151 ft.timers = append(ft.timers, nil) 152 copy(ft.timers[pos+1:], ft.timers[pos:]) 153 ft.timers[pos] = t 154 } 155 156 type frozenTimer struct { 157 ft *frozenTime 158 when time.Time 159 interval time.Duration 160 stopped bool 161 c chan time.Time 162 f func() 163 } 164 165 func (t *frozenTimer) C() <-chan time.Time { 166 return t.c 167 } 168 169 func (t *frozenTimer) Stop() bool { 170 return t.ft.stopTimer(t) 171 } 172 173 func (t *frozenTimer) Reset(d time.Duration) bool { 174 active := t.ft.stopTimer(t) 175 t.when = t.ft.Now().Add(d) 176 t.ft.startTimer(t) 177 return active 178 } 179 180 type frozenTicker struct { 181 t *frozenTimer 182 } 183 184 func (t *frozenTicker) C() <-chan time.Time { 185 return t.t.C() 186 } 187 188 func (t *frozenTicker) Stop() { 189 t.t.Stop() 190 } 191 192 func (ft *frozenTime) NewTicker(d time.Duration) Ticker { 193 if d <= 0 { 194 panic(errors.New("non-positive interval for NewTicker")) 195 } 196 t := &frozenTimer{ 197 ft: ft, 198 when: ft.Now().Add(d), 199 interval: d, 200 c: make(chan time.Time, 1), 201 } 202 ft.startTimer(t) 203 return &frozenTicker{t} 204 } 205 206 func (ft *frozenTime) Tick(d time.Duration) <-chan time.Time { 207 if d <= 0 { 208 return nil 209 } 210 return ft.NewTicker(d).C() 211 } 212 213 func (ft *frozenTime) Wait4Scheduled(count int, timeout time.Duration) bool { 214 ft.mu.Lock() 215 if len(ft.timers) >= count { 216 ft.mu.Unlock() 217 return true 218 } 219 if ft.waiter != nil { 220 panic("Concurrent call") 221 } 222 ft.waiter = &waiter{count, make(chan struct{})} 223 ft.mu.Unlock() 224 225 success := false 226 select { 227 case <-ft.waiter.signalCh: 228 success = true 229 case <-time.After(timeout): 230 } 231 ft.mu.Lock() 232 ft.waiter = nil 233 ft.mu.Unlock() 234 return success 235 }