Skip to content

Commit f78b599

Browse files
committed
Fix cron for schedules that do not match any time
1 parent 6c39401 commit f78b599

File tree

2 files changed

+45
-25
lines changed

2 files changed

+45
-25
lines changed

common/backoff/cron.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,22 @@ import (
3636
// NoBackoff is used to represent backoff when no cron backoff is needed
3737
const NoBackoff = time.Duration(-1)
3838

39+
// InfiniteBackoff is used to represent a long backoff that in practice won't fire
40+
const InfiniteBackoff = 1<<63 - 1
41+
3942
// ValidateSchedule validates a cron schedule spec
4043
func ValidateSchedule(cronSchedule string) error {
4144
if cronSchedule == "" {
4245
return nil
4346
}
44-
if _, err := cron.ParseStandard(cronSchedule); err != nil {
45-
return serviceerror.NewInvalidArgument("Invalid CronSchedule.")
47+
schedule, err := cron.ParseStandard(cronSchedule)
48+
if err != nil {
49+
return serviceerror.NewInvalidArgument("invalid CronSchedule.")
50+
}
51+
nextTime := schedule.Next(time.Now().UTC())
52+
if nextTime.IsZero() {
53+
// no time can be found to satisfy the schedule
54+
return serviceerror.NewInvalidArgument("invalid CronSchedule, no time can be found to satisfy the schedule")
4655
}
4756
return nil
4857
}
@@ -68,9 +77,13 @@ func GetBackoffForNextSchedule(cronSchedule string, scheduledTime time.Time, now
6877
} else {
6978
nextScheduleTime = schedule.Next(scheduledUTCTime)
7079
// Calculate the next schedule start time which is nearest to now (right after now).
71-
for nextScheduleTime.Before(nowUTC) {
80+
for !nextScheduleTime.IsZero() && nextScheduleTime.Before(nowUTC) {
7281
nextScheduleTime = schedule.Next(nextScheduleTime)
7382
}
83+
if nextScheduleTime.IsZero() {
84+
// no time can be found to satisfy the schedule
85+
return InfiniteBackoff
86+
}
7487
}
7588

7689
backoffInterval := nextScheduleTime.Sub(nowUTC)

common/backoff/cron_test.go

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,30 @@ var crontests = []struct {
3737
scheduledTime string
3838
now string
3939
result time.Duration
40+
err string
4041
}{
41-
{"0 10 * * *", "2018-12-17T08:00:00-08:00", "", time.Hour * 18},
42-
{"0 10 * * *", "2018-12-18T02:00:00-08:00", "", time.Hour * 24},
43-
{"0 * * * *", "2018-12-17T08:08:00+00:00", "", time.Minute * 52},
44-
{"0 * * * *", "2018-12-17T09:00:00+00:00", "", time.Hour},
45-
{"* * * * *", "2018-12-17T08:08:18+00:00", "", time.Second * 42},
46-
{"0 * * * *", "2018-12-17T09:00:00+00:00", "", time.Minute * 60},
47-
{"0 10 * * *", "2018-12-17T08:00:00+00:00", "2018-12-20T00:00:00+00:00", time.Hour * 10},
48-
{"0 10 * * *", "2018-12-17T08:00:00+00:00", "2018-12-17T09:00:00+00:00", time.Hour},
49-
{"*/10 * * * *", "2018-12-17T00:04:00+00:00", "2018-12-17T01:02:00+00:00", time.Minute * 8},
50-
{"invalid-cron-spec", "2018-12-17T00:04:00+00:00", "2018-12-17T01:02:00+00:00", NoBackoff},
51-
{"@every 5h", "2018-12-17T08:00:00+00:00", "2018-12-17T09:00:00+00:00", time.Hour * 4},
52-
{"@every 5h", "2018-12-17T08:00:00+00:00", "2018-12-18T00:00:00+00:00", time.Hour * 4},
53-
{"0 3 * * 0-6", "2018-12-17T08:00:00-08:00", "", time.Hour * 11},
54-
{"@every 30s", "2020-07-17T09:00:02-01:00", "2020-07-17T09:00:02-01:00", time.Second * 30},
55-
{"@every 30s", "2020-07-17T09:00:02-01:00", "2020-09-17T03:00:53-01:00", time.Second * 9},
56-
{"@every 30m", "2020-07-17T09:00:00-01:00", "2020-07-17T08:45:00-01:00", time.Minute * 15},
42+
{"0 0 31 2 *", "2018-12-17T08:00:00-08:00", "", 0, "invalid CronSchedule, no time can be found to satisfy the schedule"},
43+
{"invalid-cron-spec", "2018-12-17T00:04:00+00:00", "2018-12-17T01:02:00+00:00", NoBackoff, "invalid CronSchedule"},
44+
45+
{"0 10 * * *", "2018-12-17T08:00:00-08:00", "", time.Hour * 18, ""},
46+
{"0 10 * * *", "2018-12-18T02:00:00-08:00", "", time.Hour * 24, ""},
47+
{"0 * * * *", "2018-12-17T08:08:00+00:00", "", time.Minute * 52, ""},
48+
{"0 * * * *", "2018-12-17T09:00:00+00:00", "", time.Hour, ""},
49+
{"* * * * *", "2018-12-17T08:08:18+00:00", "", time.Second * 42, ""},
50+
{"0 * * * *", "2018-12-17T09:00:00+00:00", "", time.Minute * 60, ""},
51+
{"0 10 * * *", "2018-12-17T08:00:00+00:00", "2018-12-20T00:00:00+00:00", time.Hour * 10, ""},
52+
{"0 10 * * *", "2018-12-17T08:00:00+00:00", "2018-12-17T09:00:00+00:00", time.Hour, ""},
53+
{"*/10 * * * *", "2018-12-17T00:04:00+00:00", "2018-12-17T01:02:00+00:00", time.Minute * 8, ""},
54+
{"@every 5h", "2018-12-17T08:00:00+00:00", "2018-12-17T09:00:00+00:00", time.Hour * 4, ""},
55+
{"@every 5h", "2018-12-17T08:00:00+00:00", "2018-12-18T00:00:00+00:00", time.Hour * 4, ""},
56+
{"0 3 * * 0-6", "2018-12-17T08:00:00-08:00", "", time.Hour * 11, ""},
57+
{"@every 30s", "2020-07-17T09:00:02-01:00", "2020-07-17T09:00:02-01:00", time.Second * 30, ""},
58+
{"@every 30s", "2020-07-17T09:00:02-01:00", "2020-09-17T03:00:53-01:00", time.Second * 9, ""},
59+
{"@every 30m", "2020-07-17T09:00:00-01:00", "2020-07-17T08:45:00-01:00", time.Minute * 15, ""},
5760
// At 16:05 East Coast (Day light saving on)
58-
{"CRON_TZ=America/New_York 5 16 * * *", "2021-03-14T00:00:00-04:00", "2021-03-14T15:05:00-04:00", time.Hour * 1},
61+
{"CRON_TZ=America/New_York 5 16 * * *", "2021-03-14T00:00:00-04:00", "2021-03-14T15:05:00-04:00", time.Hour * 1, ""},
5962
// At 04:05 East Coast (Day light saving off)
60-
{"CRON_TZ=America/New_York 5 4 * * *", "2021-11-25T00:00:00-05:00", "2021-11-25T03:05:00-05:00", time.Hour * 1},
63+
{"CRON_TZ=America/New_York 5 4 * * *", "2021-11-25T00:00:00-05:00", "2021-11-25T03:05:00-05:00", time.Hour * 1, ""},
6164
}
6265

6366
func TestCron(t *testing.T) {
@@ -71,11 +74,15 @@ func TestCron(t *testing.T) {
7174
assert.NoError(t, err, "Parse end time: %v", tt.now)
7275
}
7376
err = ValidateSchedule(tt.cron)
74-
if tt.result != NoBackoff {
75-
assert.NoError(t, err)
77+
if len(tt.err) > 0 {
78+
assert.Error(t, err)
79+
assert.Contains(t, err.Error(), tt.err)
80+
} else {
81+
assert.NoError(t, err, "Failed on cron schedule: %v", tt.cron)
82+
83+
backoff := GetBackoffForNextSchedule(tt.cron, start, end)
84+
assert.Equal(t, tt.result, backoff, "The cron spec is %s and the expected result is %s", tt.cron, tt.result)
7685
}
77-
backoff := GetBackoffForNextSchedule(tt.cron, start, end)
78-
assert.Equal(t, tt.result, backoff, "The cron spec is %s and the expected result is %s", tt.cron, tt.result)
7986
})
8087
}
8188
}

0 commit comments

Comments
 (0)