This commit is contained in:
2022-03-17 15:55:27 +08:00
commit bd5a9fad97
92 changed files with 13861 additions and 0 deletions

327
dtask/clock.go Normal file
View File

@@ -0,0 +1,327 @@
// Package clock is a low consumption, low latency support for frequent updates of large capacity timing manager
// 1、能够添加一次性、重复性任务并能在其执行前撤销或频繁更改。
// 2、支持同一时间点多个任务提醒。
// 3、适用于中等密度大跨度的单次、多次定时任务。
// 4、支持10万次/秒的定时任务执行、提醒、撤销或添加操作平均延迟10微秒内
// 5、支持注册任务的函数调用及事件通知。
// 基本处理逻辑:
// 1、重复性任务流程是
// a、注册重复任务
// b、时间抵达时控制器调用注册函数并发送通知
// c、如果次数达到限制则撤销否则控制器更新该任务的下次执行时间点
// d、控制器等待下一个最近需要执行的任务
// 2、一次性任务可以是服务运行时当前时间点之后的任意事件流程是
// a、注册一次性任务
// b、时间抵达时控制器调用注册函数并发送通知
// c、控制器释放该任务
// d、控制器等待下一个最近需要执行的任务
// 使用方式,参见示例代码。
package dtask
import (
"gitlab.33.cn/chat/im/dtask/pkg/rbtree"
"math"
"sync"
"sync/atomic"
"time"
)
/*
This code is based on the following resources:
source code: https://github.com/alex023/clock.git
*/
const _UNTOUCHED = time.Duration(math.MaxInt64)
var (
defaultClock *Clock
oncedo sync.Once
)
//Default return singal default clock
func Default() *Clock {
oncedo.Do(initClock)
return defaultClock
}
func initClock() {
defaultClock = NewClock()
}
// Clock is jobs schedule
type Clock struct {
seq uint64
count uint64 //已执行次数不得大于times
waitJobsNum uint64 //num of jobs which wait for action
jobQueue *rbtree.Rbtree //inner memory storage
pauseChan chan struct{}
resumeChan chan struct{}
exitChan chan struct{}
}
var singal = struct{}{}
//NewClock Create a task queue controller
func NewClock() *Clock {
c := &Clock{
jobQueue: rbtree.New(),
pauseChan: make(chan struct{}, 0),
resumeChan: make(chan struct{}, 0),
exitChan: make(chan struct{}, 0),
}
c.start()
return c
}
func (jl *Clock) start() {
now := time.Now()
untouchedJob := jobItem{
createTime: now,
intervalTime: time.Duration(math.MaxInt64),
fn: func() {
//this jobItem is untouched.
},
}
_, inserted := jl.addJob(now, untouchedJob.intervalTime, 1, untouchedJob.fn)
if !inserted {
panic("[clock] internal error.Reason cannot insert job.")
}
//开启守护协程
go jl.schedule()
jl.resume()
}
func (jl *Clock) pause() {
jl.pauseChan <- singal
}
func (jl *Clock) resume() {
jl.resumeChan <- singal
}
func (jl *Clock) exit() {
jl.exitChan <- singal
}
func (jl *Clock) immediate() {
for {
if item := jl.jobQueue.Min(); item != nil {
atomic.AddUint64(&jl.count, 1)
job := item.(*jobItem)
job.action(false)
jl.removeJob(job)
} else {
break
}
}
}
func (jl *Clock) schedule() {
var (
timeout time.Duration
job *jobItem
timer = newSafeTimer(_UNTOUCHED)
)
defer timer.Stop()
Pause:
<-jl.resumeChan
for {
job, _ = jl.jobQueue.Min().(*jobItem) //ignore ok-assert
timeout = job.actionTime.Sub(time.Now())
timer.SafeReset(timeout)
select {
case <-timer.C:
timer.SCR()
atomic.AddUint64(&jl.count, 1)
job.action(true)
if job.actionMax == 0 || job.actionMax > job.actionCount {
jl.jobQueue.Delete(job)
job.actionTime = job.actionTime.Add(job.intervalTime)
jl.jobQueue.Insert(job)
} else {
jl.removeJob(job)
}
case <-jl.pauseChan:
goto Pause
case <-jl.exitChan:
goto Exit
}
}
Exit:
}
// UpdateJobTimeout update a timed task with time duration after now
// @job: job identifier
// @actionTime: new job schedule time,must be greater than 0
func (jl *Clock) UpdateJobTimeout(job Job, actionTime time.Duration) (updated bool) {
if job == nil || actionTime.Nanoseconds() <= 0 || !job.isAvailable() {
return false
}
now := time.Now()
item, ok := job.(*jobItem)
if !ok {
return false
}
jl.pause()
defer jl.resume()
// update jobitem in job queue
jl.jobQueue.Delete(item)
item.actionTime = now.Add(actionTime)
jl.jobQueue.Insert(item)
updated = true
return
}
// AddJobWithInterval insert a timed task with time duration after now
// @actionTime: Duration after now
// @jobFunc: Callback function,not nil
// return
// @jobScheduled: A reference to a task that has been scheduled.
func (jl *Clock) AddJobWithInterval(actionInterval time.Duration, jobFunc func()) (jobScheduled Job, inserted bool) {
if jobFunc == nil || actionInterval.Nanoseconds() <= 0 {
return
}
now := time.Now()
jl.pause()
jobScheduled, inserted = jl.addJob(now, actionInterval, 1, jobFunc)
jl.resume()
return
}
// AddJobWithDeadtime insert a timed task with time point after now
// @actionTime: Execution start time. must after now
// @jobFunc: Callback function,not nil
// return
// @jobScheduled : A reference to a task that has been scheduled.
// @inserted : return false ,if actionTime before time.Now or jobFunc is nil
func (jl *Clock) AddJobWithDeadtime(actionTime time.Time, jobFunc func()) (jobScheduled Job, inserted bool) {
actionInterval := actionTime.Sub(time.Now())
if jobFunc == nil || actionInterval.Nanoseconds() <= 0 {
return
}
now := time.Now()
jl.pause()
jobScheduled, inserted = jl.addJob(now, actionInterval, 1, jobFunc)
jl.resume()
return
}
// AddJobRepeat add a repeat task with interval duration
// @interval: The interval between two actions of the job
// @actionMax: The number of job execution
// @jobFunc: Callback function,not nil
// return
// @jobScheduled : A reference to a task that has been scheduled.
// @inserted : return false ,if interval is not Positiveor jobFunc is nil
//Note
// when jobTimes==0,the job will be executed without limitation。If you no longer use, be sure to call the DelJob method to release
func (jl *Clock) AddJobRepeat(interval time.Duration, actionMax uint64, jobFunc func()) (jobScheduled Job, inserted bool) {
if jobFunc == nil || interval.Nanoseconds() <= 0 {
return
}
now := time.Now()
jl.pause()
jobScheduled, inserted = jl.addJob(now, interval, actionMax, jobFunc)
jl.resume()
return
}
func (jl *Clock) addJob(createTime time.Time, actionInterval time.Duration, actionMax uint64, jobFunc func()) (job *jobItem, inserted bool) {
jl.seq++
jl.waitJobsNum++
job = &jobItem{
id: jl.seq,
actionMax: actionMax,
createTime: createTime,
actionTime: createTime.Add(actionInterval),
intervalTime: actionInterval,
msgChan: make(chan Job, 10),
fn: jobFunc,
clock: jl,
}
jl.jobQueue.Insert(job)
inserted = true
return
}
func (jl *Clock) removeJob(item *jobItem) {
if jl.jobQueue.Delete(item) != nil {
jl.waitJobsNum--
//job.Cancel --> rmJob -->removeJob; schedule -->removeJob
//it is call repeatly when Job.Cancel
if atomic.CompareAndSwapInt32(&item.cancelFlag, 0, 1) {
item.innerCancel()
}
}
}
func (jl *Clock) rmJob(job *jobItem) {
jl.pause()
defer jl.resume()
jl.removeJob(job)
return
}
// Count 已经执行的任务数。对于重复任务,会计算多次
func (jl *Clock) Count() uint64 {
return atomic.LoadUint64(&jl.count)
}
//重置Clock的内部状态
func (jl *Clock) Reset() *Clock {
jl.exit()
jl.count = 0
jl.cleanJobs()
jl.start()
return jl
}
func (jl *Clock) cleanJobs() {
item := jl.jobQueue.Min()
for item != nil {
job, ok := item.(*jobItem)
if ok {
jl.removeJob(job)
}
item = jl.jobQueue.Min()
}
}
//WaitJobs get how much jobs waiting for call
func (jl *Clock) WaitJobs() uint64 {
jobs := atomic.LoadUint64(&jl.waitJobsNum) - 1
return jobs
}
//Stop stop clock , and cancel all waiting jobs
func (jl *Clock) Stop() {
jl.exit()
jl.cleanJobs()
}
//StopGracefull stop clock ,and do once every waiting job including Once\Reapeat
//Note:对于任务队列中,即使安排执行多次或者不限次数的,也仅仅执行一次。
func (jl *Clock) StopGraceful() {
jl.exit()
jl.immediate()
}