Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement auto-cancellation of concurrent jobs if the event is push #25716

Merged
merged 28 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a20c5c3
feat: implement auto-cancellation of concurrent jobs
appleboy Jul 6, 2023
a1b22e2
refactor: refactor status field to support multiple values
appleboy Jul 6, 2023
f066eb0
refactor: optimize job cancellation and action run indexing
appleboy Jul 7, 2023
b64ed17
refactor: refactor job cancellation process
appleboy Jul 7, 2023
e318f28
Merge branch 'main' into cancel
appleboy Jul 7, 2023
251be01
Merge branch 'main' into cancel
appleboy Jul 7, 2023
edeb7af
Merge branch 'main' into cancel
appleboy Jul 8, 2023
2db7378
Update models/actions/run.go
appleboy Jul 12, 2023
f383d9e
Merge branch 'main' into cancel
appleboy Jul 12, 2023
1b16a52
Merge branch 'main' into cancel
lunny Jul 13, 2023
a7ed008
refactor: refactor code to use `WorkflowID` instead of `WorkflowFileN…
appleboy Jul 21, 2023
18cd66f
refactor: refine job cancellation and notification logic
appleboy Jul 21, 2023
05f078d
refactor: refactor job cancellation in workflows
appleboy Jul 21, 2023
6b6b17c
Merge branch 'main' into cancel
appleboy Jul 21, 2023
231e237
refactor: improve code readability and test robustness
appleboy Jul 21, 2023
c542d1b
feat: implement migration to update actions ref index
appleboy Jul 21, 2023
d3edc58
Merge branch 'main' into cancel
appleboy Jul 22, 2023
2412e84
refactor: refactor notifier helper and improve job handling
appleboy Jul 22, 2023
5d78596
refactor: refactor status filter handling in `actions.go`
appleboy Jul 22, 2023
b0ab570
Merge branch 'main' into cancel
appleboy Jul 22, 2023
d7b577f
Apply suggestions from code review
wolfogre Jul 24, 2023
1af0878
Merge branch 'main' into cancel
GiteaBot Jul 24, 2023
e767742
Update models/actions/run.go
appleboy Jul 24, 2023
9ab6ec4
Update models/actions/run_list.go
appleboy Jul 24, 2023
d649e95
Update models/migrations/v1_21/v267.go
appleboy Jul 24, 2023
face538
Update services/actions/notifier_helper.go
wolfogre Jul 25, 2023
8a05c67
chore: update migration version and rename file
appleboy Jul 25, 2023
a79384d
Merge branch 'main' into cancel
appleboy Jul 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion models/actions/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type ActionRun struct {
Index int64 `xorm:"index unique(repo_index)"` // a unique number for each run of a repository
TriggerUserID int64 `xorm:"index"`
TriggerUser *user_model.User `xorm:"-"`
Ref string
Ref string `xorm:"index"` // the ref of the run
appleboy marked this conversation as resolved.
Show resolved Hide resolved
appleboy marked this conversation as resolved.
Show resolved Hide resolved
CommitSHA string
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.
NeedApproval bool // may need approval if it's a fork pull request
Expand Down Expand Up @@ -164,6 +164,73 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err
return err
}

// CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow.
func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error {
// Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'.
runs, total, err := FindRuns(ctx, FindRunOptions{
RepoID: repoID,
Ref: ref,
WorkflowID: workflowID,
Status: []Status{StatusRunning, StatusWaiting},
})
if err != nil {
return err
}

// If there are no runs found, there's no need to proceed with cancellation, so return nil.
if total == 0 {
return nil
}

// Iterate over each found run and cancel its associated jobs.
for _, run := range runs {
// Find all jobs associated with the current run.
jobs, _, err := FindRunJobs(ctx, FindRunJobOptions{
RunID: run.ID,
})
if err != nil {
return err
}

// Iterate over each job and attempt to cancel it.
for _, job := range jobs {
// Skip jobs that are already in a terminal state (completed, cancelled, etc.).
status := job.Status
if status.IsDone() {
continue
}

// If the job has no associated task (probably an error), set its status to 'Cancelled' and stop it.
if job.TaskID == 0 {
job.Status = StatusCancelled
job.Stopped = timeutil.TimeStampNow()

// Update the job's status and stopped time in the database.
n, err := UpdateRunJob(ctx, job, builder.Eq{"task_id": 0}, "status", "stopped")
if err != nil {
return err
}

// If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again.
if n == 0 {
return fmt.Errorf("job has changed, try again")
}

// Continue with the next job.
continue
}

// If the job has an associated task, try to stop the task, effectively cancelling the job.
if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil {
return err
}
}
}

// Return nil to indicate successful cancellation of all running and waiting jobs.
return nil
}

// InsertRun inserts a run
func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWorkflow) error {
ctx, commiter, err := db.TxContext(ctx)
Expand Down
24 changes: 14 additions & 10 deletions models/actions/run_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ func (runs RunList) LoadRepos() error {

type FindRunOptions struct {
db.ListOptions
RepoID int64
OwnerID int64
WorkflowFileName string
TriggerUserID int64
Approved bool // not util.OptionalBool, it works only when it's true
Status Status
RepoID int64
OwnerID int64
WorkflowID string
Ref string
appleboy marked this conversation as resolved.
Show resolved Hide resolved
TriggerUserID int64
Approved bool // not util.OptionalBool, it works only when it's true
Status []Status
}

func (opts FindRunOptions) toConds() builder.Cond {
Expand All @@ -82,17 +83,20 @@ func (opts FindRunOptions) toConds() builder.Cond {
if opts.OwnerID > 0 {
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
}
if opts.WorkflowFileName != "" {
cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowFileName})
if opts.WorkflowID != "" {
cond = cond.And(builder.Eq{"workflow_id": opts.WorkflowID})
}
if opts.TriggerUserID > 0 {
cond = cond.And(builder.Eq{"trigger_user_id": opts.TriggerUserID})
}
if opts.Approved {
cond = cond.And(builder.Gt{"approved_by": 0})
}
if opts.Status > StatusUnknown {
cond = cond.And(builder.Eq{"status": opts.Status})
if len(opts.Status) > 0 {
cond = cond.And(builder.In("status", opts.Status))
}
if opts.Ref != "" {
cond = cond.And(builder.Eq{"ref": opts.Ref})
}
return cond
}
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@ var migrations = []Migration{
NewMigration("Alter Actions Artifact table", v1_21.AlterActionArtifactTable),
// v266 -> v267
NewMigration("Reduce commit status", v1_21.ReduceCommitStatus),
// v267 -> v268
NewMigration("Update Action Ref", v1_21.UpdateActionsRefIndex),
}

// GetCurrentDBVersion returns the current db version
Expand Down
16 changes: 16 additions & 0 deletions models/migrations/v1_21/v267.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_21 //nolint

import (
"xorm.io/xorm"
)

// UpdateActionsRefIndex updates the index of actions ref field
func UpdateActionsRefIndex(x *xorm.Engine) error {
type ActionRun struct {
Ref string `xorm:"index"` // the ref of the run
appleboy marked this conversation as resolved.
Show resolved Hide resolved
}
return x.Sync(new(ActionRun))
}
12 changes: 8 additions & 4 deletions routers/web/repo/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,14 @@ func List(ctx *context.Context) {
Page: page,
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
},
RepoID: ctx.Repo.Repository.ID,
WorkflowFileName: workflow,
TriggerUserID: actorID,
Status: actions_model.Status(status),
RepoID: ctx.Repo.Repository.ID,
WorkflowID: workflow,
TriggerUserID: actorID,
}

// if status is not StatusUnknown, it means user has selected a status filter
if actions_model.Status(status) != actions_model.StatusUnknown {
opts.Status = []actions_model.Status{actions_model.Status(status)}
}

runs, total, err := actions_model.FindRuns(ctx, opts)
Expand Down
23 changes: 19 additions & 4 deletions services/actions/notifier_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,16 +230,31 @@ func notify(ctx context.Context, input *notifyInput) error {
log.Error("jobparser.Parse: %v", err)
continue
}

// cancel running jobs if the event is push
if run.Event == webhook_module.HookEventPush {
// cancel running jobs of the same workflow
if err := actions_model.CancelRunningJobs(
ctx,
run.ID,
wolfogre marked this conversation as resolved.
Show resolved Hide resolved
run.Ref,
wolfogre marked this conversation as resolved.
Show resolved Hide resolved
run.WorkflowID,
); err != nil {
log.Error("CancelRunningJobs: %v", err)
}
}

if err := actions_model.InsertRun(ctx, run, jobs); err != nil {
log.Error("InsertRun: %v", err)
continue
}
if jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID}); err != nil {

alljobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: run.ID})
lunny marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
log.Error("FindRunJobs: %v", err)
} else {
CreateCommitStatus(ctx, jobs...)
continue
}

CreateCommitStatus(ctx, alljobs...)
}
return nil
}
Expand Down