Skip to content

Commit 44781f9

Browse files
appleboywolfogredelvh
authored
Implement auto-cancellation of concurrent jobs if the event is push (#25716)
- cancel running jobs if the event is push - Add a new function `CancelRunningJobs` to cancel all running jobs of a run - Update `FindRunOptions` struct to include `Ref` field and update its condition in `toConds` function - Implement auto cancellation of running jobs in the same workflow in `notify` function related task: #22751 --------- Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com> Signed-off-by: appleboy <appleboy.tw@gmail.com> Co-authored-by: Jason Song <i@wolfogre.com> Co-authored-by: delvh <dev.lh@web.de>
1 parent 5db640a commit 44781f9

File tree

6 files changed

+127
-19
lines changed

6 files changed

+127
-19
lines changed

models/actions/run.go

+68-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type ActionRun struct {
3434
Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
3535
TriggerUserID int64 `xorm:"index"`
3636
TriggerUser *user_model.User `xorm:"-"`
37-
Ref string
37+
Ref string `xorm:"index"` // the commit/tag/… that caused the run
3838
CommitSHA string
3939
IsForkPullRequest bool // If this is triggered by a PR from a forked repository or an untrusted user, we need to check if it is approved and limit permissions when running the workflow.
4040
NeedApproval bool // may need approval if it's a fork pull request
@@ -164,6 +164,73 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
164164
return err
165165
}
166166

167+
// CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow.
168+
func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error {
169+
// Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'.
170+
runs, total, err := FindRuns(ctx, FindRunOptions{
171+
RepoID: repoID,
172+
Ref: ref,
173+
WorkflowID: workflowID,
174+
Status: []Status{StatusRunning, StatusWaiting},
175+
})
176+
if err != nil {
177+
return err
178+
}
179+
180+
// If there are no runs found, there's no need to proceed with cancellation, so return nil.
181+
if total == 0 {
182+
return nil
183+
}
184+
185+
// Iterate over each found run and cancel its associated jobs.
186+
for _, run := range runs {
187+
// Find all jobs associated with the current run.
188+
jobs, _, err := FindRunJobs(ctx, FindRunJobOptions{
189+
RunID: run.ID,
190+
})
191+
if err != nil {
192+
return err
193+
}
194+
195+
// Iterate over each job and attempt to cancel it.
196+
for _, job := range jobs {
197+
// Skip jobs that are already in a terminal state (completed, cancelled, etc.).
198+
status := job.Status
199+
if status.IsDone() {
200+
continue
201+
}
202+
203+
// If the job has no associated task (probably an error), set its status to 'Cancelled' and stop it.
204+
if job.TaskID == 0 {
205+
job.Status = StatusCancelled
206+
job.Stopped = timeutil.TimeStampNow()
207+
208+
// Update the job's status and stopped time in the database.
209+
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
210+
if err != nil {
211+
return err
212+
}
213+
214+
// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
215+
if n == 0 {
216+
return fmt.Errorf("job has changed, try again")
217+
}
218+
219+
// Continue with the next job.
220+
continue
221+
}
222+
223+
// If the job has an associated task, try to stop the task, effectively cancelling the job.
224+
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
225+
return err
226+
}
227+
}
228+
}
229+
230+
// Return nil to indicate successful cancellation of all running and waiting jobs.
231+
return nil
232+
}
233+
167234
// InsertRun inserts a run
168235
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
169236
ctx, commiter, err := db.TxContext(ctx)

models/actions/run_list.go

+14-10
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,13 @@ func (runs RunList) LoadRepos() error {
6666

6767
type FindRunOptions struct {
6868
db.ListOptions
69-
RepoID int64
70-
OwnerID int64
71-
WorkflowFileName string
72-
TriggerUserID int64
73-
Approved bool // not util.OptionalBool, it works only when it's true
74-
Status Status
69+
RepoID int64
70+
OwnerID int64
71+
WorkflowID string
72+
Ref string // the commit/tag/… that caused this workflow
73+
TriggerUserID int64
74+
Approved bool // not util.OptionalBool, it works only when it's true
75+
Status []Status
7576
}
7677

7778
func (opts FindRunOptions) toConds() builder.Cond {
@@ -82,17 +83,20 @@ func (opts FindRunOptions) toConds() builder.Cond {
8283
if opts.OwnerID > 0 {
8384
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
8485
}
85-
if opts.WorkflowFileName != "" {
86-
cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowFileName})
86+
if opts.WorkflowID != "" {
87+
cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowID})
8788
}
8889
if opts.TriggerUserID > 0 {
8990
cond = cond.And(builder.Eq{"trigger_user_id": opts.TriggerUserID})
9091
}
9192
if opts.Approved {
9293
cond = cond.And(builder.Gt{"approved_by": 0})
9394
}
94-
if opts.Status > StatusUnknown {
95-
cond = cond.And(builder.Eq{"status": opts.Status})
95+
if len(opts.Status) > 0 {
96+
cond = cond.And(builder.In("status", opts.Status))
97+
}
98+
if opts.Ref != "" {
99+
cond = cond.And(builder.Eq{"ref": opts.Ref})
96100
}
97101
return cond
98102
}

models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,8 @@ var migrations = []Migration{
517517
NewMigration("Reduce commit status", v1_21.ReduceCommitStatus),
518518
// v267 -> v268
519519
NewMigration("Add action_tasks_version table", v1_21.CreateActionTasksVersionTable),
520+
// v268 -> v269
521+
NewMigration("Update Action Ref", v1_21.UpdateActionsRefIndex),
520522
}
521523

522524
// GetCurrentDBVersion returns the current db version

models/migrations/v1_21/v268.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_21 //nolint
5+
6+
import (
7+
"xorm.io/xorm"
8+
)
9+
10+
// UpdateActionsRefIndex updates the index of actions ref field
11+
func UpdateActionsRefIndex(x *xorm.Engine) error {
12+
type ActionRun struct {
13+
Ref string `xorm:"index"` // the commit/tag/… causing the run
14+
}
15+
return x.Sync(new(ActionRun))
16+
}

routers/web/repo/actions/actions.go

+8-4
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,14 @@ func List(ctx *context.Context) {
150150
Page: page,
151151
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
152152
},
153-
RepoID: ctx.Repo.Repository.ID,
154-
WorkflowFileName: workflow,
155-
TriggerUserID: actorID,
156-
Status: actions_model.Status(status),
153+
RepoID: ctx.Repo.Repository.ID,
154+
WorkflowID: workflow,
155+
TriggerUserID: actorID,
156+
}
157+
158+
// if status is not StatusUnknown, it means user has selected a status filter
159+
if actions_model.Status(status) != actions_model.StatusUnknown {
160+
opts.Status = []actions_model.Status{actions_model.Status(status)}
157161
}
158162

159163
runs, total, err := actions_model.FindRuns(ctx, opts)

services/actions/notifier_helper.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -230,16 +230,31 @@ func notify(ctx context.Context, input *notifyInput) error {
230230
log.Error("jobparser.Parse: %v", err)
231231
continue
232232
}
233+
234+
// cancel running jobs if the event is push
235+
if run.Event == webhook_module.HookEventPush {
236+
// cancel running jobs of the same workflow
237+
if err := actions_model.CancelRunningJobs(
238+
ctx,
239+
run.RepoID,
240+
run.Ref,
241+
run.WorkflowID,
242+
); err != nil {
243+
log.Error("CancelRunningJobs: %v", err)
244+
}
245+
}
246+
233247
if err := actions_model.InsertRun(ctx, run, jobs); err != nil {
234248
log.Error("InsertRun: %v", err)
235249
continue
236250
}
237-
if jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID}); err != nil {
251+
252+
alljobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID})
253+
if err != nil {
238254
log.Error("FindRunJobs: %v", err)
239-
} else {
240-
CreateCommitStatus(ctx, jobs...)
255+
continue
241256
}
242-
257+
CreateCommitStatus(ctx, alljobs...)
243258
}
244259
return nil
245260
}

0 commit comments

Comments
 (0)