github.com/sandwich-go/boost@v1.3.29/xtime/dispatcher.go (about) 1 package xtime 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/sandwich-go/boost/internal/log" 10 "github.com/sandwich-go/boost/xpanic" 11 12 "github.com/sandwich-go/boost/xsync" 13 "github.com/sandwich-go/boost/xtime/cron" 14 ) 15 16 const ( 17 DefaultTimerDomain = "timer-domain-system" 18 ) 19 20 // Lifecycle manages the start and close lifecycle methods. 21 type Lifecycle interface { 22 Start() 23 Close() 24 } 25 26 // TimerDispatcher manages timer-related functionalities. 27 type TimerDispatcher interface { 28 AfterFunc(d time.Duration, cb func()) *SafeTimer 29 AfterFuncWithOwnershipTransfer(td time.Duration, cb func()) *DanglingTimer 30 CronFunc(cronExpr *cron.Expression, cb func()) *Cron 31 // TickFunc 注册tick回调,key防止cb重复注册 32 TickFunc(key interface{}, cb TickFunc) 33 34 RemoveAllTimer() 35 RemoveAllTimerInDomain(domain string) 36 AfterFuncInDomain(td time.Duration, cb func(), domain string) *SafeTimer 37 AfterFuncWithOwnershipTransferInDomain(td time.Duration, cb func(), domain string) *DanglingTimer 38 39 TimerNotify() <-chan Timer 40 } 41 42 type Dispatcher interface { 43 TimerDispatcher 44 Lifecycle 45 } 46 47 type TickerDispatcher interface { 48 TickerC() <-chan time.Time 49 TriggerTickFuncs(context.Context) 50 } 51 52 type TickFunc func(context.Context) 53 54 // dispatcher one dispatcher per goroutine (goroutine not safe) 55 type dispatcher struct { 56 ChanTimer chan Timer // one receiver, N senders 57 timerMutex sync.RWMutex 58 runningTimers sync.Map 59 queueLen int 60 stopChan chan struct{} 61 closeFlag xsync.AtomicInt32 62 cc *Options 63 ticker *time.Ticker 64 tickMutex sync.RWMutex 65 tickFuncs []*tickHandler 66 } 67 68 const ( 69 stateRunning = 0 70 stateClosed = 1 71 ) 72 73 type tickHandler struct { 74 key interface{} 75 cb TickFunc 76 } 77 78 // NewDispatcher 构造新的Dispatcher 79 // Note: 80 // - Dispatcher目前主要服务于pkg/skeleton,用于Actor类型对象的有序动作处理 81 // - Dispatcher通过TimerNotify将事件通知到外层,并不执行注册的的回调方法,需逻辑层接管事件通知触发回调逻辑 82 func NewDispatcher(l int, opts ...Option) Dispatcher { 83 d := new(dispatcher) 84 d.cc = NewOptions(opts...) 85 d.queueLen = l 86 d.closeFlag.Set(stateClosed) 87 d.Start() 88 return d 89 } 90 91 // Start for skeleton restart the dispatcher 92 func (d *dispatcher) Start() { 93 if d.closeFlag.CompareAndSwap(stateClosed, stateRunning) { 94 d.stopChan = make(chan struct{}) 95 d.ChanTimer = make(chan Timer, d.queueLen) 96 if d.cc.TickDuration > 0 { 97 d.ticker = time.NewTicker(d.cc.TickDuration) 98 d.cc.TickCount.Inc() 99 if d.cc.TickHostingMode { 100 go d.tickHosting() 101 } 102 } 103 } 104 } 105 106 func (d *dispatcher) tickHosting() { 107 for { 108 select { 109 case <-d.stopChan: 110 return 111 case <-d.ticker.C: 112 d.TriggerTickFuncs(context.Background()) 113 } 114 } 115 } 116 117 func (d *dispatcher) TickFunc(key interface{}, cb TickFunc) { 118 d.tickMutex.Lock() 119 defer d.tickMutex.Unlock() 120 for _, h := range d.tickFuncs { 121 if h.key == key { 122 log.Warn(fmt.Sprintf("multi tick func, key:%s", key)) 123 return 124 } 125 } 126 d.tickFuncs = append(d.tickFuncs, &tickHandler{ 127 key: key, 128 cb: cb, 129 }) 130 } 131 132 // TickerC 返回ticker 的 chan,用于外部协程接管ticker执行协程 133 // d := NewDispatcher( 134 // 135 // 64, 136 // WithTickDuration(time.Millisecond*50), 137 // WithTickHostingMode(false), 138 // ) 139 // d.Start() 140 // td := d.(TickerDispatcher) 141 // for { 142 // select { 143 // case <-td.TickerC(): 144 // td.TriggerTickFuncs(context.Background()) 145 // } 146 // } 147 func (d *dispatcher) TickerC() <-chan time.Time { 148 if d.ticker != nil { 149 if d.cc.TickHostingMode { 150 log.Warn("To indicate that the ticker is externally handled, you can set the TickHostingMode to false") 151 return nil 152 } 153 return d.ticker.C 154 } 155 return nil 156 } 157 158 func (d *dispatcher) TriggerTickFuncs(ctx context.Context) { 159 if d.closeFlag.Get() != stateRunning { 160 return 161 } 162 d.tickMutex.RLock() 163 defer d.tickMutex.RUnlock() 164 for _, h := range d.tickFuncs { 165 xpanic.Try(func() { 166 h.cb(ctx) 167 }).Catch(func(err xpanic.E) { 168 log.Error(fmt.Sprintf("panic in tick funcs, reason:%v", err)) 169 }) 170 } 171 } 172 173 func (d *dispatcher) TimerNotify() <-chan Timer { return d.ChanTimer } 174 func (d *dispatcher) Close() { 175 if d.closeFlag.CompareAndSwap(stateRunning, stateClosed) { 176 close(d.stopChan) 177 if d.ticker != nil { 178 d.ticker.Stop() 179 d.cc.TickCount.Dec() 180 } 181 // close #174 182 d.timerMutex.Lock() 183 close(d.ChanTimer) 184 d.timerMutex.Unlock() 185 186 //clear all timers 187 for t := range d.ChanTimer { 188 t.Cb() 189 } 190 d.RemoveAllTimer() 191 } 192 } 193 194 func (d *dispatcher) RemoveTimer(t Timer) { d.runningTimers.Delete(t) } 195 196 func (d *dispatcher) RemoveAllTimer() { 197 d.runningTimers.Range(func(key, value interface{}) bool { 198 t := key.(Timer) 199 t.stop() 200 d.RemoveTimer(t) 201 return true 202 }) 203 } 204 func (d *dispatcher) RemoveAllTimerInDomain(domain string) { 205 d.runningTimers.Range(func(key, value interface{}) bool { 206 t := key.(Timer) 207 if t.GetDomain() != domain { 208 return true 209 } 210 t.stop() 211 d.RemoveTimer(t) 212 return true 213 }) 214 } 215 216 // AfterFuncWithOwnershipTransfer 自己管理 *Timer 声明周期, 使用完毕后需要手动调用 xtime Stop() 方法释放资源, 以免内存泄露 217 // 可以使用 Reset() 方法重置定时器 218 func (d *dispatcher) AfterFuncWithOwnershipTransfer(td time.Duration, cb func()) *DanglingTimer { 219 return d.AfterFuncWithOwnershipTransferInDomain(td, cb, DefaultTimerDomain) 220 } 221 222 func (d *dispatcher) AfterFuncWithOwnershipTransferInDomain(td time.Duration, cb func(), domain string) *DanglingTimer { 223 t := new(DanglingTimer) 224 t.cb = cb 225 t.domain = domain 226 t.t = time.AfterFunc(td, func() { 227 // callback from another goroutine 228 select { 229 // FIRST read from no buffer chan, even closed, will return false 230 case <-d.stopChan: 231 return 232 default: 233 // close #174 (走到这里时,Close被执行了,这里的ChanTimer可能被close了) 234 d.timerMutex.RLock() 235 if d.closeFlag.Get() == stateRunning { 236 d.ChanTimer <- t 237 } 238 d.timerMutex.RUnlock() 239 } 240 }) 241 log.Debug(fmt.Sprintf("Timer dispatcher add AfterFuncInDomain:%s after:%s", domain, td)) 242 return t 243 } 244 245 // AfterFunc 框架管理 Timer, dispatcher close时自动释放, 无需手动Stop 246 // Reset() 方法无效 247 func (d *dispatcher) AfterFunc(td time.Duration, cb func()) *SafeTimer { 248 return d.AfterFuncInDomain(td, cb, DefaultTimerDomain) 249 } 250 func (d *dispatcher) AfterFuncInDomain(td time.Duration, cb func(), domain string) *SafeTimer { 251 t := new(SafeTimer) 252 t.cb = cb 253 t.domain = domain 254 t.t = time.AfterFunc(td, func() { 255 // callback from another goroutine 256 select { 257 // FIRST read from no buffer chan, even closed, will return false 258 case <-d.stopChan: 259 return 260 default: 261 // close #174 (走到这里时,Close被执行了,这里的ChanTimer可能被close了) 262 d.timerMutex.RLock() 263 if d.closeFlag.Get() == stateRunning { 264 d.ChanTimer <- t 265 // 这里需要删除,否则runningTimers有泄漏 266 d.RemoveTimer(t) 267 } 268 d.timerMutex.RUnlock() 269 } 270 }) 271 d.runningTimers.Store(t, struct{}{}) 272 log.Debug(fmt.Sprintf("Timer dispatcher add AfterFuncInDomain:%s after:%s", domain, td)) 273 return t 274 } 275 276 func (d *dispatcher) CronFunc(cronExpr *cron.Expression, callBack func()) *Cron { 277 c := new(Cron) 278 279 now := time.Now() 280 nextTime := cronExpr.Next(now) 281 if nextTime.IsZero() { 282 return c 283 } 284 285 // callback 286 var cb func() 287 cb = func() { 288 defer callBack() 289 now := time.Now() 290 nextTime := cronExpr.Next(now) 291 if nextTime.IsZero() { 292 return 293 } 294 c.t = d.AfterFunc(nextTime.Sub(now), cb) 295 } 296 297 c.t = d.AfterFunc(nextTime.Sub(now), cb) 298 return c 299 }