Skip to content

fix(nb-tools,#16213): reprise EAGAIN partagee pour les deux derniers gardes - #16217

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/16213-eagain-shared-retry
Sep 15, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/16213-eagain-shared-retry

Conversation

@myia-ai-01

Copy link
Copy Markdown
Collaborator

Grain: MED/guard — lane myia-ai-01:CoursIA — prev: LIGHT/tooling #16036

Perimetre : 2 fichiers modifies, 2 fichiers ajoutes. Aucun notebook, aucune regle,
catalogue byte-identique a main.

Ce que livre cette PR

Les deux derniers gardes sans reprise sur la pression de fork sont cables sur une primitive
partagee, scripts/notebook_tools/fork_retry.py :

Garde Ligne Politique a l'epuisement
check_slot_reservation.py _git fail-closed -- l'OSError remonte
check_source_output_ratchet.py git fail-open -- None, comme avant

Trois gardes avaient deja recu cette reprise, ecrite trois fois : check_twin_parity (#16125),
check_kernel_suffix_canon et check_exec_ratchet (#16157). Ecrire une quatrieme et une
cinquieme copie aurait ete le geste facile. Le depot a deja tranche cette classe de probleme
avec naming_canon.py (#15503) -- plusieurs organes redefinissant la meme primitive, extraite
en lecture unique -- et c'est ce modele qui est suivi.

La cause, datee

f149f2fe93, mergee sur main le 2026-09-13T23:08:17Z : passage de Scripts Tests (CPU)
a pytest-xdist -n 4 --dist loadscope (#15833, sur #14598). Quatre workers executant des
gardes qui forkent git saturent la table de processus du runner ; le spawn suivant est refuse
avec BlockingIOError: [Errno 11]. Les echecs commencent le lendemain a 02:34.

-n 4 n'est pas en cause et reste : decision mesuree (92,5 % d'annulations sur les runners
po-2024 contre 4 % ailleurs ; 1,90x de gain mesure sur #15762). Les deux gestes sont
complementaires -- on ne retire pas le parallelisme, on rend les gardes capables de le
supporter. Le payoff est mesure : sur main a ec79792f60, Scripts Tests (CPU) rend
success.

Ce que la mutualisation ne doit PAS faire, et qui est teste

Extraire une primitive partagee par cinq gardes fait courir un risque precis : uniformiser en
passant ce qui etait deliberement different. Deux choses l'etaient.

La forme de l'appel. run_with_fork_retry transmet ses kwargs VERBATIM -- aucune
position sur le mode texte, l'encodage, cwd, env ou check. Un garde binaire et un garde
utf-8 passent par la meme porte sans qu'aucun n'impose sa forme a l'autre
(test_kwargs_transmis_verbatim).

La politique d'epuisement. Fail-closed d'un cote, fail-open de l'autre : deux tests
l'epinglent garde par garde. Le fail-open du ratchet est preexistant et deliberement
conserve -- son arbitrage appartient a #16164, pas a cette PR, qui ne doit pas le trancher au
passage.

Mesures

scripts/notebook_tools/tests/test_fork_retry.py            14 passed    rc=0
scripts/tests/test_check_slot_reservation.py   \
scripts/tests/test_check_source_output_ratchet.py          56 passed    rc=0

Controle de discriminance. Un correctif de reprise est, vu de l'exterieur, indiscernable
d'un avaleur d'erreurs. En retirant la reprise des deux gardes (retour a subprocess.run nu),
la suite rend rc=1 et fait tomber exactement les deux tests d'integration :

FAILED test_fork_retry.py::test_slot_reservation_retente_puis_reste_fail_closed
FAILED test_fork_retry.py::test_source_output_ratchet_retente_puis_reste_fail_open

Ils mesurent donc les gardes, pas eux-memes.

Controle positif du filtre. ENOENT, EACCES, ENOMEM et EINVAL remontent au premier
appel
, sans reprise ni attente. ENOMEM est le voisin dangereux : lui aussi vient de la
charge, et lui n'est pas transitoire.

Hors perimetre, volontairement

See #16213, #16111, #16164.

🤖 Generated with Claude Code

…gardes

Les deux gardes sans reprise sur la pression de fork -- check_slot_reservation
et check_source_output_ratchet -- sont cables sur une primitive partagee,
`fork_retry.run_with_fork_retry`, sur le modele de `naming_canon.py` (#15503)
plutot qu'en quatrieme et cinquieme copie de la meme boucle.

Chaque garde CONSERVE sa politique d'epuisement : fail-closed pour
check_slot_reservation (un instrument muet ne vote pas), fail-open pour
check_source_output_ratchet (politique preexistante, arbitrage #16164). Deux
tests l'epinglent garde par garde -- c'est exactement ce qu'un refactor
distrait harmoniserait.

Le filtre reste etroit : seuls EAGAIN et EWOULDBLOCK sont retentes, controle
positif inclus (ENOENT / EACCES / ENOMEM / EINVAL remontent au premier appel).

See #16213, #16111, #16164.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) label Sep 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2 light cap reached (advisory, non bloquant).
La lane myia-ai-01:CoursIA a deja consomme son budget LIGHT du jour (#16138 (merge a 2026-09-14T10:03:54Z)).
G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour,
toutes categories LIGHT confondues
(guard, doc, refs, ... partagent un seul budget) :
c'est un RATIO, pas un plafond plat. La decision de merge reste au coordinateur.

@github-actions github-actions Bot added variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory) variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) labels Sep 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-ai-01:CoursIA` voit ces signaux actifs sur les mergees du jour (UTC 2026-09-14) :

  • TIER-INFLATION : declared LIGHT << effective LIGHT-genre (tally : declared=3 genre=6 cap=3)
  • CAP-EXCEEDED-BY-GENRE : light_genre > cap partage G-VAR-2 (tally : declared=3 genre=6 cap=3)

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #16217 (fix(nb-tools,#16213): reprise EAGAIN partagee pour les deux derniers gardes) 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 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[ai-01 — validation finale exact-head f10bacb29ee38690af523240f8fef58fd0d058d7]

Lecture personnelle B.0 complète : body, trois commentaires, zéro thread inline, zéro review antérieure et diff intégral de 460 lignes. La primitive ne retente que EAGAIN/EWOULDBLOCK, transmet les kwargs sans altération et remonte la dernière OSError à l'épuisement. Les intégrations préservent délibérément les politiques distinctes : slot reservation fail-closed, source-output ratchet fail-open.

Validation indépendante sur ce head dans un worktree détaché : 70 passed en 15,12 s (test_fork_retry.py, test_check_slot_reservation.py, test_check_source_output_ratchet.py). Les checks latest-wins requis sont verts, dont Always-on, Scripts Tests et PR gate. G-VAR-3 recalculé sur la séquence mergée : guard_pass=true, exemption mécanique #14357 (MED, fichiers disjoints du prédécesseur réel #15748).

La collision faible avec #15813 est comprise : cette autre PR modifie le même helper pour distinguer les suppressions réelles via l'API GitHub, sans contredire le wrapper de reprise ; #15813 est déjà DIRTY et devra intégrer ce head/main après fusion plutôt que perdre la résilience EAGAIN.

Verdict ai-01 : READY pour fusion squash après re-grounding instant-T. GitHub refuse l'état APPROVED car le compte propriétaire de la PR et le compte reviewer sont identiques ; cette review COMMENTED consigne donc la validation sans contourner cette protection.

@myia-ai-01
myia-ai-01 merged commit 8f6076b into main Sep 15, 2026
19 of 20 checks passed
jsboige added a commit that referenced this pull request Sep 15, 2026
…rk_retry

#16217 a livre la primitive partagee (run_with_fork_retry, modele
naming_canon) et laisse check_exec_ratchet sur sa copie inline de #16157 --
cet arbitrage etait explicitement reserve a #16164. La boucle locale
(_EAGAIN_ERRNOS / _EAGAIN_ATTEMPTS / _EAGAIN_BACKOFF / _est_eagain) est
supprimee : git() appelle run_with_fork_retry et traduit l'OSError remontee
en InstrumentUnavailable.

Le filtre etroit (EAGAIN/EWOULDBLOCK seuls retentes, toute autre OSError
remonte au premier appel) vit desormais dans la primitive et reste couvert
par test_fork_retry.py ; les deux tests de retry du ratchet qui la
dupliquaient sont retires, la couverture propre au garde (traduction en
InstrumentUnavailable, absence de retry sur OSError non transitoire) est
conservee.

Tests : 57 passed (ratchet + fork_retry + exec_sequence).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Sep 16, 2026
…gardes (#16217)


Les deux gardes sans reprise sur la pression de fork -- check_slot_reservation
et check_source_output_ratchet -- sont cables sur une primitive partagee,
`fork_retry.run_with_fork_retry`, sur le modele de `naming_canon.py` (#15503)
plutot qu'en quatrieme et cinquieme copie de la meme boucle.

Chaque garde CONSERVE sa politique d'epuisement : fail-closed pour
check_slot_reservation (un instrument muet ne vote pas), fail-open pour
check_source_output_ratchet (politique preexistante, arbitrage #16164). Deux
tests l'epinglent garde par garde -- c'est exactement ce qu'un refactor
distrait harmoniserait.

Le filtre reste etroit : seuls EAGAIN et EWOULDBLOCK sont retentes, controle
positif inclus (ENOENT / EACCES / ENOMEM / EINVAL remontent au premier appel).

See #16213, #16111, #16164.

Co-authored-by: jsboige <jsboige@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
myia-ai-01 added a commit that referenced this pull request Sep 17, 2026
…onible » à l'épuisement EAGAIN, fin du faux vert (#16255)

* fix(nb-tools,#16164): ratchet — exit 2 instrument indisponible, plus de faux vert

git() leve InstrumentUnavailable quand le spawn echoue (EAGAIN a l'epuisement
des 3 tentatives, ou OSError non transitoire) ; returncode != 0 reste None
(git a repondu). Le CLI sort en 2 avec un message nommant l'echec de spawn,
distinct de 1 (regression) et de 0 (mesure faite) — plus jamais
"changed notebooks : 0" sur un arbre non lu.

Chemin masque repare : verdict_at_base() lisait ABSENT (donc "ajoute par la
PR") sur un spawn failure de git show ; il monte desormais.

Decision arbitree (#16164 critere 4) : fail-closed sur instrument
indisponible, convergence avec check_kernel_suffix_canon.py. Appelants
recenses : seul notebook-exec-sequence-ratchet.yml:88 (run nu -> exit 2
rougit l'etape, voulu), aucun importateur Python hors tests.

Tests : 2 pins #16157 revises (arbitrage reserve a cette issue) + spawn
failure != ABSENT + CLI exit 2 in-process. Ratchet 21 passed ; suite
notebook_tools 5772 passed.

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

* refactor(nb-tools,#16164): ratchet — migrer la reprise EAGAIN vers fork_retry

#16217 a livre la primitive partagee (run_with_fork_retry, modele
naming_canon) et laisse check_exec_ratchet sur sa copie inline de #16157 --
cet arbitrage etait explicitement reserve a #16164. La boucle locale
(_EAGAIN_ERRNOS / _EAGAIN_ATTEMPTS / _EAGAIN_BACKOFF / _est_eagain) est
supprimee : git() appelle run_with_fork_retry et traduit l'OSError remontee
en InstrumentUnavailable.

Le filtre etroit (EAGAIN/EWOULDBLOCK seuls retentes, toute autre OSError
remonte au premier appel) vit desormais dans la primitive et reste couvert
par test_fork_retry.py ; les deux tests de retry du ratchet qui la
dupliquaient sont retires, la couverture propre au garde (traduction en
InstrumentUnavailable, absence de retry sur OSError non transitoire) est
conservee.

Tests : 57 passed (ratchet + fork_retry + exec_sequence).

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

---------

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

Labels

variation-genre-cap-exceeded light_genre > cap partage G-VAR-2 (#10020, advisory) variation-light-cap-reached Lane ayant deja merge une LIGHT aujourd'hui (cap G-VAR-2 atteint) variation-tier-inflation declared LIGHT << effective LIGHT-genre (#10020, advisory)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants