gitea源码

job_emitter.go 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package actions
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. actions_model "code.gitea.io/gitea/models/actions"
  9. "code.gitea.io/gitea/models/db"
  10. "code.gitea.io/gitea/modules/graceful"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/queue"
  13. notify_service "code.gitea.io/gitea/services/notify"
  14. "github.com/nektos/act/pkg/jobparser"
  15. "xorm.io/builder"
  16. )
  17. var jobEmitterQueue *queue.WorkerPoolQueue[*jobUpdate]
  18. type jobUpdate struct {
  19. RunID int64
  20. }
  21. func EmitJobsIfReady(runID int64) error {
  22. err := jobEmitterQueue.Push(&jobUpdate{
  23. RunID: runID,
  24. })
  25. if errors.Is(err, queue.ErrAlreadyInQueue) {
  26. return nil
  27. }
  28. return err
  29. }
  30. func jobEmitterQueueHandler(items ...*jobUpdate) []*jobUpdate {
  31. ctx := graceful.GetManager().ShutdownContext()
  32. var ret []*jobUpdate
  33. for _, update := range items {
  34. if err := checkJobsOfRun(ctx, update.RunID); err != nil {
  35. ret = append(ret, update)
  36. }
  37. }
  38. return ret
  39. }
  40. func checkJobsOfRun(ctx context.Context, runID int64) error {
  41. jobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: runID})
  42. if err != nil {
  43. return err
  44. }
  45. var updatedjobs []*actions_model.ActionRunJob
  46. if err := db.WithTx(ctx, func(ctx context.Context) error {
  47. idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
  48. for _, job := range jobs {
  49. idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
  50. }
  51. updates := newJobStatusResolver(jobs).Resolve()
  52. for _, job := range jobs {
  53. if status, ok := updates[job.ID]; ok {
  54. job.Status = status
  55. if n, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": actions_model.StatusBlocked}, "status"); err != nil {
  56. return err
  57. } else if n != 1 {
  58. return fmt.Errorf("no affected for updating blocked job %v", job.ID)
  59. }
  60. updatedjobs = append(updatedjobs, job)
  61. }
  62. }
  63. return nil
  64. }); err != nil {
  65. return err
  66. }
  67. CreateCommitStatus(ctx, jobs...)
  68. for _, job := range updatedjobs {
  69. _ = job.LoadAttributes(ctx)
  70. notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
  71. }
  72. if len(jobs) > 0 {
  73. runUpdated := true
  74. for _, job := range jobs {
  75. if !job.Status.IsDone() {
  76. runUpdated = false
  77. break
  78. }
  79. }
  80. if runUpdated {
  81. NotifyWorkflowRunStatusUpdateWithReload(ctx, jobs[0])
  82. }
  83. }
  84. return nil
  85. }
  86. func NotifyWorkflowRunStatusUpdateWithReload(ctx context.Context, job *actions_model.ActionRunJob) {
  87. job.Run = nil
  88. if err := job.LoadAttributes(ctx); err != nil {
  89. log.Error("LoadAttributes: %v", err)
  90. return
  91. }
  92. notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
  93. }
  94. type jobStatusResolver struct {
  95. statuses map[int64]actions_model.Status
  96. needs map[int64][]int64
  97. jobMap map[int64]*actions_model.ActionRunJob
  98. }
  99. func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
  100. idToJobs := make(map[string][]*actions_model.ActionRunJob, len(jobs))
  101. jobMap := make(map[int64]*actions_model.ActionRunJob)
  102. for _, job := range jobs {
  103. idToJobs[job.JobID] = append(idToJobs[job.JobID], job)
  104. jobMap[job.ID] = job
  105. }
  106. statuses := make(map[int64]actions_model.Status, len(jobs))
  107. needs := make(map[int64][]int64, len(jobs))
  108. for _, job := range jobs {
  109. statuses[job.ID] = job.Status
  110. for _, need := range job.Needs {
  111. for _, v := range idToJobs[need] {
  112. needs[job.ID] = append(needs[job.ID], v.ID)
  113. }
  114. }
  115. }
  116. return &jobStatusResolver{
  117. statuses: statuses,
  118. needs: needs,
  119. jobMap: jobMap,
  120. }
  121. }
  122. func (r *jobStatusResolver) Resolve() map[int64]actions_model.Status {
  123. ret := map[int64]actions_model.Status{}
  124. for i := 0; i < len(r.statuses); i++ {
  125. updated := r.resolve()
  126. if len(updated) == 0 {
  127. return ret
  128. }
  129. for k, v := range updated {
  130. ret[k] = v
  131. r.statuses[k] = v
  132. }
  133. }
  134. return ret
  135. }
  136. func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
  137. ret := map[int64]actions_model.Status{}
  138. for id, status := range r.statuses {
  139. if status != actions_model.StatusBlocked {
  140. continue
  141. }
  142. allDone, allSucceed := true, true
  143. for _, need := range r.needs[id] {
  144. needStatus := r.statuses[need]
  145. if !needStatus.IsDone() {
  146. allDone = false
  147. }
  148. if needStatus.In(actions_model.StatusFailure, actions_model.StatusCancelled, actions_model.StatusSkipped) {
  149. allSucceed = false
  150. }
  151. }
  152. if allDone {
  153. if allSucceed {
  154. ret[id] = actions_model.StatusWaiting
  155. } else {
  156. // Check if the job has an "if" condition
  157. hasIf := false
  158. if wfJobs, _ := jobparser.Parse(r.jobMap[id].WorkflowPayload); len(wfJobs) == 1 {
  159. _, wfJob := wfJobs[0].Job()
  160. hasIf = len(wfJob.If.Value) > 0
  161. }
  162. if hasIf {
  163. // act_runner will check the "if" condition
  164. ret[id] = actions_model.StatusWaiting
  165. } else {
  166. // If the "if" condition is empty and not all dependent jobs completed successfully,
  167. // the job should be skipped.
  168. ret[id] = actions_model.StatusSkipped
  169. }
  170. }
  171. }
  172. }
  173. return ret
  174. }