Skip to content

Commit 2ae65ff

Browse files
authored
Merge pull request robfig#103 from bgaifullin/master
Some enhancements
2 parents df38d32 + 316e2a2 commit 2ae65ff

File tree

3 files changed

+86
-62
lines changed

3 files changed

+86
-62
lines changed

cron.go

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,11 @@ func (c *Cron) runWithRecovery(j Job) {
165165
j.Run()
166166
}
167167

168-
// Run the scheduler.. this is private just due to the need to synchronize
168+
// Run the scheduler. this is private just due to the need to synchronize
169169
// access to the 'running' state variable.
170170
func (c *Cron) run() {
171171
// Figure out the next activation times for each entry.
172-
now := time.Now().In(c.location)
172+
now := c.now()
173173
for _, entry := range c.entries {
174174
entry.Next = entry.Schedule.Next(now)
175175
}
@@ -178,45 +178,46 @@ func (c *Cron) run() {
178178
// Determine the next entry to run.
179179
sort.Sort(byTime(c.entries))
180180

181-
var effective time.Time
181+
var timer *time.Timer
182182
if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
183183
// If there are no entries yet, just sleep - it still handles new entries
184184
// and stop requests.
185-
effective = now.AddDate(10, 0, 0)
185+
timer = time.NewTimer(100000 * time.Hour)
186186
} else {
187-
effective = c.entries[0].Next
187+
timer = time.NewTimer(c.entries[0].Next.Sub(now))
188188
}
189189

190-
timer := time.NewTimer(effective.Sub(now))
191-
select {
192-
case now = <-timer.C:
193-
now = now.In(c.location)
194-
// Run every entry whose next time was this effective time.
195-
for _, e := range c.entries {
196-
if e.Next != effective {
197-
break
190+
for {
191+
select {
192+
case now = <-timer.C:
193+
now = now.In(c.location)
194+
// Run every entry whose next time was less than now
195+
for _, e := range c.entries {
196+
if e.Next.After(now) || e.Next.IsZero() {
197+
break
198+
}
199+
go c.runWithRecovery(e.Job)
200+
e.Prev = e.Next
201+
e.Next = e.Schedule.Next(now)
198202
}
199-
go c.runWithRecovery(e.Job)
200-
e.Prev = e.Next
201-
e.Next = e.Schedule.Next(now)
202-
}
203-
continue
204203

205-
case newEntry := <-c.add:
206-
c.entries = append(c.entries, newEntry)
207-
newEntry.Next = newEntry.Schedule.Next(time.Now().In(c.location))
204+
case newEntry := <-c.add:
205+
timer.Stop()
206+
now = c.now()
207+
newEntry.Next = newEntry.Schedule.Next(now)
208+
c.entries = append(c.entries, newEntry)
208209

209-
case <-c.snapshot:
210-
c.snapshot <- c.entrySnapshot()
210+
case <-c.snapshot:
211+
c.snapshot <- c.entrySnapshot()
212+
continue
211213

212-
case <-c.stop:
213-
timer.Stop()
214-
return
215-
}
214+
case <-c.stop:
215+
timer.Stop()
216+
return
217+
}
216218

217-
// 'now' should be updated after newEntry and snapshot cases.
218-
now = time.Now().In(c.location)
219-
timer.Stop()
219+
break
220+
}
220221
}
221222
}
222223

@@ -251,3 +252,8 @@ func (c *Cron) entrySnapshot() []*Entry {
251252
}
252253
return entries
253254
}
255+
256+
// now returns current time in c location
257+
func (c *Cron) now() time.Time {
258+
return time.Now().In(c.location)
259+
}

cron_test.go

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
// Many tests schedule a job for every second, and then wait at most a second
1111
// for it to run. This amount is just slightly larger than 1 second to
1212
// compensate for a few milliseconds of runtime.
13-
const ONE_SECOND = 1*time.Second + 10*time.Millisecond
13+
const OneSecond = 1*time.Second + 10*time.Millisecond
1414

1515
func TestFuncPanicRecovery(t *testing.T) {
1616
cron := New()
@@ -19,7 +19,7 @@ func TestFuncPanicRecovery(t *testing.T) {
1919
cron.AddFunc("* * * * * ?", func() { panic("YOLO") })
2020

2121
select {
22-
case <-time.After(ONE_SECOND):
22+
case <-time.After(OneSecond):
2323
return
2424
}
2525
}
@@ -39,7 +39,7 @@ func TestJobPanicRecovery(t *testing.T) {
3939
cron.AddJob("* * * * * ?", job)
4040

4141
select {
42-
case <-time.After(ONE_SECOND):
42+
case <-time.After(OneSecond):
4343
return
4444
}
4545
}
@@ -50,8 +50,8 @@ func TestNoEntries(t *testing.T) {
5050
cron.Start()
5151

5252
select {
53-
case <-time.After(ONE_SECOND):
54-
t.FailNow()
53+
case <-time.After(OneSecond):
54+
t.Fatal("expected cron will be stopped immediately")
5555
case <-stop(cron):
5656
}
5757
}
@@ -67,10 +67,10 @@ func TestStopCausesJobsToNotRun(t *testing.T) {
6767
cron.AddFunc("* * * * * ?", func() { wg.Done() })
6868

6969
select {
70-
case <-time.After(ONE_SECOND):
70+
case <-time.After(OneSecond):
7171
// No job ran!
7272
case <-wait(wg):
73-
t.FailNow()
73+
t.Fatal("expected stopped cron does not run any job")
7474
}
7575
}
7676

@@ -86,8 +86,8 @@ func TestAddBeforeRunning(t *testing.T) {
8686

8787
// Give cron 2 seconds to run our job (which is always activated).
8888
select {
89-
case <-time.After(ONE_SECOND):
90-
t.FailNow()
89+
case <-time.After(OneSecond):
90+
t.Fatal("expected job runs")
9191
case <-wait(wg):
9292
}
9393
}
@@ -103,8 +103,8 @@ func TestAddWhileRunning(t *testing.T) {
103103
cron.AddFunc("* * * * * ?", func() { wg.Done() })
104104

105105
select {
106-
case <-time.After(ONE_SECOND):
107-
t.FailNow()
106+
case <-time.After(OneSecond):
107+
t.Fatal("expected job runs")
108108
case <-wait(wg):
109109
}
110110
}
@@ -118,10 +118,9 @@ func TestAddWhileRunningWithDelay(t *testing.T) {
118118
var calls = 0
119119
cron.AddFunc("* * * * * *", func() { calls += 1 })
120120

121-
<-time.After(ONE_SECOND)
121+
<-time.After(OneSecond)
122122
if calls != 1 {
123-
fmt.Printf("called %d times, expected 1\n", calls)
124-
t.Fail()
123+
t.Errorf("called %d times, expected 1\n", calls)
125124
}
126125
}
127126

@@ -137,14 +136,14 @@ func TestSnapshotEntries(t *testing.T) {
137136

138137
// Cron should fire in 2 seconds. After 1 second, call Entries.
139138
select {
140-
case <-time.After(ONE_SECOND):
139+
case <-time.After(OneSecond):
141140
cron.Entries()
142141
}
143142

144143
// Even though Entries was called, the cron should fire at the 2 second mark.
145144
select {
146-
case <-time.After(ONE_SECOND):
147-
t.FailNow()
145+
case <-time.After(OneSecond):
146+
t.Error("expected job runs at 2 second mark")
148147
case <-wait(wg):
149148
}
150149

@@ -168,8 +167,8 @@ func TestMultipleEntries(t *testing.T) {
168167
defer cron.Stop()
169168

170169
select {
171-
case <-time.After(ONE_SECOND):
172-
t.FailNow()
170+
case <-time.After(OneSecond):
171+
t.Error("expected job run in proper order")
173172
case <-wait(wg):
174173
}
175174
}
@@ -188,8 +187,8 @@ func TestRunningJobTwice(t *testing.T) {
188187
defer cron.Stop()
189188

190189
select {
191-
case <-time.After(2 * ONE_SECOND):
192-
t.FailNow()
190+
case <-time.After(2 * OneSecond):
191+
t.Error("expected job fires 2 times")
193192
case <-wait(wg):
194193
}
195194
}
@@ -210,8 +209,8 @@ func TestRunningMultipleSchedules(t *testing.T) {
210209
defer cron.Stop()
211210

212211
select {
213-
case <-time.After(2 * ONE_SECOND):
214-
t.FailNow()
212+
case <-time.After(2 * OneSecond):
213+
t.Error("expected job fires 2 times")
215214
case <-wait(wg):
216215
}
217216
}
@@ -221,7 +220,7 @@ func TestLocalTimezone(t *testing.T) {
221220
wg := &sync.WaitGroup{}
222221
wg.Add(2)
223222

224-
now := time.Now().Local()
223+
now := time.Now()
225224
spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
226225
now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
227226

@@ -231,8 +230,8 @@ func TestLocalTimezone(t *testing.T) {
231230
defer cron.Stop()
232231

233232
select {
234-
case <-time.After(ONE_SECOND * 2):
235-
t.FailNow()
233+
case <-time.After(OneSecond * 2):
234+
t.Error("expected job fires 2 times")
236235
case <-wait(wg):
237236
}
238237
}
@@ -258,8 +257,8 @@ func TestNonLocalTimezone(t *testing.T) {
258257
defer cron.Stop()
259258

260259
select {
261-
case <-time.After(ONE_SECOND * 2):
262-
t.FailNow()
260+
case <-time.After(OneSecond * 2):
261+
t.Error("expected job fires 2 times")
263262
case <-wait(wg):
264263
}
265264
}
@@ -297,7 +296,7 @@ func TestJob(t *testing.T) {
297296
defer cron.Stop()
298297

299298
select {
300-
case <-time.After(ONE_SECOND):
299+
case <-time.After(OneSecond):
301300
t.FailNow()
302301
case <-wait(wg):
303302
}
@@ -312,12 +311,31 @@ func TestJob(t *testing.T) {
312311

313312
for i, expected := range expecteds {
314313
if actuals[i] != expected {
315-
t.Errorf("Jobs not in the right order. (expected) %s != %s (actual)", expecteds, actuals)
316-
t.FailNow()
314+
t.Fatalf("Jobs not in the right order. (expected) %s != %s (actual)", expecteds, actuals)
317315
}
318316
}
319317
}
320318

319+
type ZeroSchedule struct{}
320+
321+
func (*ZeroSchedule) Next(time.Time) time.Time {
322+
return time.Time{}
323+
}
324+
325+
// Tests that job without time does not run
326+
func TestJobWithZeroTimeDoesNotRun(t *testing.T) {
327+
cron := New()
328+
calls := 0
329+
cron.AddFunc("* * * * * *", func() { calls += 1 })
330+
cron.Schedule(new(ZeroSchedule), FuncJob(func() { t.Error("expected zero task will not run") }))
331+
cron.Start()
332+
defer cron.Stop()
333+
<-time.After(OneSecond)
334+
if calls != 1 {
335+
t.Errorf("called %d times, expected 1\n", calls)
336+
}
337+
}
338+
321339
func wait(wg *sync.WaitGroup) chan bool {
322340
ch := make(chan bool)
323341
go func() {

parser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestRange(t *testing.T) {
4747
t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
4848
}
4949
if len(c.err) == 0 && err != nil {
50-
t.Error("%s => unexpected error %v", c.expr, err)
50+
t.Errorf("%s => unexpected error %v", c.expr, err)
5151
}
5252
if actual != c.expected {
5353
t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)

0 commit comments

Comments
 (0)