diff --git a/models/actions/task.go b/models/actions/task.go index 87bbc76ef8..34c61b3ffb 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -265,6 +265,12 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask } // Check max-parallel constraint for matrix jobs + // Note: This is a best-effort check with a small race window. Multiple runners + // could simultaneously check the count before any commits, potentially allowing + // the limit to be briefly exceeded (e.g., if limit=2 and count=1, two runners + // might both proceed). Perfect enforcement would require row-level locking across + // all jobs with the same run_id+job_id, which has a significant performance impact. + // The race window is small since jobs are picked and committed quickly. if v.MaxParallel > 0 { runningCount, err := CountRunningJobsByWorkflowAndRun(ctx, v.RunID, v.JobID) if err != nil { diff --git a/services/actions/task_assignment_test.go b/services/actions/task_assignment_test.go index 0da280185b..4ceb7d3d46 100644 --- a/services/actions/task_assignment_test.go +++ b/services/actions/task_assignment_test.go @@ -147,7 +147,6 @@ func TestMaxParallelJobStatusAndCounting(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // Create jobs with the test max-parallel value for i := range 5 { job := &actions_model.ActionRunJob{