github.com/GeniusesGroup/libgo@v0.0.0-20220929090155-5ff932cb408e/timer/timer-async.go (about) 1 /* For license and copyright information please see the LEGAL file in the code repository */ 2 3 package timer 4 5 import ( 6 "unsafe" 7 8 "github.com/GeniusesGroup/libgo/cpu" 9 "github.com/GeniusesGroup/libgo/protocol" 10 "github.com/GeniusesGroup/libgo/race" 11 "github.com/GeniusesGroup/libgo/scheduler" 12 "github.com/GeniusesGroup/libgo/time/monotonic" 13 ) 14 15 // NewAsync waits for the duration to elapse and then calls callback. 16 // If callback need blocking operation it must do its logic in new thread(goroutine). 17 // It returns a Timer that can be used to cancel the call using its Stop method. 18 func NewAsync(d protocol.Duration, callback protocol.TimerListener) (t *Async, err protocol.Error) { 19 var timer Async 20 timer.Init(callback) 21 err = timer.Start(d) 22 t = &timer 23 return 24 } 25 26 type Async struct { 27 // Timer wakes up at when, and then at when+period, ... (period > 0 only) 28 // when must be positive on an active timer. 29 when monotonic.Time 30 period protocol.Duration 31 32 // The status field holds one of the values in status file. 33 status status 34 35 // callback function that call when reach 36 // it is possible that callback will be called a little after the delay. 37 // **NOTE**: each time calling callback() in the timer goroutine, so callback must be 38 // a well-behaved function and not block. 39 callback protocol.TimerListener 40 41 timers *TimingHeap 42 } 43 44 // Init initialize the timer with given callback function or make the channel and send signal on it 45 // Be aware that given function must not be closure and must not block the caller. 46 func (t *Async) Init(callback protocol.TimerListener) { 47 if t.callback != nil { 48 panic("timer: Don't initialize a timer twice. Use Reset() method to change the timer.") 49 } 50 51 t.callback = callback 52 } 53 func (t *Async) Reinit() { 54 t.callback = nil 55 t.timers = nil 56 } 57 func (t *Async) Deinit() { 58 } 59 60 // Start adds the timer to the running cpu core timing. 61 // This should only be called with a newly created timer. 62 // That avoids the risk of changing the when field of a timer in some P's heap, 63 // which could cause the heap to become unsorted. 64 func (t *Async) Start(d protocol.Duration) (err protocol.Error) { 65 if t.callback == nil { 66 panic("timer: Timer must initialized before start") 67 } 68 if t.status != status_Unset { 69 panic("timer: start called with started timer") 70 } 71 if t.timers != nil { 72 panic("timer: timers already set in timer") 73 } 74 // when must be positive. A negative value will cause ts.runTimer to 75 // overflow during its delta calculation and never expire other runtime timers. 76 // Zero will cause checkTimers to fail to notice the timer. 77 if d < 1 { 78 panic("timer: timer must have positive duration.") 79 } 80 81 if race.DetectorEnabled { 82 race.Release(unsafe.Pointer(t)) 83 } 84 t.when = when(d) 85 t.status = status_Waiting 86 t.timers = &poolByCores[cpu.ActiveCoreID()] 87 t.timers.AddTimer(t) 88 return 89 } 90 91 // Stop deletes the timer t. We can't actually remove it from the timers heap. 92 // We can only mark it as deleted. It will be removed in due course by the timing whose heap it is on. 93 // Reports whether the timer was removed before it was run. 94 func (t *Async) Stop() bool { 95 if t.callback == nil { 96 panic("timer: Stop called on uninitialized Timer") 97 } 98 99 for { 100 var status = t.status.Load() 101 switch status { 102 case status_Waiting, status_ModifiedLater: 103 if t.status.CompareAndSwap(status, status_Modifying) { 104 // Must fetch t.timers before changing status, 105 // as ts.cleanTimers in another goroutine can clear t.timers of a status_Deleted timer. 106 var timers = t.timers 107 if !t.status.CompareAndSwap(status_Modifying, status_Deleted) { 108 badTimer() 109 } 110 timers.deletedTimers.Add(1) 111 // Timer was not yet run. 112 return true 113 } 114 case status_ModifiedEarlier: 115 if t.status.CompareAndSwap(status, status_Modifying) { 116 var timers = t.timers 117 if !t.status.CompareAndSwap(status_Modifying, status_Deleted) { 118 badTimer() 119 } 120 timers.deletedTimers.Add(1) 121 // Timer was not yet run. 122 return true 123 } 124 case status_Deleted, status_Removing, status_Removed: 125 // Timer was already run. 126 return false 127 case status_Running, status_Moving: 128 // The timer is being run or moved, by a different P. 129 // Wait for it to complete. 130 scheduler.Yield(scheduler.Thread_WaitReason_Preempted) 131 case status_Unset: 132 // Removing timer that was never added or 133 // has already been run. Also see issue 21874. 134 return false 135 case status_Modifying: 136 // Simultaneous calls to delete and modify. 137 // Wait for the other call to complete. 138 scheduler.Yield(scheduler.Thread_WaitReason_Preempted) 139 default: 140 badTimer() 141 } 142 } 143 } 144 145 // Modify modifies an existing timer. 146 // It's OK to call modify() on a newly allocated Timer. 147 // Reports whether the timer was modified before it was run. 148 func (t *Async) Modify(d protocol.Duration) (pending bool) { 149 // when must be positive. A negative value will cause ts.runTimer to 150 // overflow during its delta calculation and never expire other runtime timers. 151 // Zero will cause checkTimers to fail to notice the timer. 152 if d < 1 { 153 panic("timer: timer must have positive duration") 154 } 155 if t.callback == nil { 156 panic("timer: Timer must initialized before reset") 157 } 158 159 if race.DetectorEnabled { 160 race.Release(unsafe.Pointer(t)) 161 } 162 163 var wasRemoved = false 164 loop: 165 for { 166 var status = t.status.Load() 167 switch status { 168 case status_Waiting, status_ModifiedEarlier, status_ModifiedLater: 169 if t.status.CompareAndSwap(status, status_Modifying) { 170 pending = true // timer not yet run 171 break loop 172 } 173 case status_Unset, status_Removed: 174 // Timer was already run and t is no longer in a heap. 175 // Act like AddTimer. 176 if t.status.CompareAndSwap(status, status_Modifying) { 177 wasRemoved = true 178 pending = false // timer already run or stopped 179 break loop 180 } 181 case status_Deleted: 182 if t.status.CompareAndSwap(status, status_Modifying) { 183 t.timers.deletedTimers.Add(-1) 184 pending = false // timer already stopped 185 break loop 186 } 187 case status_Running, status_Removing, status_Moving: 188 // The timer is being run or moved, by a different P. 189 // Wait for it to complete. 190 scheduler.Yield(scheduler.Thread_WaitReason_Preempted) 191 case status_Modifying: 192 // Multiple simultaneous calls to modify. 193 // Wait for the other call to complete. 194 scheduler.Yield(scheduler.Thread_WaitReason_Preempted) 195 default: 196 badTimer() 197 } 198 } 199 200 var timerOldWhen = t.when 201 var timerNewWhen = when(d) 202 t.when = timerNewWhen 203 if t.period != 0 { 204 t.period = d 205 } 206 if wasRemoved { 207 t.timers = &poolByCores[cpu.ActiveCoreID()] 208 t.timers.AddTimer(t) 209 if !t.status.CompareAndSwap(status_Modifying, status_Waiting) { 210 badTimer() 211 } 212 } else { 213 var newStatus = status_ModifiedLater 214 if timerNewWhen < timerOldWhen { 215 newStatus = status_ModifiedEarlier 216 t.timers.updateTimerModifiedEarliest(timerNewWhen) 217 } 218 219 // Set the new status of the timer. 220 if !t.status.CompareAndSwap(status_Modifying, newStatus) { 221 badTimer() 222 } 223 } 224 225 return 226 } 227 228 // Tick will send a signal on the t.Signal() channel after each tick on initialized Timer. 229 // The period of the ticks is specified by the duration arguments. 230 // The ticker will adjust the time interval or drop ticks to make up for slow receivers. 231 // The durations must be greater than zero; if not, Tick() will panic. 232 // Stop the ticker to release associated resources. 233 func (t *Async) Tick(first, interval protocol.Duration) (err protocol.Error) { 234 if first < 1 || interval < 1 { 235 panic("timer: non-positive interval to tick. period must be non-negative,") 236 } 237 t.period = interval 238 t.Start(first) 239 return 240 }