2020-05-17 02:31:38 +03:00
// 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"
2021-11-18 08:58:42 +03:00
admin_model "code.gitea.io/gitea/models/admin"
2021-11-10 08:13:16 +03:00
"code.gitea.io/gitea/models/db"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2020-05-17 02:31:38 +03:00
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
2022-02-07 10:43:53 +03:00
"code.gitea.io/gitea/modules/translation"
2020-05-17 02:31:38 +03:00
)
2022-01-20 20:46:10 +03:00
var (
lock = sync . Mutex { }
started = false
tasks = [ ] * Task { }
tasksMap = map [ string ] * Task { }
)
2020-05-17 02:31:38 +03:00
// Task represents a Cron task
type Task struct {
2022-03-29 04:31:07 +03:00
lock sync . Mutex
Name string
config Config
fun func ( context . Context , * user_model . User , Config ) error
Status string
LastMessage string
LastDoer string
ExecTimes int64
2020-05-17 02:31:38 +03:00
}
// 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 ( ) {
2021-11-24 12:49:20 +03:00
t . RunWithUser ( & user_model . User {
2020-05-17 02:31:38 +03:00
ID : - 1 ,
Name : "(Cron)" ,
LowerName : "(cron)" ,
} , t . config )
}
// RunWithUser will run the task incrementing the cron counter at the time with User
2021-11-24 12:49:20 +03:00
func ( t * Task ) RunWithUser ( doer * user_model . User , config Config ) {
2020-05-17 02:31:38 +03:00
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 ) {
pm := process . GetManager ( )
2022-03-29 04:31:07 +03:00
doerName := ""
if doer != nil && doer . ID != - 1 {
doerName = doer . Name
}
ctx , _ , finished := pm . AddContext ( baseCtx , config . FormatMessage ( "en-US" , t . Name , "process" , doerName ) )
2021-11-30 23:06:32 +03:00
defer finished ( )
2020-05-17 02:31:38 +03:00
if err := t . fun ( ctx , doer , config ) ; err != nil {
2022-03-29 04:31:07 +03:00
var message string
var status string
2021-11-10 08:13:16 +03:00
if db . IsErrCancelled ( err ) {
2022-03-29 04:31:07 +03:00
status = "cancelled"
message = err . ( db . ErrCancelled ) . Message
} else {
status = "error"
message = err . Error ( )
2020-05-17 02:31:38 +03:00
}
2022-03-29 04:31:07 +03:00
t . lock . Lock ( )
t . LastMessage = message
t . Status = status
t . LastDoer = doerName
t . lock . Unlock ( )
if err := admin_model . CreateNotice ( ctx , admin_model . NoticeTask , config . FormatMessage ( "en-US" , t . Name , "cancelled" , doerName , message ) ) ; err != nil {
2020-05-17 02:31:38 +03:00
log . Error ( "CreateNotice: %v" , err )
}
return
}
2022-03-29 04:31:07 +03:00
t . lock . Lock ( )
t . Status = "finished"
t . LastMessage = ""
t . LastDoer = doerName
t . lock . Unlock ( )
2020-08-05 23:40:36 +03:00
if config . DoNoticeOnSuccess ( ) {
2022-03-29 04:31:07 +03:00
if err := admin_model . CreateNotice ( ctx , admin_model . NoticeTask , config . FormatMessage ( "en-US" , t . Name , "finished" , doerName ) ) ; err != nil {
2020-08-05 23:40:36 +03:00
log . Error ( "CreateNotice: %v" , err )
}
2020-05-17 02:31:38 +03:00
}
} )
}
// 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
2021-11-24 12:49:20 +03:00
func RegisterTask ( name string , config Config , fun func ( context . Context , * user_model . User , Config ) error ) error {
2020-05-17 02:31:38 +03:00
log . Debug ( "Registering task: %s" , name )
2022-02-07 10:43:53 +03:00
i18nKey := "admin.dashboard." + name
if _ , ok := translation . TryTr ( "en-US" , i18nKey ) ; ! ok {
return fmt . Errorf ( "translation is missing for task %q, please add translation for %q" , name , i18nKey )
}
2020-05-17 02:31:38 +03:00
_ , 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
2021-11-24 12:49:20 +03:00
func RegisterTaskFatal ( name string , config Config , fun func ( context . Context , * user_model . User , Config ) error ) {
2020-05-17 02:31:38 +03:00
if err := RegisterTask ( name , config , fun ) ; err != nil {
log . Fatal ( "Unable to register cron task %s Error: %v" , name , err )
}
}