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.

Fix bugs in rerunning jobs (#29955)

Fix #28761
Fix #27884
Fix #28093

## Changes

### Rerun all jobs
When rerun all jobs, status of the jobs with `needs` will be set to
`blocked` instead of `waiting`. Therefore, these jobs will not run until
the required jobs are completed.

### Rerun a single job
When a single job is rerun, its dependents should also be rerun, just
like GitHub does
(https://github.com/go-gitea/gitea/issues/28761#issuecomment-2008620820).
In this case, only the specified job will be set to `waiting`, its
dependents will be set to `blocked` to wait the job.

### Show warning if every job has `needs`
If every job in a workflow has `needs`, all jobs will be blocked and no
job can be run. So I add a warning message.

<img
src="https://github.com/go-gitea/gitea/assets/15528715/88f43511-2360-465d-be96-ee92b57ff67b"
width="480px" />

(cherry picked from commit 2f060c5834d81f0317c795fc281f9a07e03e5962)

authored by

Zettat123 and committed by
Earl Warren
8848b0ea 3d99b43d

+117 -6
+1
options/locale/locale_en-US.ini
··· 3673 3673 runs.workflow = Workflow 3674 3674 runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s 3675 3675 runs.no_matching_online_runner_helper = No matching online runner with label: %s 3676 + runs.no_job_without_needs = The workflow must contain at least one job without dependencies. 3676 3677 runs.actor = Actor 3677 3678 runs.status = Status 3678 3679 runs.actors_no_select = All actors
+9 -1
routers/web/repo/actions/actions.go
··· 104 104 workflows = append(workflows, workflow) 105 105 continue 106 106 } 107 - // Check whether have matching runner 107 + // The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run. 108 + hasJobWithoutNeeds := false 109 + // Check whether have matching runner and a job without "needs" 108 110 for _, j := range wf.Jobs { 111 + if !hasJobWithoutNeeds && len(j.Needs()) == 0 { 112 + hasJobWithoutNeeds = true 113 + } 109 114 runsOnList := j.RunsOn() 110 115 for _, ro := range runsOnList { 111 116 if strings.Contains(ro, "${{") { ··· 122 127 if workflow.ErrMsg != "" { 123 128 break 124 129 } 130 + } 131 + if !hasJobWithoutNeeds { 132 + workflow.ErrMsg = ctx.Locale.TrString("actions.runs.no_job_without_needs") 125 133 } 126 134 workflows = append(workflows, workflow) 127 135 }
+21 -5
routers/web/repo/actions/view.go
··· 353 353 return 354 354 } 355 355 356 - if jobIndexStr != "" { 357 - jobs = []*actions_model.ActionRunJob{job} 356 + if jobIndexStr == "" { // rerun all jobs 357 + for _, j := range jobs { 358 + // if the job has needs, it should be set to "blocked" status to wait for other jobs 359 + shouldBlock := len(j.Needs) > 0 360 + if err := rerunJob(ctx, j, shouldBlock); err != nil { 361 + ctx.Error(http.StatusInternalServerError, err.Error()) 362 + return 363 + } 364 + } 365 + ctx.JSON(http.StatusOK, struct{}{}) 366 + return 358 367 } 359 368 360 - for _, j := range jobs { 361 - if err := rerunJob(ctx, j); err != nil { 369 + rerunJobs := actions_service.GetAllRerunJobs(job, jobs) 370 + 371 + for _, j := range rerunJobs { 372 + // jobs other than the specified one should be set to "blocked" status 373 + shouldBlock := j.JobID != job.JobID 374 + if err := rerunJob(ctx, j, shouldBlock); err != nil { 362 375 ctx.Error(http.StatusInternalServerError, err.Error()) 363 376 return 364 377 } ··· 367 380 ctx.JSON(http.StatusOK, struct{}{}) 368 381 } 369 382 370 - func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob) error { 383 + func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shouldBlock bool) error { 371 384 status := job.Status 372 385 if !status.IsDone() { 373 386 return nil ··· 375 388 376 389 job.TaskID = 0 377 390 job.Status = actions_model.StatusWaiting 391 + if shouldBlock { 392 + job.Status = actions_model.StatusBlocked 393 + } 378 394 job.Started = 0 379 395 job.Stopped = 0 380 396
+38
services/actions/rerun.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package actions 5 + 6 + import ( 7 + actions_model "code.gitea.io/gitea/models/actions" 8 + "code.gitea.io/gitea/modules/container" 9 + ) 10 + 11 + // GetAllRerunJobs get all jobs that need to be rerun when job should be rerun 12 + func GetAllRerunJobs(job *actions_model.ActionRunJob, allJobs []*actions_model.ActionRunJob) []*actions_model.ActionRunJob { 13 + rerunJobs := []*actions_model.ActionRunJob{job} 14 + rerunJobsIDSet := make(container.Set[string]) 15 + rerunJobsIDSet.Add(job.JobID) 16 + 17 + for { 18 + found := false 19 + for _, j := range allJobs { 20 + if rerunJobsIDSet.Contains(j.JobID) { 21 + continue 22 + } 23 + for _, need := range j.Needs { 24 + if rerunJobsIDSet.Contains(need) { 25 + found = true 26 + rerunJobs = append(rerunJobs, j) 27 + rerunJobsIDSet.Add(j.JobID) 28 + break 29 + } 30 + } 31 + } 32 + if !found { 33 + break 34 + } 35 + } 36 + 37 + return rerunJobs 38 + }
+48
services/actions/rerun_test.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package actions 5 + 6 + import ( 7 + "testing" 8 + 9 + actions_model "code.gitea.io/gitea/models/actions" 10 + 11 + "github.com/stretchr/testify/assert" 12 + ) 13 + 14 + func TestGetAllRerunJobs(t *testing.T) { 15 + job1 := &actions_model.ActionRunJob{JobID: "job1"} 16 + job2 := &actions_model.ActionRunJob{JobID: "job2", Needs: []string{"job1"}} 17 + job3 := &actions_model.ActionRunJob{JobID: "job3", Needs: []string{"job2"}} 18 + job4 := &actions_model.ActionRunJob{JobID: "job4", Needs: []string{"job2", "job3"}} 19 + 20 + jobs := []*actions_model.ActionRunJob{job1, job2, job3, job4} 21 + 22 + testCases := []struct { 23 + job *actions_model.ActionRunJob 24 + rerunJobs []*actions_model.ActionRunJob 25 + }{ 26 + { 27 + job1, 28 + []*actions_model.ActionRunJob{job1, job2, job3, job4}, 29 + }, 30 + { 31 + job2, 32 + []*actions_model.ActionRunJob{job2, job3, job4}, 33 + }, 34 + { 35 + job3, 36 + []*actions_model.ActionRunJob{job3, job4}, 37 + }, 38 + { 39 + job4, 40 + []*actions_model.ActionRunJob{job4}, 41 + }, 42 + } 43 + 44 + for _, tc := range testCases { 45 + rerunJobs := GetAllRerunJobs(tc.job, jobs) 46 + assert.ElementsMatch(t, tc.rerunJobs, rerunJobs) 47 + } 48 + }