From c8fffc6cd628be60c595bfb97f18ad696f578a59 Mon Sep 17 00:00:00 2001 From: logikonline Date: Wed, 28 Jan 2026 01:27:14 -0500 Subject: [PATCH] fix(actions): improve unhealthy runner job assignment logic Refine unhealthy runner behavior to only skip jobs when a healthy AND idle alternative exists. Previously, unhealthy runners would skip jobs if any healthy runner existed, even if busy. Now unhealthy runners take jobs when all alternatives are either unhealthy or busy. Rename isOnlyMatchingRunner to hasHealthyIdleAlternative to better reflect the actual check being performed. Also add cleanup for leftover vault clones in Windows build step and ensure HOME directory exists in release job. --- .gitea/workflows/build.yml | 4 ++++ models/actions/task.go | 35 +++++++++++++++++------------------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 5c21767dc6..d509f2e6ee 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -299,6 +299,9 @@ jobs: env: VAULT_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | + # Clean up any leftover vault clone from previous runs + Remove-Item -Recurse -Force "$env:TEMP\gitcaddy-vault" -ErrorAction SilentlyContinue + # Clone vault repo git -c "http.extraheader=Authorization: token $($env:VAULT_TOKEN)" clone --depth 1 https://direct.git.marketally.com/gitcaddy/gitcaddy-vault.git "$env:TEMP\gitcaddy-vault" @@ -601,6 +604,7 @@ jobs: env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | + mkdir -p "$HOME" git config --global http.https://git.marketally.com/.extraheader "Authorization: token ${RELEASE_TOKEN}" git config --global http.https://direct.git.marketally.com/.extraheader "Authorization: token ${RELEASE_TOKEN}" diff --git a/models/actions/task.go b/models/actions/task.go index 8b0aded093..a3c364d151 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -285,15 +285,15 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask log.Trace("runner labels: %v", runner.AgentLabels) for _, v := range jobs { if runner.CanMatchLabels(v.RunsOn) { - // If runner is unhealthy, only assign if it's the only matching runner + // If runner is unhealthy, only skip if a healthy idle alternative exists if runnerUnhealthy { - if isOnlyMatchingRunner(ctx, runner, v) { - log.Info("Assigning job %d to unhealthy runner %s (only matching runner)", v.ID, runner.Name) - job = v - break + if hasHealthyIdleAlternative(ctx, runner, v) { + log.Trace("Skipping job %d for unhealthy runner %s: healthy idle alternative available", v.ID, runner.Name) + continue } - log.Trace("Skipping job %d for unhealthy runner %s: other healthy runners available", v.ID, runner.Name) - continue + log.Info("Assigning job %d to unhealthy runner %s (no healthy idle alternative)", v.ID, runner.Name) + job = v + break } // Check if this runner should get this job based on bandwidth @@ -595,32 +595,31 @@ func GetCurrentTasksForRunners(ctx context.Context, runnerIDs []int64) (map[int6 return result, nil } -// isOnlyMatchingRunner checks if this runner is the only one that can handle the job. -// Used to ensure unhealthy runners still get assigned jobs if they're the only option. -func isOnlyMatchingRunner(ctx context.Context, runner *ActionRunner, job *ActionRunJob) bool { +// hasHealthyIdleAlternative checks if there is another online, healthy, and idle +// runner that can handle this job. Used to decide whether an unhealthy runner +// should skip a job: only skip if a healthy idle alternative actually exists. +// If all alternatives are also unhealthy or busy, the unhealthy runner takes the job. +func hasHealthyIdleAlternative(ctx context.Context, runner *ActionRunner, job *ActionRunJob) bool { runners, err := db.Find[ActionRunner](ctx, FindRunnerOptions{ IsOnline: optional.Some(true), }) if err != nil { log.Error("Failed to find runners for job %d: %v", job.ID, err) - return true // Assume we're the only one if we can't check + return false // No alternative found, assign to current runner } for _, r := range runners { - // Skip self if r.ID == runner.ID { continue } - // Skip offline runners if !r.IsOnline() { continue } - // Check if this runner can handle the job - if r.CanMatchLabels(job.RunsOn) { - // Found another runner that can handle it - return false + // Only count as an alternative if it can match labels, is healthy, and is idle + if r.CanMatchLabels(job.RunsOn) && r.IsHealthy() && isRunnerIdle(ctx, r) { + return true } } - return true + return false }