343 lines
7.6 KiB
Go
343 lines
7.6 KiB
Go
package crontab
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"474420502.top/eson/structure/circular_linked"
|
|
"github.com/Pallinder/go-randomdata"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
)
|
|
|
|
// StatusType 设置状态的类型
|
|
type StatusType int
|
|
|
|
const (
|
|
_ StatusType = iota
|
|
// SExecuteOK 设置这个次成功或者失败的状态
|
|
SExecuteOK
|
|
// SExecuteSleep 设置下次Sleep时间, 只影响一次
|
|
SExecuteSleep
|
|
// SExecuteCrontab 设置改写Crontab, 覆盖以前的Crontab
|
|
SExecuteCrontab
|
|
)
|
|
|
|
// Force 强制下次执行
|
|
type Force struct {
|
|
sleep time.Duration
|
|
}
|
|
|
|
// NextTime 下次执行时间
|
|
func (force *Force) NextTime() time.Time {
|
|
return time.Now().Add(force.sleep)
|
|
}
|
|
|
|
// Crontab 的string解析
|
|
type Crontab struct {
|
|
crontab string
|
|
|
|
force *Force
|
|
|
|
min []timePointer
|
|
hour []timePointer
|
|
day []timePointer
|
|
month []timePointer
|
|
week []timePointer
|
|
|
|
WillPlans []time.Time
|
|
SkipPlans []time.Time
|
|
|
|
YearPlan *trieYear
|
|
|
|
interval *clinked.CircularLinked
|
|
lastStatus bool
|
|
isCalculated bool
|
|
|
|
trueCount int
|
|
failCount int
|
|
nextTime time.Time
|
|
}
|
|
|
|
// NewCrontab create 一个crontab
|
|
func NewCrontab(crontab string) *Crontab {
|
|
cron := &Crontab{}
|
|
cron.crontab = strings.TrimSpace(crontab)
|
|
cron.FromString(cron.crontab)
|
|
return cron
|
|
}
|
|
|
|
// UnmarshalYAML 添加序列化接口 Marshal没实现, 需要的时候可以自己添加
|
|
func (cron *Crontab) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
var buf string
|
|
err := unmarshal(&buf)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
if err := cron.FromString(buf); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetStatus 设置上次状态 true false
|
|
func (cron *Crontab) SetStatus(statusType StatusType, statusValue ...interface{}) {
|
|
|
|
switch statusType {
|
|
case SExecuteOK:
|
|
if cron.interval != nil {
|
|
cron.lastStatus = statusValue[0].(bool)
|
|
}
|
|
case SExecuteSleep:
|
|
force := new(Force)
|
|
ivalue := statusValue[0]
|
|
switch value := ivalue.(type) {
|
|
case int:
|
|
force.sleep = time.Duration(value) * time.Second
|
|
case int64:
|
|
force.sleep = time.Duration(value) * time.Second
|
|
case time.Duration:
|
|
force.sleep = value
|
|
default:
|
|
panic(errors.New("statusValue type is error, the type is" + reflect.TypeOf(ivalue).String()))
|
|
}
|
|
cron.force = force
|
|
case SExecuteCrontab:
|
|
crontab := statusValue[0].(string)
|
|
cron.crontab = strings.TrimSpace(crontab)
|
|
cron.FromString(cron.crontab)
|
|
default:
|
|
panic(errors.New("StatusType is unknown, the type " + reflect.TypeOf(statusType).String()))
|
|
}
|
|
|
|
}
|
|
|
|
// // GetStatus 获取上次状态 true false
|
|
// func (cron *Crontab) GetStatus() (status bool) {
|
|
// return cron.lastStatus
|
|
// }
|
|
|
|
// TimeUp 是否时间快到, 时间间隔调用完TimeUp后必须调用NextTime, 为了精准控制时间
|
|
func (cron *Crontab) TimeUp() bool {
|
|
|
|
if cron.interval != nil {
|
|
return cron.intervalTimeUp()
|
|
}
|
|
|
|
return cron.linuxTimeUp()
|
|
}
|
|
|
|
// NextTime 返回下次任务的时间
|
|
func (cron *Crontab) NextTime() time.Time {
|
|
|
|
if cron.force != nil {
|
|
nt := cron.force.NextTime()
|
|
cron.force = nil
|
|
return nt
|
|
}
|
|
|
|
if cron.interval != nil {
|
|
if !cron.isCalculated {
|
|
now := time.Now()
|
|
cron.intervalCalculateNextTime(now)
|
|
}
|
|
return cron.nextTime
|
|
}
|
|
|
|
if len(cron.WillPlans) > 0 {
|
|
return cron.WillPlans[0]
|
|
}
|
|
|
|
return time.Now().Add(time.Second * 2)
|
|
}
|
|
|
|
func (cron *Crontab) String() string {
|
|
return fmt.Sprintf("min:%s\nhour:%s\nday:%s\nmonth:%s\nweek:%s\n", spew.Sdump(cron.min), spew.Sdump(cron.hour), spew.Sdump(cron.day), spew.Sdump(cron.month), spew.Sdump(cron.week))
|
|
}
|
|
|
|
// FromString 解析crontab 的 表达式
|
|
func (cron *Crontab) FromString(crontab string) error {
|
|
crontab = cron.crontab
|
|
|
|
cron.interval = nil
|
|
cron.min = nil
|
|
cron.hour = nil
|
|
cron.day = nil
|
|
cron.month = nil
|
|
cron.week = nil
|
|
|
|
cron.WillPlans = nil
|
|
cron.SkipPlans = nil
|
|
|
|
cron.YearPlan = nil
|
|
|
|
matches := regexp.MustCompile("[^ ]+").FindAllString(crontab, -1)
|
|
mlen := len(matches)
|
|
switch mlen {
|
|
case 1:
|
|
// "f1-2|5-10x5,f1|10m,10-15,f1"
|
|
|
|
cron.lastStatus = true
|
|
cron.isCalculated = true
|
|
cron.nextTime = time.Now()
|
|
|
|
cron.interval = clinked.NewCircularLinked()
|
|
var intervalList []interface{}
|
|
intervalList = parseIntervalString(matches[0])
|
|
cron.interval.Append(intervalList...)
|
|
|
|
case 5:
|
|
cron.min = createTimePointer(matches[0], 0, 59, true)
|
|
cron.hour = createTimePointer(matches[1], 0, 23, true)
|
|
cron.day = createTimePointer(matches[2], 1, 31, false)
|
|
cron.month = createTimePointer(matches[3], 1, 12, true)
|
|
cron.week = createTimePointer(matches[4], 0, 6, true)
|
|
|
|
cron.createYearPlan()
|
|
cron.TimeUp()
|
|
default:
|
|
return errors.New("mathches len != want, check crontab string")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createYearPlan 创建年度计划
|
|
func (cron *Crontab) createYearPlan() {
|
|
cron.YearPlan = newTrieYear()
|
|
cron.YearPlan.FromCrontab(cron)
|
|
}
|
|
|
|
func (cron *Crontab) linuxTimeUp() bool {
|
|
now := time.Now()
|
|
maxlen := 1000
|
|
createlen := 500
|
|
|
|
plen := len(cron.WillPlans)
|
|
if plen <= createlen { // 如果当前生成的计划表少于 限制的500. 就生成新的计划表
|
|
var lastplan time.Time
|
|
if plen == 0 {
|
|
lastplan = now
|
|
} else {
|
|
lastplan = cron.WillPlans[plen-1].Add(time.Minute)
|
|
}
|
|
if !cron.YearPlan.CheckYear() {
|
|
cron.createYearPlan()
|
|
}
|
|
|
|
timeplans := cron.YearPlan.GetPlanTime(cron, lastplan, uint(maxlen-plen))
|
|
cron.WillPlans = append(cron.WillPlans, timeplans...)
|
|
}
|
|
|
|
if len(cron.WillPlans) > 0 {
|
|
istimeup := false
|
|
for i := 0; i < maxlen; i++ {
|
|
|
|
// 统计过了多少计划任务时间表 i - 1
|
|
if now.Unix() >= cron.WillPlans[i].Unix() {
|
|
istimeup = true
|
|
} else {
|
|
|
|
// 清除过时的计划任务时间表
|
|
if istimeup {
|
|
if i-1 > 0 {
|
|
cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans[0:i-1]...)
|
|
if len(cron.SkipPlans) >= maxlen+200 {
|
|
cron.SkipPlans = cron.SkipPlans[200:]
|
|
}
|
|
}
|
|
cron.WillPlans = cron.WillPlans[i:]
|
|
return istimeup
|
|
}
|
|
|
|
return istimeup
|
|
}
|
|
}
|
|
// 如果所有计划表都不符合, 全部放到忽略的计划表上, 这个表方便打印查看, 因为程序执行超时过了多少计划任务
|
|
cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans...)
|
|
cron.WillPlans = nil
|
|
return istimeup
|
|
}
|
|
|
|
log.Panicln("error willplans range")
|
|
return false
|
|
}
|
|
|
|
// IntervalCalculateNextTime 计算时间间隔的下次时间
|
|
func (cron *Crontab) intervalCalculateNextTime(now time.Time) {
|
|
iv := cron.interval.Cursor().GetValue().(*hInterval)
|
|
isecond := 0
|
|
|
|
if iv.PlanFailCount.Size() == 0 && len(iv.PlanFail) == 0 {
|
|
cron.lastStatus = true
|
|
}
|
|
|
|
if cron.lastStatus {
|
|
|
|
cron.trueCount++
|
|
cron.failCount = 0
|
|
|
|
isecond = intervalPriorityListISecond(&iv.PlanTrueCount, cron.trueCount)
|
|
if isecond == -1 {
|
|
if len(iv.PlanTrue) > 0 {
|
|
idx := randomdata.Number(len(iv.PlanTrue))
|
|
lr := iv.PlanTrue[idx]
|
|
isecond = randomdata.Number(lr.left, lr.right+1)
|
|
|
|
} else {
|
|
isecond = 0
|
|
}
|
|
|
|
}
|
|
|
|
log.Println("success:", cron.trueCount, "count time wait:", isecond, "s")
|
|
|
|
} else {
|
|
|
|
cron.failCount++
|
|
cron.trueCount = 0
|
|
|
|
isecond = intervalPriorityListISecond(&iv.PlanFailCount, cron.failCount)
|
|
if isecond == -1 {
|
|
if len(iv.PlanFail) > 0 {
|
|
idx := randomdata.Number(len(iv.PlanFail))
|
|
lr := iv.PlanFail[idx]
|
|
isecond = randomdata.Number(lr.left, lr.right+1)
|
|
} else {
|
|
isecond = 0
|
|
}
|
|
}
|
|
|
|
log.Println("fail:", cron.failCount, "count time wait:", isecond, "s")
|
|
}
|
|
|
|
iv.Count--
|
|
if iv.Count <= 0 {
|
|
iv.reset()
|
|
cron.interval.MoveNext()
|
|
}
|
|
|
|
cron.isCalculated = true
|
|
cron.nextTime = now.Add(time.Duration(isecond) * time.Second)
|
|
}
|
|
|
|
func (cron *Crontab) intervalTimeUp() bool {
|
|
|
|
if cron.isCalculated { // 需要调用nexttime()才能正常正常计算下次的时间
|
|
now := time.Now()
|
|
if now.Unix() >= cron.nextTime.Unix() {
|
|
cron.isCalculated = false
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|