package timewheel import ( "context" "go_dreamfactory/lego/sys/log" "runtime" "sync" "sync/atomic" "time" ) // 创建一个时间轮 func newsys(options Options) (sys *TimeWheel, err error) { sys = &TimeWheel{ // tick tick: options.Tick, tickQueue: make(chan time.Time, 10), // store bucketsNum: options.BucketsNum, bucketIndexes: make(map[taskID]int, 1024*100), buckets: make([]map[taskID]*Task, options.BucketsNum), currentIndex: 0, // signal addC: make(chan *Task, 1024*5), removeC: make(chan *Task, 1024*2), stopC: make(chan struct{}), syncPool: options.IsSyncPool, } for i := 0; i < options.BucketsNum; i++ { sys.buckets[i] = make(map[taskID]*Task, 16) } return } const ( typeTimer taskType = iota typeTicker modeIsCircle = true modeNotCircle = false modeIsAsync = true modeNotAsync = false ) type ( taskType int64 taskID int64 Task struct { delay time.Duration id taskID round int args []interface{} callback func(*Task, ...interface{}) async bool stop bool circle bool } TimeWheel struct { randomID int64 tick time.Duration ticker *time.Ticker tickQueue chan time.Time bucketsNum int buckets []map[taskID]*Task // key: added item, value: *Task bucketIndexes map[taskID]int // key: added item, value: bucket position currentIndex int onceStart sync.Once addC chan *Task removeC chan *Task stopC chan struct{} exited bool syncPool bool } ) // for sync.Pool func (t *Task) Reset() { t.round = 0 t.callback = nil t.async = false t.stop = false t.circle = false } //启动时间轮 func (this *TimeWheel) Start() { // onlye once start this.onceStart.Do( func() { this.ticker = time.NewTicker(this.tick) go this.schduler() go this.tickGenerator() }, ) } func (this *TimeWheel) Add(delay time.Duration, handler func(*Task, ...interface{}), args ...interface{}) *Task { return this.addAny(delay, modeNotCircle, modeIsAsync, handler, args...) } // AddCron add interval task func (this *TimeWheel) AddCron(delay time.Duration, handler func(*Task, ...interface{}), args ...interface{}) *Task { return this.addAny(delay, modeIsCircle, modeIsAsync, handler, args...) } func (this *TimeWheel) Remove(task *Task) error { this.removeC <- task return nil } //停止时间轮 func (this *TimeWheel) Stop() { this.stopC <- struct{}{} } //此处写法 为监控时间轮是否正常执行 func (this *TimeWheel) tickGenerator() { if this.tickQueue == nil { return } for !this.exited { select { case <-this.ticker.C: select { case this.tickQueue <- time.Now(): default: panic("raise long time blocking") } } } } //调度器 func (this *TimeWheel) schduler() { queue := this.ticker.C if this.tickQueue != nil { queue = this.tickQueue } for { select { case <-queue: this.handleTick() case task := <-this.addC: this.put(task) case key := <-this.removeC: this.remove(key) case <-this.stopC: this.exited = true this.ticker.Stop() return } } } //清理 func (this *TimeWheel) collectTask(task *Task) { index := this.bucketIndexes[task.id] delete(this.bucketIndexes, task.id) delete(this.buckets[index], task.id) if this.syncPool && !task.circle { defaultTaskPool.put(task) } } func (this *TimeWheel) handleTick() { bucket := this.buckets[this.currentIndex] for k, task := range bucket { if task.stop { this.collectTask(task) continue } if bucket[k].round > 0 { bucket[k].round-- continue } if task.async { go func(task *Task) { go this.calltask(task, task.args...) }(task) } else { // optimize gopool this.calltask(task, task.args...) } // circle if task.circle { this.collectTask(task) this.putCircle(task, modeIsCircle) continue } // gc this.collectTask(task) } if this.currentIndex == this.bucketsNum-1 { this.currentIndex = 0 return } this.currentIndex++ } //执行时间轮事件 捕捉异常错误 防止程序崩溃 func (this *TimeWheel) calltask(task *Task, args ...interface{}) { defer func() { //程序异常 收集异常信息传递给前端显示 if r := recover(); r != nil { buf := make([]byte, 4096) l := runtime.Stack(buf, false) log.Errorf("timewheel err:%s", string(buf[0:l])) } }() task.callback(task, task.args...) } func (this *TimeWheel) addAny(delay time.Duration, circle, async bool, callback func(*Task, ...interface{}), agr ...interface{}) *Task { if delay <= 0 { delay = this.tick } id := this.genUniqueID() var task *Task if this.syncPool { task = defaultTaskPool.get() } else { task = new(Task) } task.delay = delay task.id = id task.args = agr task.callback = callback task.circle = circle task.async = async // refer to src/runtime/time.go this.addC <- task return task } func (this *TimeWheel) put(task *Task) { this.store(task, false) } func (this *TimeWheel) putCircle(task *Task, circleMode bool) { this.store(task, circleMode) } func (this *TimeWheel) store(task *Task, circleMode bool) { round := this.calculateRound(task.delay) index := this.calculateIndex(task.delay) if round > 0 && circleMode { task.round = round - 1 } else { task.round = round } this.bucketIndexes[task.id] = index this.buckets[index][task.id] = task } func (this *TimeWheel) calculateRound(delay time.Duration) (round int) { delaySeconds := delay.Seconds() tickSeconds := this.tick.Seconds() round = int(delaySeconds / tickSeconds / float64(this.bucketsNum)) return } func (this *TimeWheel) calculateIndex(delay time.Duration) (index int) { delaySeconds := delay.Seconds() tickSeconds := this.tick.Seconds() index = (int(float64(this.currentIndex) + delaySeconds/tickSeconds)) % this.bucketsNum return } func (this *TimeWheel) remove(task *Task) { this.collectTask(task) } func (this *TimeWheel) NewTimer(delay time.Duration) *Timer { queue := make(chan bool, 1) // buf = 1, refer to src/time/sleep.go task := this.addAny(delay, modeNotCircle, modeNotAsync, func(*Task, ...interface{}) { notfiyChannel(queue) }, ) // init timer ctx, cancel := context.WithCancel(context.Background()) timer := &Timer{ this: this, C: queue, // faster task: task, Ctx: ctx, cancel: cancel, } return timer } func (this *TimeWheel) AfterFunc(delay time.Duration, callback func()) *Timer { queue := make(chan bool, 1) task := this.addAny(delay, modeNotCircle, modeIsAsync, func(*Task, ...interface{}) { callback() notfiyChannel(queue) }, ) // init timer ctx, cancel := context.WithCancel(context.Background()) timer := &Timer{ this: this, C: queue, // faster task: task, Ctx: ctx, cancel: cancel, fn: callback, } return timer } func (this *TimeWheel) NewTicker(delay time.Duration) *Ticker { queue := make(chan bool, 1) task := this.addAny(delay, modeIsCircle, modeNotAsync, func(*Task, ...interface{}) { notfiyChannel(queue) }, ) // init ticker ctx, cancel := context.WithCancel(context.Background()) ticker := &Ticker{ task: task, this: this, C: queue, Ctx: ctx, cancel: cancel, } return ticker } func (this *TimeWheel) After(delay time.Duration) <-chan time.Time { queue := make(chan time.Time, 1) this.addAny(delay, modeNotCircle, modeNotAsync, func(*Task, ...interface{}) { queue <- time.Now() }, ) return queue } func (this *TimeWheel) Sleep(delay time.Duration) { queue := make(chan bool, 1) this.addAny(delay, modeNotCircle, modeNotAsync, func(*Task, ...interface{}) { queue <- true }, ) <-queue } // similar to golang std timer type Timer struct { task *Task this *TimeWheel fn func() // external custom func C chan bool cancel context.CancelFunc Ctx context.Context } func (t *Timer) Reset(delay time.Duration) { var task *Task if t.fn != nil { // use AfterFunc task = t.this.addAny(delay, modeNotCircle, modeIsAsync, // must async mode func(*Task, ...interface{}) { t.fn() notfiyChannel(t.C) }, ) } else { task = t.this.addAny(delay, modeNotCircle, modeNotAsync, func(*Task, ...interface{}) { notfiyChannel(t.C) }, ) } t.task = task } func (t *Timer) Stop() { t.task.stop = true t.cancel() t.this.Remove(t.task) } func (t *Timer) StopFunc(callback func()) { t.fn = callback } type Ticker struct { this *TimeWheel task *Task cancel context.CancelFunc C chan bool Ctx context.Context } func (t *Ticker) Stop() { t.task.stop = true t.cancel() t.this.Remove(t.task) } func notfiyChannel(q chan bool) { select { case q <- true: default: } } func (this *TimeWheel) genUniqueID() taskID { id := atomic.AddInt64(&this.randomID, 1) return taskID(id) }