gitea

Зеркало из https://github.com/go-gitea/gitea
Форк
0
/
tasks.go 
230 строк · 6.1 Кб
1
// Copyright 2020 The Gitea Authors. All rights reserved.
2
// SPDX-License-Identifier: MIT
3

4
package cron
5

6
import (
7
	"context"
8
	"fmt"
9
	"reflect"
10
	"strings"
11
	"sync"
12
	"time"
13

14
	"code.gitea.io/gitea/models/db"
15
	system_model "code.gitea.io/gitea/models/system"
16
	user_model "code.gitea.io/gitea/models/user"
17
	"code.gitea.io/gitea/modules/graceful"
18
	"code.gitea.io/gitea/modules/log"
19
	"code.gitea.io/gitea/modules/process"
20
	"code.gitea.io/gitea/modules/setting"
21
	"code.gitea.io/gitea/modules/translation"
22
)
23

24
var (
25
	lock     = sync.Mutex{}
26
	started  = false
27
	tasks    = []*Task{}
28
	tasksMap = map[string]*Task{}
29
)
30

31
// Task represents a Cron task
32
type Task struct {
33
	lock        sync.Mutex
34
	Name        string
35
	config      Config
36
	fun         func(context.Context, *user_model.User, Config) error
37
	Status      string
38
	LastMessage string
39
	LastDoer    string
40
	ExecTimes   int64
41
	// This stores the time of the last manual run of this task.
42
	LastRun time.Time
43
}
44

45
// DoRunAtStart returns if this task should run at the start
46
func (t *Task) DoRunAtStart() bool {
47
	return t.config.DoRunAtStart()
48
}
49

50
// IsEnabled returns if this task is enabled as cron task
51
func (t *Task) IsEnabled() bool {
52
	return t.config.IsEnabled()
53
}
54

55
// GetConfig will return a copy of the task's config
56
func (t *Task) GetConfig() Config {
57
	if reflect.TypeOf(t.config).Kind() == reflect.Ptr {
58
		// Pointer:
59
		return reflect.New(reflect.ValueOf(t.config).Elem().Type()).Interface().(Config)
60
	}
61
	// Not pointer:
62
	return reflect.New(reflect.TypeOf(t.config)).Elem().Interface().(Config)
63
}
64

65
// Run will run the task incrementing the cron counter with no user defined
66
func (t *Task) Run() {
67
	t.RunWithUser(&user_model.User{
68
		ID:        -1,
69
		Name:      "(Cron)",
70
		LowerName: "(cron)",
71
	}, t.config)
72
}
73

74
// RunWithUser will run the task incrementing the cron counter at the time with User
75
func (t *Task) RunWithUser(doer *user_model.User, config Config) {
76
	if !taskStatusTable.StartIfNotRunning(t.Name) {
77
		return
78
	}
79
	t.lock.Lock()
80
	if config == nil {
81
		config = t.config
82
	}
83
	t.ExecTimes++
84
	t.lock.Unlock()
85
	defer func() {
86
		taskStatusTable.Stop(t.Name)
87
	}()
88
	graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
89
		defer func() {
90
			if err := recover(); err != nil {
91
				// Recover a panic within the execution of the task.
92
				combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
93
				log.Error("PANIC whilst running task: %s Value: %v", t.Name, combinedErr)
94
			}
95
		}()
96
		// Store the time of this run, before the function is executed, so it
97
		// matches the behavior of what the cron library does.
98
		t.lock.Lock()
99
		t.LastRun = time.Now()
100
		t.lock.Unlock()
101

102
		pm := process.GetManager()
103
		doerName := ""
104
		if doer != nil && doer.ID != -1 {
105
			doerName = doer.Name
106
		}
107

108
		ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "process", doerName))
109
		defer finished()
110

111
		if err := t.fun(ctx, doer, config); err != nil {
112
			var message string
113
			var status string
114
			if db.IsErrCancelled(err) {
115
				status = "cancelled"
116
				message = err.(db.ErrCancelled).Message
117
			} else {
118
				status = "error"
119
				message = err.Error()
120
			}
121

122
			t.lock.Lock()
123
			t.LastMessage = message
124
			t.Status = status
125
			t.LastDoer = doerName
126
			t.lock.Unlock()
127

128
			if err := system_model.CreateNotice(ctx, system_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "cancelled", doerName, message)); err != nil {
129
				log.Error("CreateNotice: %v", err)
130
			}
131
			return
132
		}
133

134
		t.lock.Lock()
135
		t.Status = "finished"
136
		t.LastMessage = ""
137
		t.LastDoer = doerName
138
		t.lock.Unlock()
139

140
		if config.DoNoticeOnSuccess() {
141
			if err := system_model.CreateNotice(ctx, system_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "finished", doerName)); err != nil {
142
				log.Error("CreateNotice: %v", err)
143
			}
144
		}
145
	})
146
}
147

148
// GetTask gets the named task
149
func GetTask(name string) *Task {
150
	lock.Lock()
151
	defer lock.Unlock()
152
	log.Info("Getting %s in %v", name, tasksMap[name])
153

154
	return tasksMap[name]
155
}
156

157
// RegisterTask allows a task to be registered with the cron service
158
func RegisterTask(name string, config Config, fun func(context.Context, *user_model.User, Config) error) error {
159
	log.Debug("Registering task: %s", name)
160

161
	i18nKey := "admin.dashboard." + name
162
	if value := translation.NewLocale("en-US").TrString(i18nKey); value == i18nKey {
163
		return fmt.Errorf("translation is missing for task %q, please add translation for %q", name, i18nKey)
164
	}
165

166
	_, err := setting.GetCronSettings(name, config)
167
	if err != nil {
168
		log.Error("Unable to register cron task with name: %s Error: %v", name, err)
169
		return err
170
	}
171

172
	task := &Task{
173
		Name:   name,
174
		config: config,
175
		fun:    fun,
176
	}
177
	lock.Lock()
178
	locked := true
179
	defer func() {
180
		if locked {
181
			lock.Unlock()
182
		}
183
	}()
184
	if _, has := tasksMap[task.Name]; has {
185
		log.Error("A task with this name: %s has already been registered", name)
186
		return fmt.Errorf("duplicate task with name: %s", task.Name)
187
	}
188

189
	if config.IsEnabled() {
190
		// We cannot use the entry return as there is no way to lock it
191
		if err := addTaskToScheduler(task); err != nil {
192
			return err
193
		}
194
	}
195

196
	tasks = append(tasks, task)
197
	tasksMap[task.Name] = task
198
	if started && config.IsEnabled() && config.DoRunAtStart() {
199
		lock.Unlock()
200
		locked = false
201
		task.Run()
202
	}
203

204
	return nil
205
}
206

207
// RegisterTaskFatal will register a task but if there is an error log.Fatal
208
func RegisterTaskFatal(name string, config Config, fun func(context.Context, *user_model.User, Config) error) {
209
	if err := RegisterTask(name, config, fun); err != nil {
210
		log.Fatal("Unable to register cron task %s Error: %v", name, err)
211
	}
212
}
213

214
func addTaskToScheduler(task *Task) error {
215
	tags := []string{task.Name, task.config.GetSchedule()} // name and schedule can't be get from job, so we add them as tag
216
	if scheduleHasSeconds(task.config.GetSchedule()) {
217
		scheduler = scheduler.CronWithSeconds(task.config.GetSchedule())
218
	} else {
219
		scheduler = scheduler.Cron(task.config.GetSchedule())
220
	}
221
	if _, err := scheduler.Tag(tags...).Do(task.Run); err != nil {
222
		log.Error("Unable to register cron task with name: %s Error: %v", task.Name, err)
223
		return err
224
	}
225
	return nil
226
}
227

228
func scheduleHasSeconds(schedule string) bool {
229
	return len(strings.Fields(schedule)) >= 6
230
}
231

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.