This is a creation in Article, where the information may have evolved or changed.
Cron Scheduled task, Golang cron Open Source Project code Analysis: Https://github.com/robfig/cron
Package cron
Import (
"log"
"runtime"
"sort"
"time"
)
// Cron keeps track of any number of entries, invoking the associated func as
// specified by the schedule. It may be started, stopped, and the entries may
// be inspected while running.
Type Cron struct {
Entries []*Entry //Entry is the structure of all task encapsulation
Stop chan struct{} //close the flag channel
Add chan *Entry //Additional Entry
Snapshot chan []*Entry //Entry backup
Running bool //open flag
ErrorLog *log.Logger
Location *time.Location
}
// Job is an interface for submitted cron jobs.
Type Job interface {
Run() //Job has only one Run method
}
// The Schedule describes a job's duty cycle.
Type schedule interface {
// Return the next activation time, later than the given time.
// Next is invoked initially, and then each time the job is run.
Next(time.Time) time.Time //Scheduled to select the next execution, the entire process execution is very critical
}
// Entry consists of a schedule and the func to execute on that schedule.
Type Entry struct {
// The schedule on which this job should be run.
Schedule Schedule
// The next time the job will run. This is the zero time if Cron has not been
// started or this entry's schedule is unsatisfiable
Next time.Time //The next run time
// The last time this job was run. This is the zero time if the job has never
// Been run.
Prev time.Time //Time of last run
// The Job to run.
Job Job
}
// byTime is a wrapper for sorting the entry array by time
// (with zero time at the end).
Type byTime []*Entry
Func (s byTime) Len() int { return len(s) }
Func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
Func (s byTime) Less(i, j int) bool {
/ / Compare time, when both are 0, return directly to false, 0 is higher than any time priority
If s[i].Next.IsZero() {
Return false
}
If s[j].Next.IsZero() {
Return true
}
Return s[i].Next.Before(s[j].Next)
}
// New returns a new Cron job runner, in the Local time zone.
Func New() *Cron {
Return NewWithLocation(time.Now().Location())
}
// NewWithLocation returns a new Cron job runner.
Func NewWithLocation(location *time.Location) *Cron {
Return &Cron{
Entries: nil,
Add: make(chan *Entry), //add the entry channel
Stop: make(chan struct{}),
Snapshot: make(chan []*Entry),
Running: false,
ErrorLog: nil,
Location: location, //time
}
}
// A wrapper that turns a func() into a cron.Job
Type FuncJob func()
Func (f FuncJob) Run() { f() }
// AddFunc adds a func to the Cron to be run on the given schedule.
Func (c *Cron) AddFunc(spec string, cmd func()) error {
Return c.AddJob(spec, FuncJob(cmd))
}
// AddJob adds a Job to the Cron to be run on the given schedule.
Func (c *Cron) AddJob(spec string, cmd Job) error {
Schedule, err := Parse(spec)
If err != nil {
Return err
}
c.Schedule(schedule, cmd)
Return nil
}
// Schedule adds a Job to the Cron to be run on the given schedule.
Func (c *Cron) Schedule(schedule Schedule, cmd Job) {
Entry := &Entry{
Schedule: schedule,
Job: cmd,
}
If !c.running {
C.entries = append(c.entries, entry)
Return
}
C.add <- entry
}
// A copy of all the currently running entries for easy testing and tracking
Func (c *Cron) Entries() []*Entry {
If c.running {
/ / First pass a nil, trigger the channel read time, run the coroutine copy entry, and then read out all the entries
C.snapshot <- nil
x := <-c.snapshot
Return x
}
Return c.entrySnapshot()
}
// Location gets the time zone location
Func (c *Cron) Location() *time.Location {
Return c.location
}
/ / The difference between the Start method and the Run method is not running in a separate go-routine
// Start the cron scheduler in its own go-routine, or no-op if already started.
Func (c *Cron) Start() {
If c.running {
Return
}
C.running = true
Go c.run()
}
// Run the cron scheduler, or no-op if already running.
Func (c *Cron) Run() {
If c.running {
Return
}
/ / Start the cron run
C.running = true
C.run()
}
Func (c *Cron) runWithRecovery(j Job) {
Defer func() {
/ / Log stack information when running job panic
If r := recover(); r != nil {
Const size = 64 << 10
Buf := make([]byte, size)
Buf = buf[:runtime.Stack(buf, false)]
C.logf("cron: panic running job: %v\n%s", r, buf)
}
}()
j.Run()
}
// Run the scheduler. this is private just due to the need to synchronize
// access to the 'running' state variable.
Func (c *Cron) run() {
// Figure out the next activation times for each entry.
Now := c.now()
For _, entry := range c.entries {
entry.Next = entry.Schedule.Next(now)
}
For {
// sorting entry from small to large
sort.Sort(byTime(c.entries))
Var timer *time.Timer
If len(c.entries) == 0 || c.entries[0].Next.IsZero() {
/ / Set the oversized timer when there is no entry, you can still handle the stop
Timer = time.NewTimer(100000 * time.Hour)
} else {
Timer = time.NewTimer(c.entries[0].Next.Sub(now))
}
For {
Select {
Case now = <-timer.C:
Now = now.In(c.location)
// Run an entry less than the now time
For _, e := range c.entries {
If e.Next.After(now) || e.Next.IsZero() {
Break
}
/ / Execute the task Job
Go c.runWithRecovery(e.Job)
e.Prev = e.Next
e.Next = e.Schedule.Next(now)
}
Case newEntry := <-c.add:
//When adding an entry, stop the last timer.
timer.Stop()
Now = c.now()
newEntry.Next = newEntry.Schedule.Next(now)
C.entries = append(c.entries, newEntry)
Case <-c
.snapshot:
C.snapshot <- c.entrySnapshot()
Continue
Case <-c.stop:
timer.Stop()
Return
}
Break
}
}
}
// Logs an error to stderr or to the configured error log
Func (c *Cron) logf(format string, args ...interface{}) {
If c.ErrorLog != nil {
c.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
// Stop stops the cron scheduler if it is running; otherwise it does nothing.
Func (c *Cron) Stop() {
If !c.running {
Return
}
C.stop <- struct{}{}
C.running = false
}
// entrySnapshot returns a copy of the current cron entry list.
Func (c *Cron) entrySnapshot() []*Entry {
Entries := []*Entry{}
For _, e := range c.entries {
Entries = append(entries, &Entry{
Schedule: e.Schedule,
Next: e.Next,
Prev: e.Prev,
Job: e.Job,
})
}
Return entries
}
// now returns current time in c location
Func (c *Cron) now() time.Time {
Return time.Now().In(c.location)
}
The keypoint is the concrete implementation of the next method of schedule