github.com/gogf/gf/v2@v2.7.4/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/gogf/gf. 6 7 package gcron 8 9 import ( 10 "context" 11 "fmt" 12 "reflect" 13 "runtime" 14 "time" 15 16 "github.com/gogf/gf/v2/container/gtype" 17 "github.com/gogf/gf/v2/errors/gcode" 18 "github.com/gogf/gf/v2/errors/gerror" 19 "github.com/gogf/gf/v2/os/glog" 20 "github.com/gogf/gf/v2/os/gtimer" 21 "github.com/gogf/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 RegisterTime time.Time // Registered time. 37 Job JobFunc `json:"-"` // Callback function. 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 RegisterTime: time.Now(), 73 Job: in.Job, 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 e.cron.jobWaiter.Add(1) 156 defer func() { 157 e.cron.jobWaiter.Done() 158 if exception := recover(); exception != nil { 159 // Exception caught, it logs the error content to logger in default behavior. 160 e.logErrorf(ctx, 161 `cron job "%s(%s)" end with error: %+v`, 162 e.jobName, e.schedule.pattern, exception, 163 ) 164 } else { 165 e.logDebugf(ctx, `cron job "%s" ends`, e.getJobNameWithPattern()) 166 } 167 if e.timerEntry.Status() == StatusClosed { 168 e.Close() 169 } 170 }() 171 172 // Running times check. 173 if !e.infinite.Val() { 174 times := e.times.Add(-1) 175 if times <= 0 { 176 if e.timerEntry.SetStatus(StatusClosed) == StatusClosed || times < 0 { 177 return 178 } 179 } 180 } 181 e.logDebugf(ctx, `cron job "%s" starts`, e.getJobNameWithPattern()) 182 e.Job(ctx) 183 } 184 } 185 186 func (e *Entry) getJobNameWithPattern() string { 187 return fmt.Sprintf(`%s(%s)`, e.jobName, e.schedule.pattern) 188 } 189 190 func (e *Entry) logDebugf(ctx context.Context, format string, v ...interface{}) { 191 if logger := e.cron.GetLogger(); logger != nil { 192 logger.Debugf(ctx, format, v...) 193 } 194 } 195 196 func (e *Entry) logErrorf(ctx context.Context, format string, v ...interface{}) { 197 logger := e.cron.GetLogger() 198 if logger == nil { 199 logger = glog.DefaultLogger() 200 } 201 logger.Errorf(ctx, format, v...) 202 }