Skip to content

Commit 533bf51

Browse files
committed
merge upstream and fix tests
2 parents 3abc0f8 + 0f39cf7 commit 533bf51

File tree

2 files changed

+100
-27
lines changed

2 files changed

+100
-27
lines changed

cron.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
package cron
44

55
import (
6+
"log"
7+
"runtime"
68
"sort"
79
"time"
810
)
@@ -16,6 +18,7 @@ type Cron struct {
1618
add chan *Entry
1719
snapshot chan []*Entry
1820
running bool
21+
ErrorLog *log.Logger
1922
}
2023

2124
// Job is an interface for submitted cron jobs.
@@ -79,6 +82,7 @@ func New() *Cron {
7982
stop: make(chan struct{}),
8083
snapshot: make(chan []*Entry),
8184
running: false,
85+
ErrorLog: nil,
8286
}
8387
}
8488

@@ -92,7 +96,7 @@ func (c *Cron) AddFunc(desc, spec string, cmd func()) (*Entry, error) {
9296
return c.AddJob(desc, spec, FuncJob(cmd))
9397
}
9498

95-
// AddFunc adds a Job to the Cron to be run on the given schedule.
99+
// AddJob adds a Job to the Cron to be run on the given schedule.
96100
func (c *Cron) AddJob(desc, spec string, cmd Job) (*Entry, error) {
97101
schedule, err := Parse(spec)
98102
if err != nil {
@@ -133,6 +137,18 @@ func (c *Cron) Start() {
133137
go c.run()
134138
}
135139

140+
func (c *Cron) runWithRecovery(j Job) {
141+
defer func() {
142+
if r := recover(); r != nil {
143+
const size = 64 << 10
144+
buf := make([]byte, size)
145+
buf = buf[:runtime.Stack(buf, false)]
146+
c.logf("cron: panic running job: %v\n%s", r, buf)
147+
}
148+
}()
149+
j.Run()
150+
}
151+
136152
// Run the scheduler.. this is private just due to the need to synchronize
137153
// access to the 'running' state variable.
138154
func (c *Cron) run() {
@@ -162,7 +178,7 @@ func (c *Cron) run() {
162178
if e.Next != effective {
163179
break
164180
}
165-
go e.Job.Run()
181+
go c.runWithRecovery(e.Job)
166182
e.ExecTimes++
167183
e.Prev = e.Next
168184
e.Next = e.Schedule.Next(effective)
@@ -171,7 +187,7 @@ func (c *Cron) run() {
171187

172188
case newEntry := <-c.add:
173189
c.entries = append(c.entries, newEntry)
174-
newEntry.Next = newEntry.Schedule.Next(now)
190+
newEntry.Next = newEntry.Schedule.Next(time.Now().Local())
175191

176192
case <-c.snapshot:
177193
c.snapshot <- c.entrySnapshot()
@@ -185,6 +201,15 @@ func (c *Cron) run() {
185201
}
186202
}
187203

204+
// Logs an error to stderr or to the configured error log
205+
func (c *Cron) logf(format string, args ...interface{}) {
206+
if c.ErrorLog != nil {
207+
c.ErrorLog.Printf(format, args...)
208+
} else {
209+
log.Printf(format, args...)
210+
}
211+
}
212+
188213
// Stop stops the cron scheduler if it is running; otherwise it does nothing.
189214
func (c *Cron) Stop() {
190215
if !c.running {

cron_test.go

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,38 @@ import (
1212
// compensate for a few milliseconds of runtime.
1313
const ONE_SECOND = 1*time.Second + 10*time.Millisecond
1414

15+
func TestFuncPanicRecovery(t *testing.T) {
16+
cron := New()
17+
cron.Start()
18+
defer cron.Stop()
19+
cron.AddFunc("", "* * * * * ?", func() { panic("YOLO") })
20+
21+
select {
22+
case <-time.After(ONE_SECOND):
23+
return
24+
}
25+
}
26+
27+
type DummyJob struct{}
28+
29+
func (d DummyJob) Run() {
30+
panic("YOLO")
31+
}
32+
33+
func TestJobPanicRecovery(t *testing.T) {
34+
var job DummyJob
35+
36+
cron := New()
37+
cron.Start()
38+
defer cron.Stop()
39+
cron.AddJob("", "* * * * * ?", job)
40+
41+
select {
42+
case <-time.After(ONE_SECOND):
43+
return
44+
}
45+
}
46+
1547
// Start and stop cron with no entries.
1648
func TestNoEntries(t *testing.T) {
1749
cron := New()
@@ -32,7 +64,7 @@ func TestStopCausesJobsToNotRun(t *testing.T) {
3264
cron := New()
3365
cron.Start()
3466
cron.Stop()
35-
cron.AddFunc("* * * * * ?", func() { wg.Done() })
67+
cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
3668

3769
select {
3870
case <-time.After(ONE_SECOND):
@@ -48,7 +80,7 @@ func TestAddBeforeRunning(t *testing.T) {
4880
wg.Add(1)
4981

5082
cron := New()
51-
cron.AddFunc("* * * * * ?", func() { wg.Done() })
83+
cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
5284
cron.Start()
5385
defer cron.Stop()
5486

@@ -68,7 +100,7 @@ func TestAddWhileRunning(t *testing.T) {
68100
cron := New()
69101
cron.Start()
70102
defer cron.Stop()
71-
cron.AddFunc("* * * * * ?", func() { wg.Done() })
103+
cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
72104

73105
select {
74106
case <-time.After(ONE_SECOND):
@@ -77,13 +109,29 @@ func TestAddWhileRunning(t *testing.T) {
77109
}
78110
}
79111

112+
// Test for #34. Adding a job after calling start results in multiple job invocations
113+
func TestAddWhileRunningWithDelay(t *testing.T) {
114+
cron := New()
115+
cron.Start()
116+
defer cron.Stop()
117+
time.Sleep(5 * time.Second)
118+
var calls = 0
119+
cron.AddFunc("", "* * * * * *", func() { calls += 1 })
120+
121+
<-time.After(ONE_SECOND)
122+
if calls != 1 {
123+
fmt.Printf("called %d times, expected 1\n", calls)
124+
t.Fail()
125+
}
126+
}
127+
80128
// Test timing with Entries.
81129
func TestSnapshotEntries(t *testing.T) {
82130
wg := &sync.WaitGroup{}
83131
wg.Add(1)
84132

85133
cron := New()
86-
cron.AddFunc("@every 2s", func() { wg.Done() })
134+
cron.AddFunc("", "@every 2s", func() { wg.Done() })
87135
cron.Start()
88136
defer cron.Stop()
89137

@@ -111,10 +159,10 @@ func TestMultipleEntries(t *testing.T) {
111159
wg.Add(2)
112160

113161
cron := New()
114-
cron.AddFunc("0 0 0 1 1 ?", func() {})
115-
cron.AddFunc("* * * * * ?", func() { wg.Done() })
116-
cron.AddFunc("0 0 0 31 12 ?", func() {})
117-
cron.AddFunc("* * * * * ?", func() { wg.Done() })
162+
cron.AddFunc("", "0 0 0 1 1 ?", func() {})
163+
cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
164+
cron.AddFunc("", "0 0 0 31 12 ?", func() {})
165+
cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
118166

119167
cron.Start()
120168
defer cron.Stop()
@@ -132,9 +180,9 @@ func TestRunningJobTwice(t *testing.T) {
132180
wg.Add(2)
133181

134182
cron := New()
135-
cron.AddFunc("0 0 0 1 1 ?", func() {})
136-
cron.AddFunc("0 0 0 31 12 ?", func() {})
137-
cron.AddFunc("* * * * * ?", func() { wg.Done() })
183+
cron.AddFunc("", "0 0 0 1 1 ?", func() {})
184+
cron.AddFunc("", "0 0 0 31 12 ?", func() {})
185+
cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
138186

139187
cron.Start()
140188
defer cron.Stop()
@@ -151,12 +199,12 @@ func TestRunningMultipleSchedules(t *testing.T) {
151199
wg.Add(2)
152200

153201
cron := New()
154-
cron.AddFunc("0 0 0 1 1 ?", func() {})
155-
cron.AddFunc("0 0 0 31 12 ?", func() {})
156-
cron.AddFunc("* * * * * ?", func() { wg.Done() })
157-
cron.Schedule(Every(time.Minute), FuncJob(func() {}))
158-
cron.Schedule(Every(time.Second), FuncJob(func() { wg.Done() }))
159-
cron.Schedule(Every(time.Hour), FuncJob(func() {}))
202+
cron.AddFunc("", "0 0 0 1 1 ?", func() {})
203+
cron.AddFunc("", "0 0 0 31 12 ?", func() {})
204+
cron.AddFunc("", "* * * * * ?", func() { wg.Done() })
205+
cron.Schedule("", "", Every(time.Minute), FuncJob(func() {}))
206+
cron.Schedule("", "", Every(time.Second), FuncJob(func() { wg.Done() }))
207+
cron.Schedule("", "", Every(time.Hour), FuncJob(func() {}))
160208

161209
cron.Start()
162210
defer cron.Stop()
@@ -178,7 +226,7 @@ func TestLocalTimezone(t *testing.T) {
178226
now.Second()+1, now.Minute(), now.Hour(), now.Day(), now.Month())
179227

180228
cron := New()
181-
cron.AddFunc(spec, func() { wg.Done() })
229+
cron.AddFunc("", spec, func() { wg.Done() })
182230
cron.Start()
183231
defer cron.Stop()
184232

@@ -211,12 +259,12 @@ func TestJob(t *testing.T) {
211259
wg.Add(1)
212260

213261
cron := New()
214-
cron.AddJob("0 0 0 30 Feb ?", testJob{wg, "job0"})
215-
cron.AddJob("0 0 0 1 1 ?", testJob{wg, "job1"})
216-
cron.AddJob("* * * * * ?", testJob{wg, "job2"})
217-
cron.AddJob("1 0 0 1 1 ?", testJob{wg, "job3"})
218-
cron.Schedule(Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
219-
cron.Schedule(Every(5*time.Minute), testJob{wg, "job5"})
262+
cron.AddJob("", "0 0 0 30 Feb ?", testJob{wg, "job0"})
263+
cron.AddJob("", "0 0 0 1 1 ?", testJob{wg, "job1"})
264+
cron.AddJob("", "* * * * * ?", testJob{wg, "job2"})
265+
cron.AddJob("", "1 0 0 1 1 ?", testJob{wg, "job3"})
266+
cron.Schedule("", "", Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
267+
cron.Schedule("", "", Every(5*time.Minute), testJob{wg, "job5"})
220268

221269
cron.Start()
222270
defer cron.Stop()

0 commit comments

Comments
 (0)