Skip to content

fix(ci,#15091): po-2024 runners supervisor — bump ExecStart 8→12 slots (P0 ai-01 famine runners) - #15313

Merged
myia-ai-01 merged 2 commits into
mainfrom
fix/15091-runners-12-slots
Sep 9, 2026
Merged

myia-ai-01 merged 2 commits into
mainfrom
fix/15091-runners-12-slots

Conversation

@jsboige

@jsboige jsboige commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Grain: LIGHT/guard — lane myia-po-2024:CoursIA — prev: DEEP/lean #15135

Cible

Mission P0 ai-01 (msg-20260909T011024-jgamgp, HIGH, deadline 08:30Z EPF) : augmenter la capacité des runners Linux auto-hébergés po-2024.

Cause du saturé

682 jobs queued / 10 in_progress. Le pool CoursIA-Linux self-hosted ne tient pas la charge. Côté po-2024, deux défauts cumulés :

  1. Superviseur MASKED depuis 2026-09-07 — coursia-runner.service et coursia-waiters.service pointent /etc/systemd/system/*.service sur /dev/null. 0 conteneur actif sur les 3 familles (linux-docker, linux-waiter, lean-docker).
  2. ExecStart nominal = 8 slots (dispatch capacité feat(runners,#14285): volume _work persistant par slot — checkout incrémental (fin du re-clone 3,54 GiB par job) #14288 2026-09-02). Sous le MAXI po-2024 nominal, mais marge CPU/RAM mesurée firsthand autorise plus.

Mesures firsthand (WSL /proc, 2026-09-09T05:07Z)

Donnée Valeur Source
nproc 16 nproc WSL
loadavg 0.08 (1/652 process) /proc/loadavg
MemTotal 23.47 GB /proc/meminfo
MemAvailable 22.43 GB /proc/meminfo
Docker socket /var/run/docker-ce.sock OK (séparé Docker Desktop, R3 docker-ce-trap respecté) ls -la
Image locale coursia-linux-runner:2.336.0 (6 days ago, 1.87 GB) — post-fix entrypoint.sh:58-72, testée OK docker history
Service coursia-runner active=inactive enabled=masked systemctl status
Service coursia-waiters active=inactive enabled=masked systemctl status

Fix appliqué

Action État
Rebuild image 2.337.0 Abandonné : l'image 2.336.0 locale inclut le fix entrypoint.sh:58-72 (testée exécution, entrypoint.sh retourne verdict attendu sur token absent) ; le rebuild 2.337.0 a stallé 3 min à 0 % CPU (probable pull réseau ubuntu:24.04 — sans blocker)
Amend ExecStart start 8 → start 12 sur scripts/ci/docker/linux-runner/persist/coursia-runner.service Commit 6735b36d7c sur branche fix/15091-runners-12-slots, push OK
Calcul CPU 12 × 3 = 36 vCPU sur 16 nproc = overcommit 2.25× ; acceptable vu loadavg 0.08 et 22.43 GB RAM libres. Borne haute alignée sur la règle ai-01 « +4 stables > +12 qui font swap » : on est à +4 (8→12), pas +12
systemctl unmask + enable --now UAC-gated (sudo password). Action user requise pour déployer côté machine. Le commit amend la référence, mais le symlink /dev/null sur la machine doit être levé manuellement

Reste à faire (action user)

  1. sudo systemctl unmask coursia-runner.service coursia-waiters.service
  2. sudo systemctl daemon-reload
  3. Restaurer /etc/systemd/system/coursia-runner.service depuis persist/coursia-runner.service (avec amend N=12 déjà dans le commit)
  4. sudo systemctl enable --now coursia-runner.service coursia-waiters.service
  5. Mesure après : docker ps --filter name=myia-po-2024-, gh api repos/jsboige/CoursIA/actions/runs?per_page=1, jobs queued avant/après

Note pour ai-01 (réponse au DM)

  • Préférence N=12 visée, mais si tu veux N=8 (règle « +4 stables »), un amend N=8 est triviale (un seul chiffre dans le même fichier).
  • L'image 2.336.0 reste la référence locale tant que 2.337.0 ne build pas. Le rebuild est non-bloquant pour l'enregistrement des runners (post-fix entrypoint.sh).
  • Le grain fix(lean,#14773): bump erc20_lean 4.32.1 -> v4.33.0 (rollout 4.33, Mathlib db584cd6) #15238 (log #print axioms brut erc20_lean) attendra la fin de la séquence P0 (cycles 05:37Z ou 06:07Z selon rapidité user).

— po-2024

Closes rien : cette PR ne résout pas l'issue #15091 (qui est sur le mécanisme restart-bounds, déjà résolu par #15094) ; elle ajuste la capacité po-2024 dans le cadre de la mission P0 ai-01.

…1 famine runners)

Cible : mission P0 ai-01 msg-20260909T011024-jgamgp, deadline 08:30Z EPF.
Cause : 682 jobs queued / 10 in_progress, pool linux auto-heberge sature.
Mesure firsthand po-2024 :
- nproc = 16, loadavg = 0.08 (1/652 process), marge CPU ~15 vCPU
- MemTotal = 23.47 GB, MemAvailable = 22.43 GB
- Image locale coursia-linux-runner:2.336.0 OK (post-fix entrypoint.sh:58-72)
- Service MASKED depuis 2026-09-07, 0 conteneur myia-po-2024-* actif

Calcul : 12 slots × 3 vCPU = 36 vCPU sur 16 nproc = overcommit 2.25x, acceptable
vu loadavg 0.08. Borne haute alignee sur la regle ai-01 '+4 stables > +12 qui font
swap' : on est a +4 (8 → 12), pas +12.

L'amend ne touche QUE l'argument ExecStart et le commentaire de section. Le
defaut wrapper (N=4 dans coursia-runner-start.sh) reste le filet ; start 12
ici est l'intention explicite pour cette machine, pas une dependance au defaut.

L'action d'unmask systemd + systemctl enable --now sur la machine est UAC-
gated (sudo password) et reste en attente action user (cf dashboard).

Co-Authored-By: Claude-Code <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #15313 (fix(ci,#15091): po-2024 runners supervisor — bump ExecStart 8→12 slots (P0 ai-01 famine runners)) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

@myia-ai-01
myia-ai-01 merged commit b86882f into main Sep 9, 2026
15 of 16 checks passed
jsboige added a commit that referenced this pull request Sep 9, 2026
…erministe

Six points du dispatch msg-20260909T174226-986o1r :
- start 12 preserve, UNE seule paire StartLimit 300/5 (jamais le bloc
  600), test d'unicite grep -c = 1 (deja en place, verifie)
- Test 29 work_cache_health de main (#15105), numerotation unique 1-29
- t0=$SECONDS restaure dans slot_loop/waiter_loop : la fusion l'avait
  perdu, lifetime=$(( SECONDS - t0 )) sous set -u tuait le superviseur
  au 1er cycle de toute boucle reelle (line 690: t0: unbound variable)
- stubs docker internes 11-28 : desormais 2 probes sha256 (la probe
  work_cache_health.sh recevait le sha de l'entrypoint => image jugee
  PERIMEE => tous les tests en boucle reelle echouaient)
- determinisme des tests 1-3/7/10/22 : stub sleep global, timeouts
  8-20 s, polls boucles ; le test 10 verifie $STATE_DIR/pids (ecrit par
  redirection directe) au lieu de l'echo stdout bufferise
- suite complete fraiche : 61 PASS / 0 FAIL (29 tests)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
myia-ai-01 pushed a commit that referenced this pull request Sep 10, 2026
…onible (#15166)

* fix(ci,#15095): superviseur runners borne -- backoff par duree de vie, garde daemon, unite fail-closed

Incident 07/09 (ai-01, 2 gels machine/90 min) : docker.service arret +
Restart=always laissaient slot_loop marteler docker run + fetch_token
4-8 fois/min (rc=0, cycles courts), ecriture ext4.vhdx 94-96 Mo/s, load 69.

- cycle_backoff : la DUREE DE VIE du conteneur (pas le rc) classe le
  cycle -- court => backoff exponentiel 15,30,60,...,900 s remis a zero
  apres un cycle sain (>= 60 s), partag par slot_loop et waiter_loop
  (remplace [ rc -ne 0 ] && sleep 15 || sleep 2)
- assert_docker_daemon : docker info sur le DOCKER_HOST epingle avant
  tout slot/fetch, dans cmd_start/cmd_waiters/cmd_lean
- persist/coursia-runner.service : Requires+BindsTo docker.service
  (inverse le choix Wants= de #14347) + StartLimitIntervalSec/Burst
  (fail-closed, retour du daemon = geste operateur)
- persist/coursia-runner-start.sh : meme garde au niveau ExecStart
- tests 11-15 par stubs PATH (sleep qui logue ses args, docker stub
  posant STOP_FILE) : 24 assertions PASS, evidence cadence 6 runs/900 s
  contre 60-120 avant (incident mesure)

Deploiement reel hors scope : coursia-runner.service reste DESACTIVE sur
ai-01 jusqu'a controle positif sur 1 slot.

Co-Authored-By: Claude-Code <noreply@anthropic.com>

* fix(ci,#15095): saturer cycle_backoff avant exponentiation + signal de travail reel

Review ai-01 (#15166, 3 points) :

1. OVERFLOW (reproduit au SHA, Git Bash 5.2.37, sleep stub) : 15*2^60
   deborde l'arithmetique signee 64 bits de bash -- cycle 61 negatif
   (-1152921504606846976), cycle 65+ nul, et le plafond n'atteint jamais
   ces valeurs (sleep 0 = retour du martellement 4-8/min de l'incident
   07/09). StartLimitBurst ne couvre pas cette boucle interne. Fix :
   saturation AVANT l'exponentiation -- l'exposant est borne au plus petit
   cap_exp tel que BASE*2^cap_exp > CAP (probe du p double, BASE*2^k ne
   pouvant egaliser une puissance de deux exacte, donc pas de debordement
   du probe lui-meme). Tout exposant >= cap_exp donnait deja le meme delai
   plafonne : la file observable est identique jusqu'au cap, puis PLATONNE.

2. CYCLE COURT UTILE vs BOUCLE VIDE : la duree seule penalisait tout
   conteneur < 60 s, meme utile. Ajout d'un signal de travail RECLAME et
   TERMINE -- le log du cycle est un cumul (rotate_log ne borne que par
   taille), donc le grep ne lit que la portion ecrite par CE cycle, a
   partir de l'offset d'octets capture avant le docker run. Un cycle court
   portant une execution de job ne nourrit pas l'exponentiel (compteur
   remis a zero, respiration courte) ; la protection contre les boucles
   sans travail est inchangee.

3. CYCLE LONG rc!=0 : n'est PLUS journalise "sainement" -- compteur de
   courts conserve, respiration intermediaire (BACKOFF_MIN_SEC), message
   "non qualifie sain". (Un cycle long rc=0 reste sain.)

Tests (test_supervise_guards.sh, +3) : T25 = 70 cycles courts consecutifs
=> plafond tient jusqu'au bout, aucune valeur negative ni nulle (le test
meme du debordement 61+ avec BASE=3 : 3*2^62 deborde au cycle ~63) ; T26 =
controle positif -- cycles courts AVEC travail => sleep 2 sans backoff puis
repart a 3,6,12 apres la fin du travail (compteur remis a zero) ; T27 =
cycle brule mais rc!=0 => non qualifie sain, compteur conserve (3,6,7,12,24,24).
Le test 14 preexistant presentait la meme sensibilite au chevauchement de
tick (HEALTHY=1, burn 2s) : durci a HEALTHY=2 / burn 3s, sequence attendue
INCHANGEE -- signale a po-2023:CoursIA-2 (proprietaire du claim #15095).

Validation : suite complete x3 = 58 PASS / 0 FAIL (auparavant 51 tests
/ 1 fail environne sur test 3 sentinel --force).

Co-Authored-By: Claude-Code <noreply@anthropic.com>

* fix(ci,#15166): detecter un cycle de travail sans SIGPIPE tail sur gros log de cycle

tail | grep -q sort des la premiere ligne et ferme le pipe pendant que tail
ecrit encore le corps du log (sous set -uo pipefail) -> SIGPIPE 141 -> pipeline
non nulle -> un cycle court AYANT traite un job est classe en boucle vide et
part en backoff (sleep 15 au lieu de 2). grep "Running job" >/dev/null lit tout
jusqu'a EOF : tail se termine proprement, rc=0 sur match.

Test 28 (controle de regression) : cycle court AVEC travail sur un log de cycle
>64 Ko (marqueur + 1 Mio de suffixe, >> tampon pipe ~64 Ko) -> sleep 2 sans
backoff. Verifie ROUGE (3,6,12) sur l'ancien grep -q, VERT (2,2,2) apres le fix.
Suite complete : 60 PASS / 0 FAIL.

Co-Authored-By: Claude-Code <noreply@anthropic.com>

* reconcile(fix/ci,#15166): reintegration post-merge #15313 + suite deterministe

Six points du dispatch msg-20260909T174226-986o1r :
- start 12 preserve, UNE seule paire StartLimit 300/5 (jamais le bloc
  600), test d'unicite grep -c = 1 (deja en place, verifie)
- Test 29 work_cache_health de main (#15105), numerotation unique 1-29
- t0=$SECONDS restaure dans slot_loop/waiter_loop : la fusion l'avait
  perdu, lifetime=$(( SECONDS - t0 )) sous set -u tuait le superviseur
  au 1er cycle de toute boucle reelle (line 690: t0: unbound variable)
- stubs docker internes 11-28 : desormais 2 probes sha256 (la probe
  work_cache_health.sh recevait le sha de l'entrypoint => image jugee
  PERIMEE => tous les tests en boucle reelle echouaient)
- determinisme des tests 1-3/7/10/22 : stub sleep global, timeouts
  8-20 s, polls boucles ; le test 10 verifie $STATE_DIR/pids (ecrit par
  redirection directe) au lieu de l'echo stdout bufferise
- suite complete fraiche : 61 PASS / 0 FAIL (29 tests)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(ci,#15166): retirer le stub sleep du test 22 -- borne reelle du rythme waiter

Le stub bin17/sleep (ajoute avec le stub sleep global) rendait la boucle
waiter instantanee : ARGV_LOG grossissait de milliers de lignes sous le
timeout 20 s, et `echo "$argv" | grep -q` prenait un SIGPIPE sous
pipefail en CI Linux (mesure : 60 PASS / 1 FAIL sur run 34399944998,
argv pourtant conforme). Le design d'origine borne le rythme via le
sleep 3 reel du stub docker + le backoff reel : le log reste a 1-2
lignes. Suite locale : 61 PASS / 0 FAIL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(ci,#15166): validation fail-closed des bornes backoff + doublement garde

Review v2 (pullrequestreview-5159868981) : les env overrides n'etaient pas
valides et le probe de cap_exp pouvait boucler sans fin -- BASE=2^62 /
CAP=2^63-1 doublait p vers 2^63 -> -2^63 -> 0 (boucle infinie), BASE=0
produisait un backoff nul sans fin.

- validate_backoff_env (cmd_start/waiters/lean, avant assert_docker_daemon)
  : decimal strictement positif, 18 chiffres max (< 2^60, marge large sous
  la borne signee pour tout produit garde), BASE <= CAP -- die nomme la
  variable fautive, jamais de boucle atteinte.
- cycle_backoff : d calcule ITERATIVEMENT, on ne double que si d <= CAP/2
  (chaque produit <= 2*floor(CAP/2) <= CAP, dans le domaine) ; doublons
  incomplets => plafond CAP. L'ancien probe cap_exp et le 2**exp sont
  retires.
- Tests 30 (BASE=0 / non numerique / BASE>CAP / CAP hors domaine : refus
  rc=1 avant toute boucle) et 31 (BASE=CAP=2^59, frontiere puissance de
  deux : 3 backoffs egaux a la borne, jamais negatif/nul, boucle TERMINEE).

Suite complete : 66 PASS / 0 FAIL (61 + 5 nouvelles assertions).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(runner,#15166): harness polls use REAL_SLEEP, not the stub

The global sleep stub at $TEST_DIR/bin/sleep sits first in the PATH of
tests 3 and 10, so their poll loops ("sleep 0.5" x24) never actually
waited: test 10 raced the pids file on the Linux CI runner (65/1).
Capture REAL_SLEEP="$(command -v sleep)" BEFORE the stub is created and
use it in the two harness polls; the stub stays untouched for the tested
program's backoffs. No timeout or iteration count was lengthened.

Local: 3 consecutive full-suite runs -> 66 PASS / 0 FAIL.

See #15166

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude-Code <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants