Jobs report how often they went back in line; the Live test shows it (#150)
The jobs API returns placementTries: the times a hosted job was placed again before a runner took it (no machine, or a runner on a replaced image turned away). The Live test prints it for the probe and for each job of its main run, so a deploy's rollout shows whether jobs were turned away, and the probe now waits up to 10 minutes for a run whose jobs were started again. Claude-Session: https://claude.ai/code/session_01Ruyx3Qr4jYrZEpPi466deb Co-authored-by: Claude <noreply@anthropic.com>
Canberk Demirci authored · GitHub committed signed1 parent 8e39f86commit 617abd08596500e00e8f6e40954924737b8a1b96Browse files
3 files changed+11−5
modified.github/workflows/live-test.yml+7−4View file
| @@ -200,7 +200,8 @@ | ||
| 200 | 200 | before=$(api GET "/repos/$OWNER/$REPO/actions/runs?event=workflow_dispatch" | jq '.workflow_runs | length') |
| 201 | 201 | api POST "/repos/$OWNER/$REPO/actions/workflows/probe.yml/dispatches" '{"ref":"main"}' >/dev/null |
| 202 | 202 | run="" |
| 203 | - for i in $(seq 1 60); do | |
| 203 | + # Up to 10 minutes: a job turned away from a replaced image starts again after 30 s, 1, 2 and 4 minutes. | |
| 204 | + for i in $(seq 1 120); do | |
| 204 | 205 | runs=$(api GET "/repos/$OWNER/$REPO/actions/runs?event=workflow_dispatch") |
| 205 | 206 | if [ "$(jq '.workflow_runs | length' <<<"$runs")" -gt "$before" ]; then |
| 206 | 207 | run=$(jq -c '.workflow_runs[0]' <<<"$runs") |
| @@ -209,8 +210,9 @@ | ||
| 209 | 210 | sleep 5 |
| 210 | 211 | done |
| 211 | 212 | [ -n "$run" ] || { echo "::error::the probe workflow didn't start"; exit 1; } |
| 212 | - images=$(api GET "/repos/$OWNER/$REPO/actions/runs/$(jq -r .id <<<"$run")/jobs" | jq -r '.jobs[] | .runner.image // "no image reported"') | |
| 213 | - echo "probe jobs ran on: $(sort <<<"$images" | uniq -c | sed 's/^ *//' | paste -sd ',')" | |
| 213 | + probe=$(api GET "/repos/$OWNER/$REPO/actions/runs/$(jq -r .id <<<"$run")/jobs") | |
| 214 | + images=$(jq -r '.jobs[] | .runner.image // "no image reported"' <<<"$probe") | |
| 215 | + echo "probe jobs ran on: $(sort <<<"$images" | uniq -c | sed 's/^ *//' | paste -sd ','), started again $(jq '[.jobs[].placementTries // 0] | add // 0' <<<"$probe") time(s) in all" | |
| 214 | 216 | [ "$(grep -cxF "$expected" <<<"$images" || true)" = 3 ] && { echo "jobs start on $expected"; exit 0; } |
| 215 | 217 | if [ "$(date +%s)" -ge "$deadline" ]; then |
| 216 | 218 | echo "::error::15 minutes after the deploy, jobs still start on a runner image other than the deployed $expected" |
| @@ -406,7 +408,8 @@ | ||
| 406 | 408 | echo "$run" | jq '{id, status, conclusion}' |
| 407 | 409 | id=$(jq -r .id <<<"$run") |
| 408 | 410 | jobs=$(api "/repos/$OWNER/$REPO/actions/runs/$id/jobs") |
| 409 | - jq -r '.jobs[] | " \(.name): \(.conclusion)\(if .failure then " - " + (.failure.message // "") else "" end)\(try " (\((.completed_at | fromdateiso8601) - (.started_at | fromdateiso8601))s)" catch "")"' <<<"$jobs" | |
| 411 | + # placementTries: times a job went back in line (no machine, or a runner on a replaced image turned away). | |
| 412 | + jq -r '.jobs[] | " \(.name): \(.conclusion)\(if .failure then " - " + (.failure.message // "") else "" end)\(try " (\((.completed_at | fromdateiso8601) - (.started_at | fromdateiso8601))s)" catch "")\(if (.placementTries // 0) > 0 then ", started again \(.placementTries) time(s)" else "" end)"' <<<"$jobs" | |
| 410 | 413 | if [ "$(jq -r .conclusion <<<"$run")" != success ]; then |
| 411 | 414 | for job in $(jq -r '.jobs[] | select(.conclusion == "failure" or .conclusion == "timed_out") | .id' <<<"$jobs"); do |
| 412 | 415 | echo "::group::Last log lines of job $job" |
modifiedapps/diffinery/docs/actions/runners.md+1−1View file
| @@ -147,7 +147,7 @@ | ||
| 147 | 147 | |
| 148 | 148 | If the container stops unexpectedly (for example it runs out of memory), the job fails with `The runner stopped unexpectedly (exit code N).` A container that stops before its runner fetched the job is replaced instead, since no step ran. |
| 149 | 149 | |
| 150 | -The log's first lines name where the job ran, for example `Runner: image diffinery-runner:96dbbe31790f, in fra06, EEUR, container 6f1c…`. The jobs API returns the same as `runner` (`image`, `location`, `instance`), next to `runner_name`. `GET /meta` names the image the last deploy set as `runner_image`. While a deploy's rollout is updating instances, a new container can still start on the previous image. Diffinery turns that runner away before it runs anything and starts the job again on another container (after 30 s, then 1, 2 and 4 minutes). After four tries, the job runs on whatever image it got rather than wait for a rollout that has stalled. | |
| 150 | +The log's first lines name where the job ran, for example `Runner: image diffinery-runner:96dbbe31790f, in fra06, EEUR, container 6f1c…`. The jobs API returns the same as `runner` (`image`, `location`, `instance`), next to `runner_name`. `GET /meta` names the image the last deploy set as `runner_image`. While a deploy's rollout is updating instances, a new container can still start on the previous image. Diffinery turns that runner away before it runs anything and starts the job again on another container (after 30 s, then 1, 2 and 4 minutes). After four tries, the job runs on whatever image it got rather than wait for a rollout that has stalled. The jobs API's `placementTries` counts the times a job went back in line before a runner took it. | |
| 151 | 151 | |
| 152 | 152 | ### Windows and macOS |
| 153 | 153 |
modifiedapps/diffinery/src/repo/actions.ts+3−0View file
| @@ -231,6 +231,8 @@ | ||
| 231 | 231 | /** The runner that took the job: a self-hosted runner's name, or the hosted container's name. */ |
| 232 | 232 | runnerName: string | null; |
| 233 | 233 | runner: JobRunnerInfo | null; |
| 234 | + /** Times a hosted job went back in line before a runner took it (no machine, or a runner on a replaced image). */ | |
| 235 | + placementTries: number; | |
| 234 | 236 | }; |
| 235 | 237 | |
| 236 | 238 | const toRun = (r: RunRow): RunInfo => ({ |
| @@ -269,6 +271,7 @@ | ||
| 269 | 271 | summary: j.summary, |
| 270 | 272 | runnerName: j.container?.replace(/^self-hosted:/, "") ?? null, |
| 271 | 273 | runner: j.runner_info ? (JSON.parse(j.runner_info) as JobRunnerInfo) : null, |
| 274 | + placementTries: j.placement_tries ?? 0, | |
| 272 | 275 | }); |
| 273 | 276 | |
| 274 | 277 | /** |