1
1
mirror of https://github.com/go-gitea/gitea synced 2025-12-07 13:28:25 +00:00

Add a doctor command to fix inconsistent run status (#35840)

#35783 fixes an actions rerun bug. Due to this bug, some runs may be
incorrectly marked as `StatusWaiting` even though all the jobs are in
done status. These runs cannot be run or cancelled. This PR adds a new
doctor command to fix the inconsistent run status.

```
gitea doctor check --run fix-actions-unfinished-run-status --fix
```

Thanks to @ChristopherHX  for the test.
This commit is contained in:
Zettat123
2025-11-03 19:32:26 -07:00
committed by GitHub
parent de26c8acce
commit bb1f52347a
7 changed files with 186 additions and 1 deletions

View File

@@ -7,12 +7,17 @@ import (
"context"
"fmt"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
repo_service "code.gitea.io/gitea/services/repository"
"xorm.io/builder"
)
func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bool) error {
@@ -59,6 +64,95 @@ func disableMirrorActionsUnit(ctx context.Context, logger log.Logger, autofix bo
return nil
}
func fixUnfinishedRunStatus(ctx context.Context, logger log.Logger, autofix bool) error {
total := 0
inconsistent := 0
fixed := 0
cond := builder.In("status", []actions_model.Status{
actions_model.StatusWaiting,
actions_model.StatusRunning,
actions_model.StatusBlocked,
}).And(builder.Lt{"updated": timeutil.TimeStampNow().AddDuration(-setting.Actions.ZombieTaskTimeout)})
err := db.Iterate(
ctx,
cond,
func(ctx context.Context, run *actions_model.ActionRun) error {
total++
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
return fmt.Errorf("GetRunJobsByRunID: %w", err)
}
expected := actions_model.AggregateJobStatus(jobs)
if expected == run.Status {
return nil
}
inconsistent++
logger.Warn("Run %d (repo_id=%d, index=%d) has status %s, expected %s", run.ID, run.RepoID, run.Index, run.Status, expected)
if !autofix {
return nil
}
run.Started, run.Stopped = getRunTimestampsFromJobs(run, expected, jobs)
run.Status = expected
if err := actions_model.UpdateRun(ctx, run, "status", "started", "stopped"); err != nil {
return fmt.Errorf("UpdateRun: %w", err)
}
fixed++
return nil
},
)
if err != nil {
logger.Critical("Unable to iterate unfinished runs: %v", err)
return err
}
if inconsistent == 0 {
logger.Info("Checked %d unfinished runs; all statuses are consistent.", total)
return nil
}
if autofix {
logger.Info("Checked %d unfinished runs; fixed %d of %d runs.", total, fixed, inconsistent)
} else {
logger.Warn("Checked %d unfinished runs; found %d runs need to be fixed", total, inconsistent)
}
return nil
}
func getRunTimestampsFromJobs(run *actions_model.ActionRun, newStatus actions_model.Status, jobs actions_model.ActionJobList) (started, stopped timeutil.TimeStamp) {
started = run.Started
if (newStatus.IsRunning() || newStatus.IsDone()) && started.IsZero() {
var earliest timeutil.TimeStamp
for _, job := range jobs {
if job.Started > 0 && (earliest.IsZero() || job.Started < earliest) {
earliest = job.Started
}
}
started = earliest
}
stopped = run.Stopped
if newStatus.IsDone() && stopped.IsZero() {
var latest timeutil.TimeStamp
for _, job := range jobs {
if job.Stopped > latest {
latest = job.Stopped
}
}
stopped = latest
}
return started, stopped
}
func init() {
Register(&Check{
Title: "Disable the actions unit for all mirrors",
@@ -67,4 +161,11 @@ func init() {
Run: disableMirrorActionsUnit,
Priority: 9,
})
Register(&Check{
Title: "Fix inconsistent status for unfinished actions runs",
Name: "fix-actions-unfinished-run-status",
IsDefault: false,
Run: fixUnfinishedRunStatus,
Priority: 9,
})
}