4
4
package actions
5
5
6
6
import (
7
+ "bytes"
7
8
"context"
8
9
"errors"
9
10
"fmt"
10
11
11
12
actions_model "code.gitea.io/gitea/models/actions"
12
13
"code.gitea.io/gitea/models/db"
13
14
"code.gitea.io/gitea/modules/graceful"
15
+ "code.gitea.io/gitea/modules/log"
14
16
"code.gitea.io/gitea/modules/queue"
15
17
16
18
"github.com/nektos/act/pkg/jobparser"
19
+ act_model "github.com/nektos/act/pkg/model"
17
20
"xorm.io/builder"
18
21
)
19
22
@@ -55,7 +58,7 @@ func checkJobsByRunID(ctx context.Context, runID int64) error {
55
58
56
59
return db .WithTx (ctx , func (ctx context.Context ) error {
57
60
// check jobs of the current run
58
- if err := checkJobsOfRun (ctx , runID ); err != nil {
61
+ if err := checkJobsOfRun (ctx , run ); err != nil {
59
62
return err
60
63
}
61
64
@@ -78,7 +81,7 @@ func checkJobsByRunID(ctx context.Context, runID int64) error {
78
81
if cRun .NeedApproval {
79
82
continue
80
83
}
81
- if err := checkJobsOfRun (ctx , cRun . ID ); err != nil {
84
+ if err := checkJobsOfRun (ctx , cRun ); err != nil {
82
85
return err
83
86
}
84
87
break // only run one blocked action run with the same concurrency group
@@ -87,18 +90,23 @@ func checkJobsByRunID(ctx context.Context, runID int64) error {
87
90
})
88
91
}
89
92
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 })
92
95
if err != nil {
93
96
return err
94
97
}
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
+
95
104
if err := db .WithTx (ctx , func (ctx context.Context ) error {
96
- idToJobs := make (map [string ][]* actions_model.ActionRunJob , len (jobs ))
97
105
for _ , job := range jobs {
98
- idToJobs [ job .JobID ] = append ( idToJobs [ job . JobID ], job )
106
+ job .Run = run
99
107
}
100
108
101
- updates := newJobStatusResolver (jobs ).Resolve ()
109
+ updates := newJobStatusResolver (jobs , vars ).Resolve (ctx )
102
110
for _ , job := range jobs {
103
111
if status , ok := updates [job .ID ]; ok {
104
112
job .Status = status
@@ -121,9 +129,10 @@ type jobStatusResolver struct {
121
129
statuses map [int64 ]actions_model.Status
122
130
needs map [int64 ][]int64
123
131
jobMap map [int64 ]* actions_model.ActionRunJob
132
+ vars map [string ]string
124
133
}
125
134
126
- func newJobStatusResolver (jobs actions_model.ActionJobList ) * jobStatusResolver {
135
+ func newJobStatusResolver (jobs actions_model.ActionJobList , vars map [ string ] string ) * jobStatusResolver {
127
136
idToJobs := make (map [string ][]* actions_model.ActionRunJob , len (jobs ))
128
137
jobMap := make (map [int64 ]* actions_model.ActionRunJob )
129
138
for _ , job := range jobs {
@@ -145,13 +154,14 @@ func newJobStatusResolver(jobs actions_model.ActionJobList) *jobStatusResolver {
145
154
statuses : statuses ,
146
155
needs : needs ,
147
156
jobMap : jobMap ,
157
+ vars : vars ,
148
158
}
149
159
}
150
160
151
- func (r * jobStatusResolver ) Resolve () map [int64 ]actions_model.Status {
161
+ func (r * jobStatusResolver ) Resolve (ctx context. Context ) map [int64 ]actions_model.Status {
152
162
ret := map [int64 ]actions_model.Status {}
153
163
for i := 0 ; i < len (r .statuses ); i ++ {
154
- updated := r .resolve ()
164
+ updated := r .resolve (ctx )
155
165
if len (updated ) == 0 {
156
166
return ret
157
167
}
@@ -163,7 +173,7 @@ func (r *jobStatusResolver) Resolve() map[int64]actions_model.Status {
163
173
return ret
164
174
}
165
175
166
- func (r * jobStatusResolver ) resolve () map [int64 ]actions_model.Status {
176
+ func (r * jobStatusResolver ) resolve (ctx context. Context ) map [int64 ]actions_model.Status {
167
177
ret := map [int64 ]actions_model.Status {}
168
178
for id , status := range r .statuses {
169
179
if status != actions_model .StatusBlocked {
@@ -180,6 +190,17 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
180
190
}
181
191
}
182
192
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
+
183
204
if allSucceed {
184
205
ret [id ] = actions_model .StatusWaiting
185
206
} else {
@@ -203,3 +224,85 @@ func (r *jobStatusResolver) resolve() map[int64]actions_model.Status {
203
224
}
204
225
return ret
205
226
}
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