Skip to content

Commit 8cf3b61

Browse files
wolfogreGiteaBot
andauthored
Add optimistic lock to ActionRun table (#26563)
Should fix #26559. How xorm works: https://xorm.io/docs/chapter-06/1.lock/ --------- Co-authored-by: Giteabot <[email protected]>
1 parent 42cbe60 commit 8cf3b61

File tree

5 files changed

+53
-25
lines changed

5 files changed

+53
-25
lines changed

models/actions/run.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type ActionRun struct {
4343
EventPayload string `xorm:"LONGTEXT"`
4444
TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow
4545
Status Status `xorm:"index"`
46+
Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed
4647
Started timeutil.TimeStamp
4748
Stopped timeutil.TimeStamp
4849
Created timeutil.TimeStamp `xorm:"created"`
@@ -332,12 +333,22 @@ func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error)
332333
return run, nil
333334
}
334335

336+
// UpdateRun updates a run.
337+
// It requires the inputted run has Version set.
338+
// It will return error if the version is not matched (it means the run has been changed after loaded).
335339
func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
336340
sess := db.GetEngine(ctx).ID(run.ID)
337341
if len(cols) > 0 {
338342
sess.Cols(cols...)
339343
}
340-
_, err := sess.Update(run)
344+
affected, err := sess.Update(run)
345+
if err != nil {
346+
return err
347+
}
348+
if affected == 0 {
349+
return fmt.Errorf("run has changed")
350+
// It's impossible that the run is not found, since Gitea never deletes runs.
351+
}
341352

342353
if run.Status != 0 || util.SliceContains(cols, "status") {
343354
if run.RepoID == 0 {
@@ -358,7 +369,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
358369
}
359370
}
360371

361-
return err
372+
return nil
362373
}
363374

364375
type ActionRunIndex db.ResourceIndex

models/actions/run_job.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -114,32 +114,41 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
114114
if affected != 0 && util.SliceContains(cols, "status") && job.Status.IsWaiting() {
115115
// if the status of job changes to waiting again, increase tasks version.
116116
if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil {
117-
return affected, err
117+
return 0, err
118118
}
119119
}
120120

121121
if job.RunID == 0 {
122122
var err error
123123
if job, err = GetRunJobByID(ctx, job.ID); err != nil {
124-
return affected, err
124+
return 0, err
125125
}
126126
}
127127

128-
jobs, err := GetRunJobsByRunID(ctx, job.RunID)
129-
if err != nil {
130-
return affected, err
128+
{
129+
// Other goroutines may aggregate the status of the run and update it too.
130+
// So we need load the run and its jobs before updating the run.
131+
run, err := GetRunByID(ctx, job.RunID)
132+
if err != nil {
133+
return 0, err
134+
}
135+
jobs, err := GetRunJobsByRunID(ctx, job.RunID)
136+
if err != nil {
137+
return 0, err
138+
}
139+
run.Status = aggregateJobStatus(jobs)
140+
if run.Started.IsZero() && run.Status.IsRunning() {
141+
run.Started = timeutil.TimeStampNow()
142+
}
143+
if run.Stopped.IsZero() && run.Status.IsDone() {
144+
run.Stopped = timeutil.TimeStampNow()
145+
}
146+
if err := UpdateRun(ctx, run, "status", "started", "stopped"); err != nil {
147+
return 0, fmt.Errorf("update run %d: %w", run.ID, err)
148+
}
131149
}
132150

133-
runStatus := aggregateJobStatus(jobs)
134-
135-
run := &ActionRun{
136-
ID: job.RunID,
137-
Status: runStatus,
138-
}
139-
if runStatus.IsDone() {
140-
run.Stopped = timeutil.TimeStampNow()
141-
}
142-
return affected, UpdateRun(ctx, run)
151+
return affected, nil
143152
}
144153

145154
func aggregateJobStatus(jobs []*ActionRunJob) Status {

models/actions/task.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,6 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
317317
return nil, false, nil
318318
}
319319

320-
if job.Run.Status.IsWaiting() {
321-
job.Run.Status = StatusRunning
322-
job.Run.Started = now
323-
if err := UpdateRun(ctx, job.Run, "status", "started"); err != nil {
324-
return nil, false, err
325-
}
326-
}
327-
328320
task.Job = job
329321

330322
if err := commiter.Commit(); err != nil {

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,8 @@ var migrations = []Migration{
524524
NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo),
525525
// v271 -> v272
526526
NewMigration("Allow archiving labels", v1_21.AddArchivedUnixColumInLabelTable),
527+
// v272 -> v273
528+
NewMigration("Add Version to ActionRun table", v1_21.AddVersionToActionRunTable),
527529
}
528530

529531
// GetCurrentDBVersion returns the current db version

models/migrations/v1_21/v272.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_21 //nolint
5+
import (
6+
"xorm.io/xorm"
7+
)
8+
9+
func AddVersionToActionRunTable(x *xorm.Engine) error {
10+
type ActionRun struct {
11+
Version int `xorm:"version default 0"`
12+
}
13+
return x.Sync(new(ActionRun))
14+
}

0 commit comments

Comments
 (0)