loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 <teabot@gitea.io>

authored by

Jason Song
Giteabot
and committed by
GitHub
8cf3b61f 42cbe600

+53 -25
+13 -2
models/actions/run.go
··· 43 43 EventPayload string `xorm:"LONGTEXT"` 44 44 TriggerEvent string // the trigger event defined in the `on` configuration of the triggered workflow 45 45 Status Status `xorm:"index"` 46 + Version int `xorm:"version default 0"` // Status could be updated concomitantly, so an optimistic lock is needed 46 47 Started timeutil.TimeStamp 47 48 Stopped timeutil.TimeStamp 48 49 Created timeutil.TimeStamp `xorm:"created"` ··· 332 333 return run, nil 333 334 } 334 335 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). 335 339 func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error { 336 340 sess := db.GetEngine(ctx).ID(run.ID) 337 341 if len(cols) > 0 { 338 342 sess.Cols(cols...) 339 343 } 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 + } 341 352 342 353 if run.Status != 0 || util.SliceContains(cols, "status") { 343 354 if run.RepoID == 0 { ··· 358 369 } 359 370 } 360 371 361 - return err 372 + return nil 362 373 } 363 374 364 375 type ActionRunIndex db.ResourceIndex
+24 -15
models/actions/run_job.go
··· 114 114 if affected != 0 && util.SliceContains(cols, "status") && job.Status.IsWaiting() { 115 115 // if the status of job changes to waiting again, increase tasks version. 116 116 if err := IncreaseTaskVersion(ctx, job.OwnerID, job.RepoID); err != nil { 117 - return affected, err 117 + return 0, err 118 118 } 119 119 } 120 120 121 121 if job.RunID == 0 { 122 122 var err error 123 123 if job, err = GetRunJobByID(ctx, job.ID); err != nil { 124 - return affected, err 124 + return 0, err 125 125 } 126 126 } 127 127 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 + } 131 149 } 132 150 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 143 152 } 144 153 145 154 func aggregateJobStatus(jobs []*ActionRunJob) Status {
-8
models/actions/task.go
··· 317 317 return nil, false, nil 318 318 } 319 319 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 - 328 320 task.Job = job 329 321 330 322 if err := commiter.Commit(); err != nil {
+2
models/migrations/migrations.go
··· 524 524 NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo), 525 525 // v271 -> v272 526 526 NewMigration("Allow archiving labels", v1_21.AddArchivedUnixColumInLabelTable), 527 + // v272 -> v273 528 + NewMigration("Add Version to ActionRun table", v1_21.AddVersionToActionRunTable), 527 529 } 528 530 529 531 // GetCurrentDBVersion returns the current db version
+14
models/migrations/v1_21/v272.go
··· 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 + }