test(harvest): drop the wall-clock upper bound that made BackoffPositive flaky in real CI - #952
Conversation
…ive flaky in real CI The upper bound (elapsed <= 2*backoff + 2000ms) is not the contract of a backoff; "the delay was applied" is, and that is the lower bound, which stays hard. Measured 2026-07-26: the test took 3 s and failed the 2 240 ms bound on dependabot PRs #928 and #935 while 24 sibling npm runs passed. Those two diffs touch ONLY vendored npm lockfiles under DNNPlatform/Portals/1/2sxc/**, so no production code could be implicated -- 26 dependabot runs firing inside 5 minutes contend for the runner. The flakiness became visible only because #911 made the CI Test step real. Subtracting rather than widening, deliberately: a bigger number only moves the load level at which the bound lies, and it still would not be testing anything. The note in the file records the measurement so the bound is not "restored" later. Local: 643 total / 638 passed / 0 failed / 5 skipped (24 s). Refs #911, #928, #935 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Preuve empirique de la flakiness (post-merge)Le diagnostic posé dans le corps de cette PR était : « la borne wall-clock supérieure ment sous charge, ce n'est pas un défaut de Si le code était en cause, un re-run sur le même diff échoue de la même façon. Résultat :
Deux verts sur deux diffs identiques à ceux qui avaient rougi ⇒ la variable explicative n'est pas dans le diff, elle est dans la charge du runner (26 runs dependabot déclenchés en 5 minutes ; 24/26 verts au premier passage). La borne de 2 240 ms sur une opération de 240 ms mesurait la contention de l'agent CI, pas le backoff. Ce que ça change concrètement :
L'asymétrie avec |
Constat
Depuis #911, l'étape
Testde la CI exécute réellement les tests. Un test est apparu intermittent :HarvestManagerRetryAsyncTests.BackoffPositive_AppliesDelayBetweenAttempts.Mesure (2026-07-26) — sur les 26 PRs npm groupées ouvertes par dependabot en 5 minutes :
build (Debug)BackoffPositive_AppliesDelayBetweenAttempts[3 s],Total tests: 643 / Passed: 637 / Failed: 1 / Skipped: 5Les diffs de #928 et #935 ne touchent que des lockfiles npm vendorés sous
DNNPlatform/Portals/1/2sxc/**— aucun code .NET ne peut être en cause. La variable réelle est la contention du runner : 26 jobs déclenchés dans la même fenêtre de 5 minutes.L'assertion qui casse :
Le test a mis 3 s pour une opération dont le délai attendu est 240 ms.
Correctif : soustraire, pas élargir
La borne supérieure n'est pas le contrat d'un backoff — « le délai a bien été appliqué » l'est, et c'est la borne inférieure, qui reste dure :
Passer
2000à10000aurait été le réflexe de contrepoids : ça ne fait que déplacer le niveau de charge auquel la borne ment, et ça ne teste toujours rien. Le commentaire du fichier enregistre la mesure pour que la borne ne soit pas « restaurée » plus tard.Rien n'est affaibli : si le backoff cessait d'être appliqué, la borne inférieure échoue. C'est bien la seule chose que ce test peut observer de l'extérieur.
Traitement asymétrique de son voisin, délibéré
BackoffZero_DoesNotDelay(même fichier) garde sa borne supérieure< 500 ms— elle est son contrat : «TimeSpan.Zeron'introduit aucun délai » ne s'observe que par le haut. Elle est du même genre de fragilité en principe, elle n'a pas été observée en échec, et la retirer supprimerait la seule garde du chemin rapide. Noté plutôt que masqué.Vérification
643 total / 638 réussis / 0 échec / 5 ignorésen 24 s, local (dotnet test, Debug). Aucuncontinue-on-error, aucun test désactivé, aucunSkip.Ce que ça ne corrige pas
Le rouge de #949 est un autre défaut, sur d'autres tests, et il est réel — voir la correction postée sur #949 et l'issue #951 (Humanizer v3 renomme les IRI publiées).
Refs #911, #928, #935
🤖 Coordinator ai-01