Skip to content

Commit 602934f

Browse files
authored
Merge pull request #23 from go-co-op/scheduler-bug
fix bug in scheduler, rescheduling jobs immediately
2 parents ced6391 + 2874920 commit 602934f

File tree

4 files changed

+49
-36
lines changed

4 files changed

+49
-36
lines changed

job.go

+1-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ type Job struct {
2020
fparams map[string][]interface{} // Map for function and params of function
2121
lock bool // lock the Job from running at same time form multiple instances
2222
tags []string // allow the user to tag Jobs with certain labels
23-
time timeHelper // an instance of timeHelper to interact with the time package
2423
}
2524

2625
// NewJob creates a new Job with the provided interval
@@ -34,19 +33,12 @@ func NewJob(interval uint64) *Job {
3433
funcs: make(map[string]interface{}),
3534
fparams: make(map[string][]interface{}),
3635
tags: []string{},
37-
time: th,
3836
}
3937
}
4038

41-
// shouldRun returns true if the Job should be run now
42-
func (j *Job) shouldRun() bool {
43-
return j.time.Now().Unix() >= j.nextRun.Unix()
44-
}
45-
4639
// Run the Job and immediately reschedule it
4740
func (j *Job) run() {
48-
j.lastRun = j.time.Now()
49-
go callJobFuncWithParams(j.funcs[j.jobFunc], j.fparams[j.jobFunc])
41+
callJobFuncWithParams(j.funcs[j.jobFunc], j.fparams[j.jobFunc])
5042
}
5143

5244
// Err returns an error if one ocurred while creating the Job

scheduler.go

+40-20
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,7 @@ func (s *Scheduler) SetLocation(newLocation *time.Location) {
7070

7171
// scheduleNextRun Compute the instant when this Job should run next
7272
func (s *Scheduler) scheduleNextRun(j *Job) error {
73-
now := s.time.Now().In(s.loc)
74-
if j.lastRun == s.time.Unix(0, 0) {
75-
j.lastRun = now
76-
}
77-
78-
if j.nextRun.After(now) {
79-
return nil
80-
}
73+
now := s.time.Now(s.loc)
8174

8275
periodDuration, err := j.periodDuration()
8376
if err != nil {
@@ -89,7 +82,7 @@ func (s *Scheduler) scheduleNextRun(j *Job) error {
8982
j.nextRun = j.lastRun.Add(periodDuration)
9083
case days:
9184
j.nextRun = s.roundToMidnight(j.lastRun)
92-
j.nextRun = j.nextRun.Add(j.atTime)
85+
j.nextRun = j.nextRun.Add(j.atTime).Add(periodDuration)
9386
case weeks:
9487
j.nextRun = s.roundToMidnight(j.lastRun)
9588
dayDiff := int(j.startDay)
@@ -118,7 +111,7 @@ func (s *Scheduler) getRunnableJobs() []*Job {
118111
var runnableJobs []*Job
119112
sort.Sort(s)
120113
for _, job := range s.jobs {
121-
if job.shouldRun() {
114+
if s.shouldRun(job) {
122115
runnableJobs = append(runnableJobs, job)
123116
} else {
124117
break
@@ -130,7 +123,7 @@ func (s *Scheduler) getRunnableJobs() []*Job {
130123
// NextRun datetime when the next Job should run.
131124
func (s *Scheduler) NextRun() (*Job, time.Time) {
132125
if len(s.jobs) <= 0 {
133-
return nil, s.time.Now()
126+
return nil, s.time.Now(s.loc)
134127
}
135128
sort.Sort(s)
136129
return s.jobs[0], s.jobs[0].nextRun
@@ -147,11 +140,21 @@ func (s *Scheduler) Every(interval uint64) *Scheduler {
147140
func (s *Scheduler) RunPending() {
148141
runnableJobs := s.getRunnableJobs()
149142
for _, job := range runnableJobs {
150-
s.runJob(job)
143+
s.runAndReschedule(job) // we should handle this error somehow
151144
}
152145
}
153146

154-
func (s *Scheduler) runJob(job *Job) error {
147+
func (s *Scheduler) runAndReschedule(job *Job) error {
148+
if err := s.run(job); err != nil {
149+
return err
150+
}
151+
if err := s.scheduleNextRun(job); err != nil {
152+
return err
153+
}
154+
return nil
155+
}
156+
157+
func (s *Scheduler) run(job *Job) error {
155158
if job.lock {
156159
if locker == nil {
157160
return fmt.Errorf("trying to lock %s with nil locker", job.jobFunc)
@@ -161,11 +164,10 @@ func (s *Scheduler) runJob(job *Job) error {
161164
locker.Lock(key)
162165
defer locker.Unlock(key)
163166
}
164-
job.run()
165-
err := s.scheduleNextRun(job)
166-
if err != nil {
167-
return err
168-
}
167+
168+
job.lastRun = s.time.Now(s.loc)
169+
go job.run()
170+
169171
return nil
170172
}
171173

@@ -177,7 +179,7 @@ func (s *Scheduler) RunAll() {
177179
// RunAllWithDelay runs all Jobs with delay seconds
178180
func (s *Scheduler) RunAllWithDelay(d int) {
179181
for _, job := range s.jobs {
180-
err := s.runJob(job)
182+
err := s.run(job)
181183
if err != nil {
182184
continue
183185
}
@@ -253,6 +255,19 @@ func (s *Scheduler) Do(jobFun interface{}, params ...interface{}) (*Job, error)
253255
j.jobFunc = fname
254256

255257
if !j.startsImmediately {
258+
periodDuration, err := j.periodDuration()
259+
if err != nil {
260+
return nil, err
261+
}
262+
263+
if j.lastRun == s.time.Unix(0, 0) {
264+
j.lastRun = s.time.Now(s.loc)
265+
266+
if j.atTime != 0 {
267+
j.lastRun = j.lastRun.Add(-periodDuration)
268+
}
269+
}
270+
256271
if err := s.scheduleNextRun(j); err != nil {
257272
return nil, err
258273
}
@@ -283,11 +298,16 @@ func (s *Scheduler) StartAt(t time.Time) *Scheduler {
283298
// StartImmediately sets the Jobs next run as soon as the scheduler starts
284299
func (s *Scheduler) StartImmediately() *Scheduler {
285300
job := s.getCurrentJob()
286-
job.nextRun = s.time.Now().In(s.loc)
301+
job.nextRun = s.time.Now(s.loc)
287302
job.startsImmediately = true
288303
return s
289304
}
290305

306+
// shouldRun returns true if the Job should be run now
307+
func (s *Scheduler) shouldRun(j *Job) bool {
308+
return s.time.Now(s.loc).Unix() >= j.nextRun.Unix()
309+
}
310+
291311
// setUnit sets the unit type
292312
func (s *Scheduler) setUnit(unit timeUnit) {
293313
currentJob := s.getCurrentJob()

scheduler_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestExecutionSecond(t *testing.T) {
2929
func TestExecutionSeconds(t *testing.T) {
3030
sched := NewScheduler(time.UTC)
3131
jobDone := make(chan bool)
32-
executionTimes := make([]int64, 0, 2)
32+
var executionTimes []int64
3333
numberOfIterations := 2
3434

3535
sched.Every(2).Seconds().Do(func() {
@@ -75,18 +75,18 @@ func TestStartImmediately(t *testing.T) {
7575
func TestAt(t *testing.T) {
7676
s := NewScheduler(time.UTC)
7777

78-
// Schedule to run in next minute
78+
// Schedule to run in next 2 seconds
7979
now := time.Now().UTC()
8080
dayJobDone := make(chan bool, 1)
8181

8282
// Schedule every day At
83-
startAt := fmt.Sprintf("%02d:%02d", now.Hour(), now.Add(time.Minute).Minute())
83+
startAt := fmt.Sprintf("%02d:%02d:%02d", now.Hour(), now.Minute(), now.Add(time.Second*2).Second())
8484
dayJob, _ := s.Every(1).Day().At(startAt).Do(func() {
8585
dayJobDone <- true
8686
})
8787

8888
// Expected start time
89-
expectedStartTime := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Add(time.Minute).Minute(), 0, 0, time.UTC)
89+
expectedStartTime := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Add(time.Second*2).Second(), 0, time.UTC)
9090
nextRun := dayJob.NextScheduledTime()
9191
assert.Equal(t, expectedStartTime, nextRun)
9292

timeHelper.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package gocron
33
import "time"
44

55
type timeHelper interface {
6-
Now() time.Time
6+
Now(*time.Location) time.Time
77
Unix(int64, int64) time.Time
88
Sleep(time.Duration)
99
Date(int, time.Month, int, int, int, int, int, *time.Location) time.Time
@@ -16,8 +16,9 @@ func newTimeHelper() timeHelper {
1616

1717
type trueTime struct{}
1818

19-
func (t *trueTime) Now() time.Time {
20-
return time.Now()
19+
func (t *trueTime) Now(location *time.Location) time.Time {
20+
n := time.Now().In(location)
21+
return t.Date(n.Year(), n.Month(), n.Day(), n.Hour(), n.Minute(), n.Second(), 0, location)
2122
}
2223

2324
func (t *trueTime) Unix(sec int64, nsec int64) time.Time {

0 commit comments

Comments
 (0)