You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			219 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
		
		
			
		
	
	
			219 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
| 
											5 months ago
										 | package tao | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"container/heap" | ||
|  | 	"context" | ||
|  | 	"sync" | ||
|  | 	"time" | ||
|  | 
 | ||
|  | 	"flx/cite/holmes" | ||
|  | ) | ||
|  | 
 | ||
|  | const ( | ||
|  | 	tickPeriod time.Duration = 500 * time.Millisecond | ||
|  | 	bufferSize               = 1024 | ||
|  | ) | ||
|  | 
 | ||
|  | var timerIds *AtomicInt64 | ||
|  | 
 | ||
|  | func init() { | ||
|  | 	timerIds = NewAtomicInt64(0) | ||
|  | } | ||
|  | 
 | ||
|  | // timerHeap is a heap-based priority queue
 | ||
|  | type timerHeapType []*timerType | ||
|  | 
 | ||
|  | func (heap timerHeapType) getIndexByID(id int64) int { | ||
|  | 	for _, t := range heap { | ||
|  | 		if t.id == id { | ||
|  | 			return t.index | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return -1 | ||
|  | } | ||
|  | 
 | ||
|  | func (heap timerHeapType) Len() int { | ||
|  | 	return len(heap) | ||
|  | } | ||
|  | 
 | ||
|  | func (heap timerHeapType) Less(i, j int) bool { | ||
|  | 	return heap[i].expiration.UnixNano() < heap[j].expiration.UnixNano() | ||
|  | } | ||
|  | 
 | ||
|  | func (heap timerHeapType) Swap(i, j int) { | ||
|  | 	heap[i], heap[j] = heap[j], heap[i] | ||
|  | 	heap[i].index = i | ||
|  | 	heap[j].index = j | ||
|  | } | ||
|  | 
 | ||
|  | func (heap *timerHeapType) Push(x interface{}) { | ||
|  | 	n := len(*heap) | ||
|  | 	timer := x.(*timerType) | ||
|  | 	timer.index = n | ||
|  | 	*heap = append(*heap, timer) | ||
|  | } | ||
|  | 
 | ||
|  | func (heap *timerHeapType) Pop() interface{} { | ||
|  | 	old := *heap | ||
|  | 	n := len(old) | ||
|  | 	timer := old[n-1] | ||
|  | 	timer.index = -1 | ||
|  | 	*heap = old[0 : n-1] | ||
|  | 	return timer | ||
|  | } | ||
|  | 
 | ||
|  | /* | ||
|  | 	'expiration' is the time when timer time out, if 'interval' > 0 | ||
|  | 
 | ||
|  | the timer will time out periodically, 'timeout' contains the callback | ||
|  | to be called when times out | ||
|  | */ | ||
|  | type timerType struct { | ||
|  | 	id         int64 | ||
|  | 	expiration time.Time | ||
|  | 	interval   time.Duration | ||
|  | 	timeout    *OnTimeOut | ||
|  | 	index      int // for container/heap
 | ||
|  | } | ||
|  | 
 | ||
|  | func newTimer(when time.Time, interv time.Duration, to *OnTimeOut) *timerType { | ||
|  | 	return &timerType{ | ||
|  | 		id:         timerIds.GetAndIncrement(), | ||
|  | 		expiration: when, | ||
|  | 		interval:   interv, | ||
|  | 		timeout:    to, | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (t *timerType) isRepeat() bool { | ||
|  | 	return int64(t.interval) > 0 | ||
|  | } | ||
|  | 
 | ||
|  | // TimingWheel manages all the timed task.
 | ||
|  | type TimingWheel struct { | ||
|  | 	timeOutChan chan *OnTimeOut | ||
|  | 	timers      timerHeapType | ||
|  | 	ticker      *time.Ticker | ||
|  | 	wg          *sync.WaitGroup | ||
|  | 	addChan     chan *timerType // add timer in loop
 | ||
|  | 	cancelChan  chan int64      // cancel timer in loop
 | ||
|  | 	sizeChan    chan int        // get size in loop
 | ||
|  | 	ctx         context.Context | ||
|  | 	cancel      context.CancelFunc | ||
|  | } | ||
|  | 
 | ||
|  | // NewTimingWheel returns a *TimingWheel ready for use.
 | ||
|  | func NewTimingWheel(ctx context.Context) *TimingWheel { | ||
|  | 	timingWheel := &TimingWheel{ | ||
|  | 		timeOutChan: make(chan *OnTimeOut, bufferSize), | ||
|  | 		timers:      make(timerHeapType, 0), | ||
|  | 		ticker:      time.NewTicker(tickPeriod), | ||
|  | 		wg:          &sync.WaitGroup{}, | ||
|  | 		addChan:     make(chan *timerType, bufferSize), | ||
|  | 		cancelChan:  make(chan int64, bufferSize), | ||
|  | 		sizeChan:    make(chan int), | ||
|  | 	} | ||
|  | 	timingWheel.ctx, timingWheel.cancel = context.WithCancel(ctx) | ||
|  | 	heap.Init(&timingWheel.timers) | ||
|  | 	timingWheel.wg.Add(1) | ||
|  | 	go func() { | ||
|  | 		timingWheel.start() | ||
|  | 		timingWheel.wg.Done() | ||
|  | 	}() | ||
|  | 	return timingWheel | ||
|  | } | ||
|  | 
 | ||
|  | // TimeOutChannel returns the timeout channel.
 | ||
|  | func (tw *TimingWheel) TimeOutChannel() chan *OnTimeOut { | ||
|  | 	return tw.timeOutChan | ||
|  | } | ||
|  | 
 | ||
|  | // AddTimer adds new timed task.
 | ||
|  | func (tw *TimingWheel) AddTimer(when time.Time, interv time.Duration, to *OnTimeOut) int64 { | ||
|  | 	if to == nil { | ||
|  | 		return int64(-1) | ||
|  | 	} | ||
|  | 	timer := newTimer(when, interv, to) | ||
|  | 	tw.addChan <- timer | ||
|  | 	return timer.id | ||
|  | } | ||
|  | 
 | ||
|  | // Size returns the number of timed tasks.
 | ||
|  | func (tw *TimingWheel) Size() int { | ||
|  | 	return <-tw.sizeChan | ||
|  | } | ||
|  | 
 | ||
|  | // CancelTimer cancels a timed task with specified timer ID.
 | ||
|  | func (tw *TimingWheel) CancelTimer(timerID int64) { | ||
|  | 	tw.cancelChan <- timerID | ||
|  | } | ||
|  | 
 | ||
|  | // Stop stops the TimingWheel.
 | ||
|  | func (tw *TimingWheel) Stop() { | ||
|  | 	tw.cancel() | ||
|  | 	tw.wg.Wait() | ||
|  | } | ||
|  | 
 | ||
|  | func (tw *TimingWheel) getExpired() []*timerType { | ||
|  | 	expired := make([]*timerType, 0) | ||
|  | 	for tw.timers.Len() > 0 { | ||
|  | 		timer := heap.Pop(&tw.timers).(*timerType) | ||
|  | 		elapsed := time.Since(timer.expiration).Seconds() | ||
|  | 		if elapsed > 1.0 { | ||
|  | 			holmes.Warnf("elapsed %f\n", elapsed) | ||
|  | 		} | ||
|  | 		if elapsed > 0.0 { | ||
|  | 			expired = append(expired, timer) | ||
|  | 			continue | ||
|  | 		} else { | ||
|  | 			heap.Push(&tw.timers, timer) | ||
|  | 			break | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return expired | ||
|  | } | ||
|  | 
 | ||
|  | func (tw *TimingWheel) update(timers []*timerType) { | ||
|  | 	if timers != nil { | ||
|  | 		for _, t := range timers { | ||
|  | 			if t.isRepeat() { // repeatable timer task
 | ||
|  | 				t.expiration = t.expiration.Add(t.interval) | ||
|  | 				// if task time out for at least 10 seconds, the expiration time needs
 | ||
|  | 				// to be updated in case this task executes every time timer wakes up.
 | ||
|  | 				if time.Since(t.expiration).Seconds() >= 10.0 { | ||
|  | 					t.expiration = time.Now() | ||
|  | 				} | ||
|  | 				heap.Push(&tw.timers, t) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (tw *TimingWheel) start() { | ||
|  | 	for { | ||
|  | 		select { | ||
|  | 		case timerID := <-tw.cancelChan: | ||
|  | 			index := tw.timers.getIndexByID(timerID) | ||
|  | 			if index >= 0 { | ||
|  | 				heap.Remove(&tw.timers, index) | ||
|  | 			} | ||
|  | 
 | ||
|  | 		case tw.sizeChan <- tw.timers.Len(): | ||
|  | 
 | ||
|  | 		case <-tw.ctx.Done(): | ||
|  | 			tw.ticker.Stop() | ||
|  | 			return | ||
|  | 
 | ||
|  | 		case timer := <-tw.addChan: | ||
|  | 			heap.Push(&tw.timers, timer) | ||
|  | 
 | ||
|  | 		case <-tw.ticker.C: | ||
|  | 			timers := tw.getExpired() | ||
|  | 			for _, t := range timers { | ||
|  | 				tw.TimeOutChannel() <- t.timeout | ||
|  | 			} | ||
|  | 			tw.update(timers) | ||
|  | 		} | ||
|  | 	} | ||
|  | } |