github.com/dubbogo/gost@v1.14.0/time/timer.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 // Package gxtime encapsulates some golang.time functions 19 package gxtime 20 21 import ( 22 "container/list" 23 "errors" 24 "log" 25 "sync" 26 "sync/atomic" 27 "time" 28 ) 29 30 import ( 31 uatomic "go.uber.org/atomic" 32 ) 33 34 import ( 35 gxchan "github.com/dubbogo/gost/container/chan" 36 gxlog "github.com/dubbogo/gost/log" 37 ) 38 39 // nolint 40 var ErrTimeChannelClosed = errors.New("timer channel closed") 41 42 // InitDefaultTimerWheel initializes a default timer wheel 43 func InitDefaultTimerWheel() { 44 defaultTimerWheelOnce.Do(func() { 45 defaultTimerWheel = NewTimerWheel() 46 }) 47 } 48 49 func GetDefaultTimerWheel() *TimerWheel { 50 return defaultTimerWheel 51 } 52 53 // Now returns the current time. 54 func Now() time.Time { 55 return defaultTimerWheel.Now() 56 } 57 58 //////////////////////////////////////////////// 59 // timer node 60 //////////////////////////////////////////////// 61 62 var ( 63 defaultTimerWheelOnce sync.Once 64 defaultTimerWheel *TimerWheel 65 nextID TimerID 66 curGxTime = time.Now().UnixNano() // current goext time in nanoseconds 67 ) 68 69 const ( 70 maxMS = 1000 71 maxSecond = 60 72 maxMinute = 60 73 maxHour = 24 74 maxDay = 31 75 // the time accuracy is millisecond. 76 minTickerInterval = 10e6 77 maxTimerLevel = 5 78 ) 79 80 func msNum(expire int64) int64 { return expire / int64(time.Millisecond) } 81 func secondNum(expire int64) int64 { return expire / int64(time.Minute) } 82 func minuteNum(expire int64) int64 { return expire / int64(time.Minute) } 83 func hourNum(expire int64) int64 { return expire / int64(time.Hour) } 84 func dayNum(expire int64) int64 { return expire / (maxHour * int64(time.Hour)) } 85 86 // TimerFunc defines the time func. 87 // if the return error is not nil, the related timer will be closed. 88 type TimerFunc func(ID TimerID, expire time.Time, arg interface{}) error 89 90 // TimerID is the id of a timer node 91 type TimerID = uint64 92 93 type timerNode struct { 94 ID TimerID // node id 95 trig int64 // trigger time 96 typ TimerType // once or loop 97 period int64 // loop period 98 timerRun TimerFunc // timer func 99 arg interface{} // func arg 100 } 101 102 func newTimerNode(f TimerFunc, typ TimerType, period int64, arg interface{}) *timerNode { 103 return &timerNode{ 104 ID: atomic.AddUint64(&nextID, 1), 105 trig: atomic.LoadInt64(&curGxTime) + period, 106 typ: typ, 107 period: period, 108 timerRun: f, 109 arg: arg, 110 } 111 } 112 113 func compareTimerNode(first, second *timerNode) int { 114 var ret int 115 116 if first.trig < second.trig { 117 ret = -1 118 } else if first.trig > second.trig { 119 ret = 1 120 } else { 121 ret = 0 122 } 123 124 return ret 125 } 126 127 type timerAction = int64 128 129 const ( 130 TimerActionAdd timerAction = 1 131 TimerActionDel timerAction = 2 132 TimerActionReset timerAction = 3 133 ) 134 135 type timerNodeAction struct { 136 node *timerNode 137 action timerAction 138 } 139 140 //////////////////////////////////////////////// 141 // timer wheel 142 //////////////////////////////////////////////// 143 144 const ( 145 timerNodeQueueSize = 128 146 ) 147 148 var ( 149 limit = [maxTimerLevel + 1]int64{maxMS, maxSecond, maxMinute, maxHour, maxDay} 150 msLimit = [maxTimerLevel + 1]int64{ 151 int64(time.Millisecond), 152 int64(time.Second), 153 int64(time.Minute), 154 int64(time.Hour), 155 int64(maxHour * time.Hour), 156 } 157 ) 158 159 // TimerWheel is a timer based on multiple wheels 160 type TimerWheel struct { 161 start int64 // start clock 162 clock int64 // current time in nanosecond 163 number uatomic.Int64 // timer node number 164 hand [maxTimerLevel]int64 // clock 165 slot [maxTimerLevel]*list.List // timer list 166 167 enable uatomic.Bool // timer ready or closed 168 timerQ *gxchan.UnboundedChan // timer event notify channel 169 170 once sync.Once // for close ticker 171 ticker *time.Ticker // virtual atomic clock 172 wg sync.WaitGroup // gr sync 173 } 174 175 // NewTimerWheel returns a @TimerWheel object. 176 func NewTimerWheel() *TimerWheel { 177 w := &TimerWheel{ 178 clock: atomic.LoadInt64(&curGxTime), 179 // in fact, the minimum time accuracy is 10ms. 180 ticker: time.NewTicker(time.Duration(minTickerInterval)), 181 timerQ: gxchan.NewUnboundedChan(timerNodeQueueSize), 182 } 183 184 w.enable.Store(true) 185 w.start = w.clock 186 187 for i := 0; i < maxTimerLevel; i++ { 188 w.slot[i] = list.New() 189 } 190 191 w.wg.Add(1) 192 go func() { 193 defer w.wg.Done() 194 var ( 195 t time.Time 196 cFlag bool 197 ) 198 199 LOOP: 200 for { 201 if !w.enable.Load() { 202 break LOOP 203 } 204 205 select { 206 case t, cFlag = <-w.ticker.C: 207 if !cFlag { 208 break LOOP 209 } 210 211 atomic.StoreInt64(&curGxTime, t.UnixNano()) 212 ret := w.timerUpdate(t) 213 if ret == 0 { 214 w.run() 215 } 216 case node, qFlag := <-w.timerQ.Out(): 217 if !qFlag { 218 break LOOP 219 } 220 221 nodeAction := node.(*timerNodeAction) 222 // just one w.timerQ channel to ensure the exec sequence of timer event. 223 switch { 224 case nodeAction.action == TimerActionAdd: 225 w.number.Add(1) 226 w.insertTimerNode(nodeAction.node) 227 case nodeAction.action == TimerActionDel: 228 w.number.Add(-1) 229 w.deleteTimerNode(nodeAction.node) 230 case nodeAction.action == TimerActionReset: 231 // log.CInfo("node action:%#v", nodeAction) 232 w.resetTimerNode(nodeAction.node) 233 default: 234 w.number.Add(1) 235 w.insertTimerNode(nodeAction.node) 236 } 237 } 238 } 239 log.Printf("the timeWheel runner exit, current timer node num:%d", w.number.Load()) 240 }() 241 return w 242 } 243 244 func (w *TimerWheel) output() { 245 for idx := range w.slot { 246 log.Printf("print slot %d\n", idx) 247 // w.slot[idx].Output() 248 } 249 } 250 251 // TimerNumber returns the timer obj number in wheel 252 func (w *TimerWheel) TimerNumber() int { 253 return int(w.number.Load()) 254 } 255 256 // Now returns the current time 257 func (w *TimerWheel) Now() time.Time { 258 return UnixNano2Time(atomic.LoadInt64(&curGxTime)) 259 } 260 261 func (w *TimerWheel) run() { 262 var ( 263 clock int64 264 err error 265 node *timerNode 266 slot *list.List 267 reinsertNodes []*timerNode 268 ) 269 270 slot = w.slot[0] 271 clock = atomic.LoadInt64(&w.clock) 272 var next *list.Element 273 for e := slot.Front(); e != nil; e = next { 274 node = e.Value.(*timerNode) 275 if clock < node.trig { 276 break 277 } 278 279 err = node.timerRun(node.ID, UnixNano2Time(clock), node.arg) 280 if err == nil && node.typ == TimerLoop { 281 reinsertNodes = append(reinsertNodes, node) 282 // w.insertTimerNode(node) 283 } else { 284 w.number.Add(-1) 285 } 286 287 next = e.Next() 288 slot.Remove(e) 289 } 290 291 for _, reinsertNode := range reinsertNodes { 292 reinsertNode.trig += reinsertNode.period 293 w.insertTimerNode(reinsertNode) 294 } 295 } 296 297 func (w *TimerWheel) insertSlot(idx int, node *timerNode) { 298 var ( 299 pos *list.Element 300 slot *list.List 301 ) 302 303 slot = w.slot[idx] 304 for e := slot.Front(); e != nil; e = e.Next() { 305 if compareTimerNode(node, e.Value.(*timerNode)) < 0 { 306 pos = e 307 break 308 } 309 } 310 311 if pos != nil { 312 slot.InsertBefore(node, pos) 313 } else { 314 // if slot is empty or @node_ptr is the maximum node 315 // in slot, insert it at the last of slot 316 slot.PushBack(node) 317 } 318 } 319 320 func (w *TimerWheel) deleteTimerNode(node *timerNode) { 321 var level int 322 323 LOOP: 324 for level = range w.slot[:] { 325 for e := w.slot[level].Front(); e != nil; e = e.Next() { 326 if e.Value.(*timerNode).ID == node.ID { 327 w.slot[level].Remove(e) 328 // atomic.AddInt64(&w.number, -1) 329 break LOOP 330 } 331 } 332 } 333 } 334 335 func (w *TimerWheel) resetTimerNode(node *timerNode) { 336 var level int 337 338 LOOP: 339 for level = range w.slot[:] { 340 for e := w.slot[level].Front(); e != nil; e = e.Next() { 341 if e.Value.(*timerNode).ID == node.ID { 342 n := e.Value.(*timerNode) 343 n.trig -= n.period 344 n.period = node.period 345 n.trig += n.period 346 w.slot[level].Remove(e) 347 w.insertTimerNode(n) 348 break LOOP 349 } 350 } 351 } 352 } 353 354 func (w *TimerWheel) deltaDiff(clock int64) int64 { 355 var handTime int64 356 357 for idx, hand := range w.hand[:] { 358 handTime += hand * msLimit[idx] 359 } 360 361 return clock - w.start - handTime 362 } 363 364 func (w *TimerWheel) insertTimerNode(node *timerNode) { 365 var ( 366 idx int 367 diff int64 368 ) 369 370 diff = node.trig - atomic.LoadInt64(&w.clock) 371 switch { 372 case diff <= 0: 373 idx = 0 374 case dayNum(diff) != 0: 375 idx = 4 376 case hourNum(diff) != 0: 377 idx = 3 378 case minuteNum(diff) != 0: 379 idx = 2 380 case secondNum(diff) != 0: 381 idx = 1 382 default: 383 idx = 0 384 } 385 386 w.insertSlot(idx, node) 387 } 388 389 func (w *TimerWheel) timerCascade(level int) { 390 var ( 391 guard bool 392 clock int64 393 diff int64 394 cur *timerNode 395 ) 396 397 clock = atomic.LoadInt64(&w.clock) 398 var next *list.Element 399 for e := w.slot[level].Front(); e != nil; e = next { 400 cur = e.Value.(*timerNode) 401 diff = cur.trig - clock 402 switch { 403 case cur.trig <= clock: 404 guard = false 405 case level == 1: 406 guard = secondNum(diff) > 0 407 case level == 2: 408 guard = minuteNum(diff) > 0 409 case level == 3: 410 guard = hourNum(diff) > 0 411 case level == 4: 412 guard = dayNum(diff) > 0 413 } 414 415 if guard { 416 break 417 } 418 419 next = e.Next() 420 w.slot[level].Remove(e) 421 422 w.insertTimerNode(cur) 423 } 424 } 425 426 func (w *TimerWheel) timerUpdate(curTime time.Time) int { 427 var ( 428 clock int64 429 now int64 430 idx int32 431 diff int64 432 maxIdx int32 433 inc [maxTimerLevel + 1]int64 434 ) 435 436 now = curTime.UnixNano() 437 clock = atomic.LoadInt64(&w.clock) 438 diff = now - clock 439 diff += w.deltaDiff(clock) 440 if diff < minTickerInterval*0.7 { 441 return -1 442 } 443 atomic.StoreInt64(&w.clock, now) 444 445 for idx = maxTimerLevel - 1; 0 <= idx; idx-- { 446 inc[idx] = diff / msLimit[idx] 447 diff %= msLimit[idx] 448 } 449 450 maxIdx = 0 451 for idx = 0; idx < maxTimerLevel; idx++ { 452 if 0 != inc[idx] { 453 w.hand[idx] += inc[idx] 454 inc[idx+1] += w.hand[idx] / limit[idx] 455 w.hand[idx] %= limit[idx] 456 maxIdx = idx + 1 457 } 458 } 459 460 for idx = 1; idx < maxIdx; idx++ { 461 w.timerCascade(int(idx)) 462 } 463 464 return 0 465 } 466 467 // Stop stops the ticker 468 func (w *TimerWheel) Stop() { 469 w.once.Do(func() { 470 w.enable.Store(false) 471 // close(w.timerQ) // to defend data race warning 472 w.ticker.Stop() 473 }) 474 } 475 476 // Close stops the timer wheel and wait for all grs. 477 func (w *TimerWheel) Close() { 478 w.Stop() 479 w.wg.Wait() 480 } 481 482 //////////////////////////////////////////////// 483 // timer 484 //////////////////////////////////////////////// 485 486 // TimerType defines a timer task type. 487 type TimerType int32 488 489 const ( 490 TimerOnce TimerType = 0x1 << 0 491 TimerLoop TimerType = 0x1 << 1 492 ) 493 494 // AddTimer adds a timer asynchronously and returns a timer struct obj. It returns error if it failed. 495 // 496 // Attention that @f may block the timer gr. So u should create a gr to exec ur function asynchronously 497 // if it may take a long time. 498 // 499 // args: 500 // @f: timer function. 501 // @typ: timer type 502 // @period: timer loop interval. its unit is nanosecond. 503 // @arg: timer argument which is used by @f. 504 func (w *TimerWheel) AddTimer(f TimerFunc, typ TimerType, period time.Duration, arg interface{}) (*Timer, error) { 505 if !w.enable.Load() { 506 return nil, ErrTimeChannelClosed 507 } 508 509 t := &Timer{w: w} 510 node := newTimerNode(f, typ, int64(period), arg) 511 select { 512 case w.timerQ.In() <- &timerNodeAction{node: node, action: TimerActionAdd}: 513 t.ID = node.ID 514 return t, nil 515 } 516 } 517 518 func (w *TimerWheel) deleteTimer(t *Timer) error { 519 if !w.enable.Load() { 520 return ErrTimeChannelClosed 521 } 522 523 select { 524 case w.timerQ.In() <- &timerNodeAction{action: TimerActionDel, node: &timerNode{ID: t.ID}}: 525 return nil 526 } 527 } 528 529 func (w *TimerWheel) resetTimer(t *Timer, d time.Duration) error { 530 if !w.enable.Load() { 531 return ErrTimeChannelClosed 532 } 533 534 select { 535 case w.timerQ.In() <- &timerNodeAction{action: TimerActionReset, node: &timerNode{ID: t.ID, period: int64(d)}}: 536 return nil 537 } 538 } 539 540 func sendTime(_ TimerID, t time.Time, arg interface{}) error { 541 select { 542 case arg.(chan time.Time) <- t: 543 default: 544 // log.CInfo("sendTime default") 545 } 546 547 return nil 548 } 549 550 // NewTimer creates a new Timer that will send 551 // the current time on its channel after at least duration d. 552 func (w *TimerWheel) NewTimer(d time.Duration) *Timer { 553 c := make(chan time.Time, 1) 554 t := &Timer{ 555 C: c, 556 } 557 558 timer, err := w.AddTimer(sendTime, TimerOnce, d, c) 559 if err == nil { 560 t.ID = timer.ID 561 t.w = timer.w 562 return t 563 } 564 565 gxlog.CError("addTimer fail, err is %v", err) 566 close(c) 567 return nil 568 } 569 570 // After waits for the duration to elapse and then sends the current time 571 // on the returned channel. 572 func (w *TimerWheel) After(d time.Duration) <-chan time.Time { 573 //timer := defaultTimer.NewTimer(d) 574 //if timer == nil { 575 // return nil 576 //} 577 // 578 //return timer.C 579 return w.NewTimer(d).C 580 } 581 582 func goFunc(_ TimerID, _ time.Time, arg interface{}) error { 583 go arg.(func())() 584 585 return nil 586 } 587 588 // AfterFunc waits for the duration to elapse and then calls f 589 // in its own goroutine. It returns a Timer that can 590 // be used to cancel the call using its Stop method. 591 func (w *TimerWheel) AfterFunc(d time.Duration, f func()) *Timer { 592 t, _ := w.AddTimer(goFunc, TimerOnce, d, f) 593 594 return t 595 } 596 597 // Sleep pauses the current goroutine for at least the duration d. 598 // A negative or zero duration causes Sleep to return immediately. 599 func (w *TimerWheel) Sleep(d time.Duration) { 600 <-w.NewTimer(d).C 601 } 602 603 //////////////////////////////////////////////// 604 // ticker 605 //////////////////////////////////////////////// 606 607 // NewTicker returns a new Ticker containing a channel that will send 608 // the time on the channel after each tick. The period of the ticks is 609 // specified by the duration argument. The ticker will adjust the time 610 // interval or drop ticks to make up for slow receivers. 611 // The duration d must be greater than zero; if not, NewTicker will 612 // panic. Stop the ticker to release associated resources. 613 func (w *TimerWheel) NewTicker(d time.Duration) *Ticker { 614 c := make(chan time.Time, 1) 615 616 timer, err := w.AddTimer(sendTime, TimerLoop, d, c) 617 if err == nil { 618 timer.C = c 619 return (*Ticker)(timer) 620 } 621 622 gxlog.CError("addTimer fail, err is %v", err) 623 close(c) 624 return nil 625 } 626 627 // TickFunc returns a Ticker 628 func (w *TimerWheel) TickFunc(d time.Duration, f func()) *Ticker { 629 t, err := w.AddTimer(goFunc, TimerLoop, d, f) 630 if err == nil { 631 return (*Ticker)(t) 632 } 633 gxlog.CError("addTimer fail, err is %v", err) 634 635 return nil 636 } 637 638 // Tick is a convenience wrapper for NewTicker providing access to the ticking 639 // channel only. While Tick is useful for clients that have no need to shut down 640 // the Ticker, be aware that without a way to shut it down the underlying 641 // Ticker cannot be recovered by the garbage collector; it "leaks". 642 // Unlike NewTicker, Tick will return nil if d <= 0. 643 func (w *TimerWheel) Tick(d time.Duration) <-chan time.Time { 644 return w.NewTicker(d).C 645 }