init
This commit is contained in:
327
dtask/clock.go
Normal file
327
dtask/clock.go
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user