Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle occasional occurence of double run on daily job #159

Merged
merged 5 commits into from
Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 23 additions & 15 deletions scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,31 +214,31 @@ func (s *Scheduler) calculateMonths(job *Job, lastRun time.Time) time.Duration {
daysDifference := int(math.Abs(lastRun.Sub(jobDay).Hours()) / 24)
nextRun := s.roundToMidnight(lastRun).Add(job.getAtTime())
if jobDay.Before(lastRun) { // shouldn't run this month; schedule for next interval minus day difference
nextRun = nextRun.AddDate(0, int(job.interval), -daysDifference)
nextRun = nextRun.AddDate(0, job.interval, -daysDifference)
} else {
if job.interval == 1 { // every month counts current month
nextRun = nextRun.AddDate(0, int(job.interval)-1, daysDifference)
nextRun = nextRun.AddDate(0, job.interval-1, daysDifference)
} else { // should run next month interval
nextRun = nextRun.AddDate(0, int(job.interval), daysDifference)
nextRun = nextRun.AddDate(0, job.interval, daysDifference)
}
}
return s.until(lastRun, nextRun)
return until(lastRun, nextRun)
}
nextRun := lastRunRoundedMidnight.Add(job.getAtTime()).AddDate(0, int(job.interval), 0)
return s.until(lastRunRoundedMidnight, nextRun)
nextRun := lastRunRoundedMidnight.Add(job.getAtTime()).AddDate(0, job.interval, 0)
return until(lastRunRoundedMidnight, nextRun)
}

func (s *Scheduler) calculateWeekday(job *Job, lastRun time.Time) time.Duration {
daysToWeekday := remainingDaysToWeekday(lastRun.Weekday(), *job.scheduledWeekday)
totalDaysDifference := s.calculateTotalDaysDifference(lastRun, daysToWeekday, job)
nextRun := s.roundToMidnight(lastRun).Add(job.getAtTime()).AddDate(0, 0, totalDaysDifference)
return s.until(lastRun, nextRun)
return until(lastRun, nextRun)
}

func (s *Scheduler) calculateWeeks(job *Job, lastRun time.Time) time.Duration {
totalDaysDifference := int(job.interval) * 7
nextRun := s.roundToMidnight(lastRun).Add(job.getAtTime()).AddDate(0, 0, totalDaysDifference)
return s.until(lastRun, nextRun)
return until(lastRun, nextRun)
}

func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekday int, job *Job) int {
Expand All @@ -258,18 +258,26 @@ func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekda
}

func (s *Scheduler) calculateDays(job *Job, lastRun time.Time) time.Duration {

if job.interval == 1 {
lastRunDayPlusJobAtTime := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), 0, 0, 0, 0, s.Location()).Add(job.getAtTime())

// handle occasional occurrence of job running to quickly / too early such that last run was within a second of now
lastRunUnix, nowUnix := job.LastRun().Unix(), s.now().Unix()
if lastRunUnix == nowUnix || lastRunUnix == nowUnix-1 || lastRunUnix == nowUnix+1 {
lastRun = lastRunDayPlusJobAtTime
}

if shouldRunToday(lastRun, lastRunDayPlusJobAtTime) {
return s.until(lastRun, s.roundToMidnight(lastRun).Add(job.getAtTime()))
return until(lastRun, s.roundToMidnight(lastRun).Add(job.getAtTime()))
}
}

nextRunAtTime := s.roundToMidnight(lastRun).Add(job.getAtTime()).AddDate(0, 0, int(job.interval)).In(s.Location())
return s.until(lastRun, nextRunAtTime)
nextRunAtTime := s.roundToMidnight(lastRun).Add(job.getAtTime()).AddDate(0, 0, job.interval).In(s.Location())
return until(lastRun, nextRunAtTime)
}

func (s *Scheduler) until(from time.Time, until time.Time) time.Duration {
func until(from time.Time, until time.Time) time.Duration {
return until.Sub(from)
}

Expand Down Expand Up @@ -477,7 +485,7 @@ func (s *Scheduler) RemoveByTag(tag string) error {
}

func (s *Scheduler) findJobsByTag(tag string) ([]*Job, error) {
jobs := []*Job{}
var jobs []*Job
for _, job := range s.Jobs() {
if strings.Contains(strings.Join(job.Tags(), " "), tag) {
jobs = append(jobs, job)
Expand Down Expand Up @@ -658,12 +666,12 @@ func (s *Scheduler) setUnit(unit schedulingUnit) {
job.setUnit(unit)
}

// Second sets the unit with seconds
// Millisecond sets the unit with seconds
func (s *Scheduler) Millisecond() *Scheduler {
return s.Milliseconds()
}

// Seconds sets the unit with seconds
// Milliseconds sets the unit with seconds
func (s *Scheduler) Milliseconds() *Scheduler {
s.setUnit(milliseconds)
return s
Expand Down
8 changes: 8 additions & 0 deletions scheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ func TestAt(t *testing.T) {

select {
case <-time.After(1 * time.Second):
log.Println(now.Add(time.Minute))
log.Println(dayJob.nextRun)
assert.Equal(t, now.Add(1*time.Minute), dayJob.nextRun)
case <-semaphore:
t.Fatal("job ran even though scheduled in future")
Expand Down Expand Up @@ -618,6 +620,10 @@ func TestScheduler_StartAt(t *testing.T) {
}

func TestScheduler_CalculateNextRun(t *testing.T) {
ft := fakeTime{onNow: func(l *time.Location) time.Time {
return time.Date(1970, 1, 1, 12, 0, 0, 0, l)
}}

day := time.Hour * 24
januaryFirst2020At := func(hour, minute, second int) time.Time {
return time.Date(2020, time.January, 1, hour, minute, second, 0, time.UTC)
Expand Down Expand Up @@ -649,6 +655,7 @@ func TestScheduler_CalculateNextRun(t *testing.T) {
{name: "daily job just ran at 5:30AM and should be scheduled for today at 8:30AM", job: &Job{interval: 1, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(5, 30, 0)}, wantTimeUntilNextRun: 3 * time.Hour},
{name: "job runs every 2 days, just ran at 5:30AM and should be scheduled for 2 days at 8:30AM", job: &Job{interval: 2, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(5, 30, 0)}, wantTimeUntilNextRun: (2 * day) + 3*time.Hour},
{name: "job runs every 2 days, just ran at 8:30AM and should be scheduled for 2 days at 8:30AM", job: &Job{interval: 2, unit: days, atTime: 8*time.Hour + 30*time.Minute, lastRun: januaryFirst2020At(8, 30, 0)}, wantTimeUntilNextRun: 2 * day},
{name: "daily, last run was 1 second ago", job: &Job{interval: 1, unit: days, atTime: 12 * time.Hour, lastRun: ft.Now(time.UTC).Add(-time.Second)}, wantTimeUntilNextRun: 1 * day},
//// WEEKS
{name: "every week should run in 7 days", job: &Job{interval: 1, unit: weeks, lastRun: januaryFirst2020At(0, 0, 0)}, wantTimeUntilNextRun: 7 * day},
{name: "every week with .At time rule should run respect .At time rule", job: &Job{interval: 1, atTime: _getHours(9) + _getMinutes(30), unit: weeks, lastRun: januaryFirst2020At(9, 30, 0)}, wantTimeUntilNextRun: 7 * day},
Expand Down Expand Up @@ -679,6 +686,7 @@ func TestScheduler_CalculateNextRun(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
s := NewScheduler(time.UTC)
s.time = ft
got := s.durationToNextRun(tc.job.LastRun(), tc.job)
assert.Equalf(t, tc.wantTimeUntilNextRun, got, fmt.Sprintf("expected %s / got %s", tc.wantTimeUntilNextRun.String(), got.String()))
})
Expand Down