go_dreamfactory/lego/sys/timewheel/timewheel.go
2023-06-15 21:35:32 +08:00

454 lines
8.7 KiB
Go

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)
}