44package actions
55
66import (
7+ "bytes"
78 "context"
89 "errors"
910 "fmt"
1011
1112 actions_model "code.gitea.io/gitea/models/actions"
1213 "code.gitea.io/gitea/models/db"
1314 "code.gitea.io/gitea/modules/graceful"
15+ "code.gitea.io/gitea/modules/log"
1416 "code.gitea.io/gitea/modules/queue"
1517
1618 "github.com/nektos/act/pkg/jobparser"
19+ act_model "github.com/nektos/act/pkg/model"
1720 "xorm.io/builder"
1821)
1922
@@ -55,7 +58,7 @@ func checkJobsByRunID(ctx context.Context, runID int64) error {
5558
5659 return db .WithTx (ctx , func (ctx context.Context ) error {
5760 // check jobs of the current run
58- if err := checkJobsOfRun (ctx , runID ); err != nil {
61+ if err := checkJobsOfRun (ctx , run ); err != nil {
5962 return err
6063 }
6164
@@ -78,7 +81,7 @@ func checkJobsByRunID(ctx context.Context, runID int64) error {
7881 if cRun .NeedApproval {
7982 continue
8083 }
81- if err := checkJobsOfRun (ctx , cRun . ID ); err != nil {
84+ if err := checkJobsOfRun (ctx , cRun ); err != nil {
8285 return err
8386 }
8487 break // only run one blocked action run with the same concurrency group
@@ -87,18 +90,23 @@ func checkJobsByRunID(ctx context.Context, runID int64) error {
8790 })
8891}
8992
90- func checkJobsOfRun (ctx context.Context , runID int64 ) error {
91- jobs , err := db .Find [actions_model.ActionRunJob ](ctx , actions_model.FindRunJobOptions {RunID : runID })
93+ func checkJobsOfRun (ctx context.Context , run * actions_model. ActionRun ) error {
94+ jobs , err := db .Find [actions_model.ActionRunJob ](ctx , actions_model.FindRunJobOptions {RunID : run . ID })
9295 if err != nil {
9396 return err
9497 }
98+
99+ vars , err := actions_model .GetVariablesOfRun (ctx , run )
100+ if err != nil {
101+ return fmt .Errorf ("get run %d variables: %w" , run .ID , err )
102+ }
103+
95104 if err := db .WithTx (ctx , func (ctx context.Context ) error {
96- idToJobs := make (map [string ][]* actions_model.ActionRunJob , len (jobs ))
97105 for _ , job := range jobs {
98- idToJobs [ job .JobID ] = append ( idToJobs [ job . JobID ], job )
106+ job .Run = run
99107 }
100108
101- updates := newJobStatusResolver (jobs ).Resolve ()
109+ updates := newJobStatusResolver (jobs , vars ).Resolve (ctx )
102110 for _ , job := range jobs {
103111 if status , ok := updates [job .ID ]; ok {
104112 job .Status = status
@@ -121,9 +129,10 @@ type jobStatusResolver struct {
121129 statuses map [int64 ]actions_model.Status
122130 needs map [int64 ][]int64
123131 jobMap map [int64 ]* actions_model.ActionRunJob
132+ vars map [string ]string
124133}
125134
126- func newJobStatusResolver (jobs actions_model.ActionJobList ) * jobStatusResolver {
135+ func newJobStatusResolver (jobs actions_model.ActionJobList , vars map [ string ] string ) * jobStatusResolver {
127136 idToJobs := make (map [string ][]* actions_model.ActionRunJob , len (jobs ))
128137 jobMap := make (map [int64 ]* actions_model.ActionRunJob )
129138 for _ , job := range jobs {
@@ -145,13 +154,14 @@ func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
145154 statuses : statuses ,
146155 needs : needs ,
147156 jobMap : jobMap ,
157+ vars : vars ,
148158 }
149159}
150160
151- func (r * jobStatusResolver ) Resolve () map [int64 ]actions_model.Status {
161+ func (r * jobStatusResolver ) Resolve (ctx context. Context ) map [int64 ]actions_model.Status {
152162 ret := map [int64 ]actions_model.Status {}
153163 for i := 0 ; i < len (r .statuses ); i ++ {
154- updated := r .resolve ()
164+ updated := r .resolve (ctx )
155165 if len (updated ) == 0 {
156166 return ret
157167 }
@@ -163,7 +173,7 @@ func (r *jobStatusResolver) Resolve() map[int64]actions_model.Status {
163173 return ret
164174}
165175
166- func (r * jobStatusResolver ) resolve () map [int64 ]actions_model.Status {
176+ func (r * jobStatusResolver ) resolve (ctx context. Context ) map [int64 ]actions_model.Status {
167177 ret := map [int64 ]actions_model.Status {}
168178 for id , status := range r .statuses {
169179 if status != actions_model .StatusBlocked {
@@ -180,6 +190,17 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
180190 }
181191 }
182192 if allDone {
193+ // check concurrency
194+ blockedByJobConcurrency , err := checkJobConcurrency (ctx , r .jobMap [id ], r .vars )
195+ if err != nil {
196+ log .Error ("Check run %d job %d concurrency: %v. This job will stay blocked." )
197+ continue
198+ }
199+
200+ if blockedByJobConcurrency {
201+ continue
202+ }
203+
183204 if allSucceed {
184205 ret [id ] = actions_model .StatusWaiting
185206 } else {
@@ -203,3 +224,85 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
203224 }
204225 return ret
205226}
227+
228+ func checkJobConcurrency (ctx context.Context , actionRunJob * actions_model.ActionRunJob , vars map [string ]string ) (bool , error ) {
229+ if len (actionRunJob .RawConcurrencyGroup ) == 0 {
230+ return false , nil
231+ }
232+
233+ run := actionRunJob .Run
234+
235+ if len (actionRunJob .ConcurrencyGroup ) == 0 {
236+ rawConcurrency := & act_model.RawConcurrency {
237+ Group : actionRunJob .RawConcurrencyGroup ,
238+ CancelInProgress : actionRunJob .RawConcurrencyCancel ,
239+ }
240+
241+ gitCtx := jobparser .ToGitContext (GenerateGitContext (run , actionRunJob ))
242+
243+ actWorkflow , err := act_model .ReadWorkflow (bytes .NewReader (actionRunJob .WorkflowPayload ))
244+ if err != nil {
245+ return false , fmt .Errorf ("read workflow: %w" , err )
246+ }
247+ actJob := actWorkflow .GetJob (actionRunJob .JobID )
248+
249+ task , err := actions_model .GetTaskByID (ctx , actionRunJob .TaskID )
250+ if err != nil {
251+ return false , fmt .Errorf ("get task by id: %w" , err )
252+ }
253+ taskNeeds , err := FindTaskNeeds (ctx , task )
254+ if err != nil {
255+ return false , fmt .Errorf ("find task needs: %w" , err )
256+ }
257+
258+ jobResults := make (map [string ]* jobparser.JobResult , len (taskNeeds ))
259+ for jobID , taskNeed := range taskNeeds {
260+ jobResult := & jobparser.JobResult {
261+ Result : taskNeed .Result .String (),
262+ Outputs : taskNeed .Outputs ,
263+ }
264+ jobResults [jobID ] = jobResult
265+ }
266+
267+ actionRunJob .ConcurrencyGroup , actionRunJob .ConcurrencyCancel = jobparser .InterpolatJobConcurrency (rawConcurrency , actJob , gitCtx , vars , jobResults )
268+ if _ , err := actions_model .UpdateRunJob (ctx , & actions_model.ActionRunJob {
269+ ID : actionRunJob .ID ,
270+ ConcurrencyGroup : actionRunJob .ConcurrencyGroup ,
271+ ConcurrencyCancel : actionRunJob .ConcurrencyCancel ,
272+ }, nil ); err != nil {
273+ return false , fmt .Errorf ("update run job: %w" , err )
274+ }
275+ }
276+
277+ if actionRunJob .ConcurrencyCancel {
278+ // cancel previous jobs in the same concurrency group
279+ previousJobs , err := db .Find [actions_model.ActionRunJob ](ctx , actions_model.FindRunJobOptions {
280+ RepoID : actionRunJob .RepoID ,
281+ ConcurrencyGroup : actionRunJob .ConcurrencyGroup ,
282+ Statuses : []actions_model.Status {
283+ actions_model .StatusRunning ,
284+ actions_model .StatusWaiting ,
285+ actions_model .StatusBlocked ,
286+ },
287+ })
288+ if err != nil {
289+ return false , fmt .Errorf ("find previous jobs: %w" , err )
290+ }
291+ if err := actions_model .CancelJobs (ctx , previousJobs ); err != nil {
292+ return false , fmt .Errorf ("cancel previous jobs: %w" , err )
293+ }
294+ // we have cancelled all previous jobs, so this job does not need to be blocked
295+ return false , nil
296+ }
297+
298+ waitingConcurrentJobsNum , err := db .Count [actions_model.ActionRunJob ](ctx , actions_model.FindRunJobOptions {
299+ RepoID : actionRunJob .RepoID ,
300+ ConcurrencyGroup : actionRunJob .ConcurrencyGroup ,
301+ Statuses : []actions_model.Status {actions_model .StatusWaiting },
302+ })
303+ if err != nil {
304+ return false , fmt .Errorf ("count waiting jobs: %w" , err )
305+ }
306+
307+ return waitingConcurrentJobsNum > 0 , nil
308+ }
0 commit comments