[BUG] - CPU usage 100% and next schedule is blocking after the system time is changed #385
Closed
Description
Describe the bug
use this code run cron scheduler,CPU usage 100% and next schedule is blocking after the system time is changed.
package main
import (
"github.com/go-co-op/gocron"
"log"
"time"
)
func task() {
log.Println("hello world.")
}
func main() {
s := gocron.NewScheduler(time.UTC)
_, _ = s.Cron("*/1 * * * *").Do(task)
s.StartBlocking()
}
To Reproduce
- current system time:
Mon Aug 29 17:30:22 CST 2022
- change system time forward 1 hour:
Mon Aug 29 16:30:51 CST 2022
- next schedule has bug.
Version
v1.13.0
Additional context
use pprof make analysis,test code is:
package main
import (
"github.com/go-co-op/gocron"
"log"
"net/http"
_ "net/http/pprof"
"time"
)
func task() {
log.Println("hello world.")
}
func main() {
go func() {
http.ListenAndServe("0.0.0.0:6060", nil)
}()
s := gocron.NewScheduler(time.UTC)
_, _ = s.Cron("*/1 * * * *").Do(task)
s.StartBlocking()
}
- change system time forward 1 hour,wait for the next scheduling exception.
- use curl command dump profile.
curl http://127.0.0.1:6060/debug/pprof/profile\?seconds\=60 > profile.out
- use go tool display profile.
go tool pprof -http=":8082" profile.out
- get cpu usage 100% code line is:
0.95s 100% | github.com/go-co-op/gocron.(*Scheduler).scheduleNextRun.func1 xxx/go/pkg/mod/github.com/go-co-op/gocron@v1.13.0/scheduler.go:204
- review gocron scheduler line 204:
job.setTimer(time.AfterFunc(next.duration, func() {
if !next.dateTime.IsZero() {
for {
if time.Now().Unix() >= next.dateTime.Unix() {
break
}
}
}
s.run(job)
s.scheduleNextRun(job)
}))
The for loop here can cause CPU usage to reach 100% and block the next schedule.
6. suggest example:
We can set the timer check condition time.Now().Unix() >= next.dateTime.Unix() and set the timeout to jump out for loop,ensure the next scheduling.
job.setTimer(time.AfterFunc(next.duration, func() {
if !next.dateTime.IsZero() {
timeout := 30 * time.Second
interval := 1 * time.Second
timer := time.NewTimer(timeout)
ticker := time.NewTicker(interval)
defer timer.Stop()
defer ticker.Stop()
for {
if time.Now().Unix() >= next.dateTime.Unix() {
break
}
select {
case <-ticker.C:
ticker.Reset(interval)
continue
case <-timer.C:
break
}
break
}
}
s.run(job)
s.scheduleNextRun(job)
}))