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  }