code.gitea.io/gitea@v1.21.7/services/actions/job_emitter.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package actions 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 11 actions_model "code.gitea.io/gitea/models/actions" 12 "code.gitea.io/gitea/models/db" 13 "code.gitea.io/gitea/modules/graceful" 14 "code.gitea.io/gitea/modules/queue" 15 16 "xorm.io/builder" 17 ) 18 19 var jobEmitterQueue *queue.WorkerPoolQueue[*jobUpdate] 20 21 type jobUpdate struct { 22 RunID int64 23 } 24 25 func EmitJobsIfReady(runID int64) error { 26 err := jobEmitterQueue.Push(&jobUpdate{ 27 RunID: runID, 28 }) 29 if errors.Is(err, queue.ErrAlreadyInQueue) { 30 return nil 31 } 32 return err 33 } 34 35 func jobEmitterQueueHandler(items ...*jobUpdate) []*jobUpdate { 36 ctx := graceful.GetManager().ShutdownContext() 37 var ret []*jobUpdate 38 for _, update := range items { 39 if err := checkJobsOfRun(ctx, update.RunID); err != nil { 40 ret = append(ret, update) 41 } 42 } 43 return ret 44 } 45 46 func checkJobsOfRun(ctx context.Context, runID int64) error { 47 jobs, _, err := actions_model.FindRunJobs(ctx, actions_model.FindRunJobOptions{RunID: runID}) 48 if err != nil { 49 return err 50 } 51 if err := db.WithTx(ctx, func(ctx context.Context) error { 52 idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs)) 53 for _, job := range jobs { 54 idToJobs[job.JobID] = append(idToJobs[job.JobID], job) 55 } 56 57 updates := newJobStatusResolver(jobs).Resolve() 58 for _, job := range jobs { 59 if status, ok := updates[job.ID]; ok { 60 job.Status = status 61 if n, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": actions_model.StatusBlocked}, "status"); err != nil { 62 return err 63 } else if n != 1 { 64 return fmt.Errorf("no affected for updating blocked job %v", job.ID) 65 } 66 } 67 } 68 return nil 69 }); err != nil { 70 return err 71 } 72 CreateCommitStatus(ctx, jobs...) 73 return nil 74 } 75 76 type jobStatusResolver struct { 77 statuses map[int64]actions_model.Status 78 needs map[int64][]int64 79 } 80 81 func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver { 82 idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs)) 83 for _, job := range jobs { 84 idToJobs[job.JobID] = append(idToJobs[job.JobID], job) 85 } 86 87 statuses := make(map[int64]actions_model.Status, len(jobs)) 88 needs := make(map[int64][]int64, len(jobs)) 89 for _, job := range jobs { 90 statuses[job.ID] = job.Status 91 for _, need := range job.Needs { 92 for _, v := range idToJobs[need] { 93 needs[job.ID] = append(needs[job.ID], v.ID) 94 } 95 } 96 } 97 return &jobStatusResolver{ 98 statuses: statuses, 99 needs: needs, 100 } 101 } 102 103 func (r *jobStatusResolver) Resolve() map[int64]actions_model.Status { 104 ret := map[int64]actions_model.Status{} 105 for i := 0; i < len(r.statuses); i++ { 106 updated := r.resolve() 107 if len(updated) == 0 { 108 return ret 109 } 110 for k, v := range updated { 111 ret[k] = v 112 r.statuses[k] = v 113 } 114 } 115 return ret 116 } 117 118 func (r *jobStatusResolver) resolve() map[int64]actions_model.Status { 119 ret := map[int64]actions_model.Status{} 120 for id, status := range r.statuses { 121 if status != actions_model.StatusBlocked { 122 continue 123 } 124 allDone, allSucceed := true, true 125 for _, need := range r.needs[id] { 126 needStatus := r.statuses[need] 127 if !needStatus.IsDone() { 128 allDone = false 129 } 130 if needStatus.In(actions_model.StatusFailure, actions_model.StatusCancelled, actions_model.StatusSkipped) { 131 allSucceed = false 132 } 133 } 134 if allDone { 135 if allSucceed { 136 ret[id] = actions_model.StatusWaiting 137 } else { 138 ret[id] = actions_model.StatusSkipped 139 } 140 } 141 } 142 return ret 143 }