github.com/GeniusesGroup/libgo@v0.0.0-20220929090155-5ff932cb408e/timer/timing-wheel.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  	"github.com/GeniusesGroup/libgo/protocol"
     7  )
     8  
     9  // TimingWheel is not concurrent safe and must call each instance by each CPU core separately,
    10  // or write upper layer to implement needed logic to prevent data race.
    11  type TimingWheel struct {
    12  	wheelSize int
    13  	wheel     [][]*Async
    14  	interval  protocol.Duration // same as tw.ticker.period
    15  	pos       int
    16  	ticker    Sync
    17  	stop      chan struct{}
    18  }
    19  
    20  // if you make one sec interval for a earth day(60*60*24=86400), you need 2MB ram just to hold empty wheel without any timer.
    21  func (tw *TimingWheel) Init(interval protocol.Duration, wheelSize int) {
    22  	tw.wheelSize = wheelSize
    23  	tw.stop = make(chan struct{})
    24  	tw.wheel = make([][]*Async, wheelSize)
    25  	tw.ticker.Init()
    26  	tw.interval = interval
    27  }
    28  
    29  func (tw *TimingWheel) AddTimer(t *Async) {
    30  	var addedPosition = tw.addedPosition(t)
    31  	if addedPosition > tw.wheelSize {
    32  		panic("timer - wheel: try to add a timer with bad timeout that overflow the current timing wheel")
    33  	}
    34  	if addedPosition == tw.pos {
    35  		t.callback.TimerHandler()
    36  		tw.checkAndAddTimerAgain(t)
    37  		return
    38  	}
    39  	tw.wheel[addedPosition] = append(tw.wheel[addedPosition], t)
    40  }
    41  
    42  // call by go keyword if you don't want the current goroutine block.
    43  func (tw *TimingWheel) Start() {
    44  	tw.ticker.Tick(tw.interval, tw.interval)
    45  loop:
    46  	for {
    47  		select {
    48  		case <-tw.ticker.Signal():
    49  			var pos = tw.pos
    50  			tw.incrementTickPosition()
    51  			var timers = tw.wheel[pos]
    52  			tw.wheel[pos] = timers[:0]
    53  			for i := 0; i < len(timers); i++ {
    54  				var timer = timers[i]
    55  				timer.callback.TimerHandler()
    56  				tw.checkAndAddTimerAgain(timer)
    57  			}
    58  		case <-tw.stop:
    59  			close(tw.stop)
    60  			tw.stop = nil
    61  			break loop
    62  		}
    63  	}
    64  	tw.ticker.Stop()
    65  }
    66  
    67  // Not concurrent safe.
    68  func (tw *TimingWheel) Stop() (alreadyStopped bool) {
    69  	if tw.stop == nil {
    70  		return true
    71  	}
    72  
    73  	select {
    74  	case tw.stop <- struct{}{}:
    75  	default:
    76  		alreadyStopped = true
    77  	}
    78  	tw.ticker.Stop()
    79  	return
    80  }
    81  
    82  func (tw *TimingWheel) incrementTickPosition() {
    83  	var wheelLen = len(tw.wheel)
    84  	if wheelLen-1 == tw.pos {
    85  		tw.pos = 0
    86  	} else {
    87  		tw.pos++
    88  	}
    89  }
    90  
    91  func (tw *TimingWheel) checkAndAddTimerAgain(t *Async) {
    92  	if t.period == 0 {
    93  		t.Reinit()
    94  	} else {
    95  		var addedPosition = tw.addedPosition(t)
    96  		tw.wheel[addedPosition] = append(tw.wheel[addedPosition], t)
    97  	}
    98  }
    99  
   100  func (tw *TimingWheel) addedPosition(t *Async) int {
   101  	return int(t.period/tw.interval) + tw.pos
   102  }