|
@@ -19,6 +19,7 @@ import (
|
|
"fmt"
|
|
"fmt"
|
|
"math"
|
|
"math"
|
|
"math/rand"
|
|
"math/rand"
|
|
|
|
+ "strings"
|
|
"sync"
|
|
"sync"
|
|
"time"
|
|
"time"
|
|
|
|
|
|
@@ -262,6 +263,17 @@ func (rs *RuleState) startScheduleRule() error {
|
|
}
|
|
}
|
|
var cronCtx context.Context
|
|
var cronCtx context.Context
|
|
cronCtx, rs.cronState.cancel = context.WithCancel(context.Background())
|
|
cronCtx, rs.cronState.cancel = context.WithCancel(context.Background())
|
|
|
|
+ now := conf.GetNow()
|
|
|
|
+ isInRunningSchedule, remainedDuration, err := rs.isInRunningSchedule(now, d)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ if isInRunningSchedule {
|
|
|
|
+ if err := rs.runScheduleRule(); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ rs.stopAfterDuration(remainedDuration, cronCtx)
|
|
|
|
+ }
|
|
entryID, err := backgroundCron.AddFunc(rs.Rule.Options.Cron, func() {
|
|
entryID, err := backgroundCron.AddFunc(rs.Rule.Options.Cron, func() {
|
|
var started bool
|
|
var started bool
|
|
var err error
|
|
var err error
|
|
@@ -274,16 +286,9 @@ func (rs *RuleState) startScheduleRule() error {
|
|
defer rs.Unlock()
|
|
defer rs.Unlock()
|
|
}
|
|
}
|
|
now := conf.GetNow()
|
|
now := conf.GetNow()
|
|
- var err error
|
|
|
|
- allowed := true
|
|
|
|
- for _, timeRange := range rs.Rule.Options.CronDatetimeRange {
|
|
|
|
- allowed, err = isInScheduleRange(now, timeRange.Begin, timeRange.End)
|
|
|
|
- if err != nil {
|
|
|
|
- return false, err
|
|
|
|
- }
|
|
|
|
- if allowed {
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
|
|
+ allowed, err := rs.isInAllowedTimeRange(now)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return false, err
|
|
}
|
|
}
|
|
if !allowed {
|
|
if !allowed {
|
|
return false, nil
|
|
return false, nil
|
|
@@ -300,20 +305,7 @@ func (rs *RuleState) startScheduleRule() error {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
if started {
|
|
if started {
|
|
- after := time.After(d)
|
|
|
|
- go func(ctx context.Context) {
|
|
|
|
- select {
|
|
|
|
- case <-after:
|
|
|
|
- rs.Lock()
|
|
|
|
- defer rs.Unlock()
|
|
|
|
- if err := rs.stop(); err != nil {
|
|
|
|
- conf.Log.Errorf("close rule %s failed, err: %v", rs.RuleId, err)
|
|
|
|
- }
|
|
|
|
- return
|
|
|
|
- case <-cronCtx.Done():
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
- }(cronCtx)
|
|
|
|
|
|
+ rs.stopAfterDuration(d, cronCtx)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -324,6 +316,35 @@ func (rs *RuleState) startScheduleRule() error {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (rs *RuleState) runScheduleRule() error {
|
|
|
|
+ rs.Lock()
|
|
|
|
+ defer rs.Unlock()
|
|
|
|
+ rs.cronState.cron = rs.Rule.Options.Cron
|
|
|
|
+ rs.cronState.duration = rs.Rule.Options.Duration
|
|
|
|
+ err := rs.start()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (rs *RuleState) stopAfterDuration(d time.Duration, cronCtx context.Context) {
|
|
|
|
+ after := time.After(d)
|
|
|
|
+ go func(ctx context.Context) {
|
|
|
|
+ select {
|
|
|
|
+ case <-after:
|
|
|
|
+ rs.Lock()
|
|
|
|
+ defer rs.Unlock()
|
|
|
|
+ if err := rs.stop(); err != nil {
|
|
|
|
+ conf.Log.Errorf("close rule %s failed, err: %v", rs.RuleId, err)
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+ case <-cronCtx.Done():
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }(cronCtx)
|
|
|
|
+}
|
|
|
|
+
|
|
func (rs *RuleState) start() error {
|
|
func (rs *RuleState) start() error {
|
|
if rs.triggered != 1 {
|
|
if rs.triggered != 1 {
|
|
// If the rule has been stopped due to error, the topology is not nil
|
|
// If the rule has been stopped due to error, the topology is not nil
|
|
@@ -484,3 +505,48 @@ func isAfterTimeRange(now time.Time, end string) (bool, error) {
|
|
}
|
|
}
|
|
return now.After(e), nil
|
|
return now.After(e), nil
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+func (rs *RuleState) isInRunningSchedule(now time.Time, d time.Duration) (bool, time.Duration, error) {
|
|
|
|
+ allowed, err := rs.isInAllowedTimeRange(now)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return false, 0, err
|
|
|
|
+ }
|
|
|
|
+ if !allowed {
|
|
|
|
+ return false, 0, nil
|
|
|
|
+ }
|
|
|
|
+ cronExpr := rs.Rule.Options.Cron
|
|
|
|
+ if strings.HasPrefix(cronExpr, "mock") {
|
|
|
|
+ return false, 0, nil
|
|
|
|
+ }
|
|
|
|
+ return isInRunningSchedule(cronExpr, now, d)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (rs *RuleState) isInAllowedTimeRange(now time.Time) (bool, error) {
|
|
|
|
+ allowed := true
|
|
|
|
+ var err error
|
|
|
|
+ for _, timeRange := range rs.Rule.Options.CronDatetimeRange {
|
|
|
|
+ allowed, err = isInScheduleRange(now, timeRange.Begin, timeRange.End)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return false, err
|
|
|
|
+ }
|
|
|
|
+ if allowed {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return allowed, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// isInRunningSchedule checks whether the rule should be running, eg:
|
|
|
|
+// If the duration is 10min, and cron is "0 0 * * *", and the current time is 00:00:02
|
|
|
|
+// And the rule should be started immediately instead of checking it on the next day.
|
|
|
|
+func isInRunningSchedule(cronExpr string, now time.Time, d time.Duration) (bool, time.Duration, error) {
|
|
|
|
+ s, err := cron.ParseStandard(cronExpr)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return false, 0, err
|
|
|
|
+ }
|
|
|
|
+ previousSchedule := s.Next(now.Add(-d))
|
|
|
|
+ if now.After(previousSchedule) && now.Before(previousSchedule.Add(d)) {
|
|
|
|
+ return true, previousSchedule.Add(d).Sub(now), nil
|
|
|
|
+ }
|
|
|
|
+ return false, 0, nil
|
|
|
|
+}
|