123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- // Copyright 2020 The Gitea Authors. All rights reserved.
- // Use of this source code is governed by a MIT-style
- // license that can be found in the LICENSE file.
-
- package cron
-
- import (
- "context"
- "fmt"
- "reflect"
- "sync"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/graceful"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/process"
- "code.gitea.io/gitea/modules/setting"
- )
-
- var lock = sync.Mutex{}
- var started = false
- var tasks = []*Task{}
- var tasksMap = map[string]*Task{}
-
- // Task represents a Cron task
- type Task struct {
- lock sync.Mutex
- Name string
- config Config
- fun func(context.Context, *models.User, Config) error
- ExecTimes int64
- }
-
- // DoRunAtStart returns if this task should run at the start
- func (t *Task) DoRunAtStart() bool {
- return t.config.DoRunAtStart()
- }
-
- // IsEnabled returns if this task is enabled as cron task
- func (t *Task) IsEnabled() bool {
- return t.config.IsEnabled()
- }
-
- // GetConfig will return a copy of the task's config
- func (t *Task) GetConfig() Config {
- if reflect.TypeOf(t.config).Kind() == reflect.Ptr {
- // Pointer:
- return reflect.New(reflect.ValueOf(t.config).Elem().Type()).Interface().(Config)
- }
- // Not pointer:
- return reflect.New(reflect.TypeOf(t.config)).Elem().Interface().(Config)
- }
-
- // Run will run the task incrementing the cron counter with no user defined
- func (t *Task) Run() {
- t.RunWithUser(&models.User{
- ID: -1,
- Name: "(Cron)",
- LowerName: "(cron)",
- }, t.config)
- }
-
- // RunWithUser will run the task incrementing the cron counter at the time with User
- func (t *Task) RunWithUser(doer *models.User, config Config) {
- if !taskStatusTable.StartIfNotRunning(t.Name) {
- return
- }
- t.lock.Lock()
- if config == nil {
- config = t.config
- }
- t.ExecTimes++
- t.lock.Unlock()
- defer func() {
- taskStatusTable.Stop(t.Name)
- if err := recover(); err != nil {
- // Recover a panic within the
- combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
- log.Error("PANIC whilst running task: %s Value: %v", t.Name, combinedErr)
- }
- }()
- graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
- ctx, cancel := context.WithCancel(baseCtx)
- defer cancel()
- pm := process.GetManager()
- pid := pm.Add(config.FormatMessage(t.Name, "process", doer), cancel)
- defer pm.Remove(pid)
- if err := t.fun(ctx, doer, config); err != nil {
- if models.IsErrCancelled(err) {
- message := err.(models.ErrCancelled).Message
- if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "aborted", doer, message)); err != nil {
- log.Error("CreateNotice: %v", err)
- }
- return
- }
- if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "error", doer, err)); err != nil {
- log.Error("CreateNotice: %v", err)
- }
- return
- }
- if err := models.CreateNotice(models.NoticeTask, config.FormatMessage(t.Name, "finished", doer)); err != nil {
- log.Error("CreateNotice: %v", err)
- }
- })
- }
-
- // GetTask gets the named task
- func GetTask(name string) *Task {
- lock.Lock()
- defer lock.Unlock()
- log.Info("Getting %s in %v", name, tasksMap[name])
-
- return tasksMap[name]
- }
-
- // RegisterTask allows a task to be registered with the cron service
- func RegisterTask(name string, config Config, fun func(context.Context, *models.User, Config) error) error {
- log.Debug("Registering task: %s", name)
- _, err := setting.GetCronSettings(name, config)
- if err != nil {
- log.Error("Unable to register cron task with name: %s Error: %v", name, err)
- return err
- }
-
- task := &Task{
- Name: name,
- config: config,
- fun: fun,
- }
- lock.Lock()
- locked := true
- defer func() {
- if locked {
- lock.Unlock()
- }
- }()
- if _, has := tasksMap[task.Name]; has {
- log.Error("A task with this name: %s has already been registered", name)
- return fmt.Errorf("duplicate task with name: %s", task.Name)
- }
-
- if config.IsEnabled() {
- // We cannot use the entry return as there is no way to lock it
- if _, err = c.AddJob(name, config.GetSchedule(), task); err != nil {
- log.Error("Unable to register cron task with name: %s Error: %v", name, err)
- return err
- }
- }
-
- tasks = append(tasks, task)
- tasksMap[task.Name] = task
- if started && config.IsEnabled() && config.DoRunAtStart() {
- lock.Unlock()
- locked = false
- task.Run()
- }
-
- return nil
- }
-
- // RegisterTaskFatal will register a task but if there is an error log.Fatal
- func RegisterTaskFatal(name string, config Config, fun func(context.Context, *models.User, Config) error) {
- if err := RegisterTask(name, config, fun); err != nil {
- log.Fatal("Unable to register cron task %s Error: %v", name, err)
- }
- }
|