github.com/wangyougui/gf/v2@v2.6.5/os/gcron/gcron_entry.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/wangyougui/gf. 6 7 package gcron 8 9 import ( 10 "context" 11 "fmt" 12 "reflect" 13 "runtime" 14 "time" 15 16 "github.com/wangyougui/gf/v2/container/gtype" 17 "github.com/wangyougui/gf/v2/errors/gcode" 18 "github.com/wangyougui/gf/v2/errors/gerror" 19 "github.com/wangyougui/gf/v2/os/glog" 20 "github.com/wangyougui/gf/v2/os/gtimer" 21 "github.com/wangyougui/gf/v2/util/gconv" 22 ) 23 24 // JobFunc is the timing called job function in cron. 25 type JobFunc = gtimer.JobFunc 26 27 // Entry is timing task entry. 28 type Entry struct { 29 cron *Cron // Cron object belonged to. 30 timerEntry *gtimer.Entry // Associated timer Entry. 31 schedule *cronSchedule // Timed schedule object. 32 jobName string // Callback function name(address info). 33 times *gtype.Int // Running times limit. 34 infinite *gtype.Bool // No times limit. 35 Name string // Entry name. 36 Job JobFunc `json:"-"` // Callback function. 37 Time time.Time // Registered time. 38 } 39 40 type doAddEntryInput struct { 41 Name string // Name names this entry for manual control. 42 Job JobFunc // Job is the callback function for timed task execution. 43 Ctx context.Context // The context for the job. 44 Times int // Times specifies the running limit times for the entry. 45 Pattern string // Pattern is the crontab style string for scheduler. 46 IsSingleton bool // Singleton specifies whether timed task executing in singleton mode. 47 Infinite bool // Infinite specifies whether this entry is running with no times limit. 48 } 49 50 // doAddEntry creates and returns a new Entry object. 51 func (c *Cron) doAddEntry(in doAddEntryInput) (*Entry, error) { 52 if in.Name != "" { 53 if c.Search(in.Name) != nil { 54 return nil, gerror.NewCodef( 55 gcode.CodeInvalidOperation, 56 `duplicated cron job name "%s", already exists`, 57 in.Name, 58 ) 59 } 60 } 61 schedule, err := newSchedule(in.Pattern) 62 if err != nil { 63 return nil, err 64 } 65 // No limit for `times`, for timer checking scheduling every second. 66 entry := &Entry{ 67 cron: c, 68 schedule: schedule, 69 jobName: runtime.FuncForPC(reflect.ValueOf(in.Job).Pointer()).Name(), 70 times: gtype.NewInt(in.Times), 71 infinite: gtype.NewBool(in.Infinite), 72 Job: in.Job, 73 Time: time.Now(), 74 } 75 if in.Name != "" { 76 entry.Name = in.Name 77 } else { 78 entry.Name = "cron-" + gconv.String(c.idGen.Add(1)) 79 } 80 // When you add a scheduled task, you cannot allow it to run. 81 // It cannot start running when added to timer. 82 // It should start running after the entry is added to the Cron entries map, to avoid the task 83 // from running during adding where the entries do not have the entry information, which might cause panic. 84 entry.timerEntry = gtimer.AddEntry( 85 in.Ctx, 86 time.Second, 87 entry.checkAndRun, 88 in.IsSingleton, 89 -1, 90 gtimer.StatusStopped, 91 ) 92 c.entries.Set(entry.Name, entry) 93 entry.timerEntry.Start() 94 return entry, nil 95 } 96 97 // IsSingleton return whether this entry is a singleton timed task. 98 func (e *Entry) IsSingleton() bool { 99 return e.timerEntry.IsSingleton() 100 } 101 102 // SetSingleton sets the entry running in singleton mode. 103 func (e *Entry) SetSingleton(enabled bool) { 104 e.timerEntry.SetSingleton(enabled) 105 } 106 107 // SetTimes sets the times which the entry can run. 108 func (e *Entry) SetTimes(times int) { 109 e.times.Set(times) 110 e.infinite.Set(false) 111 } 112 113 // Status returns the status of entry. 114 func (e *Entry) Status() int { 115 return e.timerEntry.Status() 116 } 117 118 // SetStatus sets the status of the entry. 119 func (e *Entry) SetStatus(status int) int { 120 return e.timerEntry.SetStatus(status) 121 } 122 123 // Start starts running the entry. 124 func (e *Entry) Start() { 125 e.timerEntry.Start() 126 } 127 128 // Stop stops running the entry. 129 func (e *Entry) Stop() { 130 e.timerEntry.Stop() 131 } 132 133 // Close stops and removes the entry from cron. 134 func (e *Entry) Close() { 135 e.cron.entries.Remove(e.Name) 136 e.timerEntry.Close() 137 } 138 139 // checkAndRun is the core timing task check logic. 140 // This function is called every second. 141 func (e *Entry) checkAndRun(ctx context.Context) { 142 currentTime := time.Now() 143 if !e.schedule.checkMeetAndUpdateLastSeconds(ctx, currentTime) { 144 return 145 } 146 switch e.cron.status.Val() { 147 case StatusStopped: 148 return 149 150 case StatusClosed: 151 e.logDebugf(ctx, `cron job "%s" is removed`, e.getJobNameWithPattern()) 152 e.Close() 153 154 case StatusReady, StatusRunning: 155 defer func() { 156 if exception := recover(); exception != nil { 157 // Exception caught, it logs the error content to logger in default behavior. 158 e.logErrorf(ctx, 159 `cron job "%s(%s)" end with error: %+v`, 160 e.jobName, e.schedule.pattern, exception, 161 ) 162 } else { 163 e.logDebugf(ctx, `cron job "%s" ends`, e.getJobNameWithPattern()) 164 } 165 if e.timerEntry.Status() == StatusClosed { 166 e.Close() 167 } 168 }() 169 170 // Running times check. 171 if !e.infinite.Val() { 172 times := e.times.Add(-1) 173 if times <= 0 { 174 if e.timerEntry.SetStatus(StatusClosed) == StatusClosed || times < 0 { 175 return 176 } 177 } 178 } 179 e.logDebugf(ctx, `cron job "%s" starts`, e.getJobNameWithPattern()) 180 e.Job(ctx) 181 } 182 } 183 184 func (e *Entry) getJobNameWithPattern() string { 185 return fmt.Sprintf(`%s(%s)`, e.jobName, e.schedule.pattern) 186 } 187 188 func (e *Entry) logDebugf(ctx context.Context, format string, v ...interface{}) { 189 if logger := e.cron.GetLogger(); logger != nil { 190 logger.Debugf(ctx, format, v...) 191 } 192 } 193 194 func (e *Entry) logErrorf(ctx context.Context, format string, v ...interface{}) { 195 logger := e.cron.GetLogger() 196 if logger == nil { 197 logger = glog.DefaultLogger() 198 } 199 logger.Errorf(ctx, format, v...) 200 }