Passation lot 6 : chargements paresseux sous concurrence (D27 réservée)
Le point ouvert concurrence est scindé : la manifestation caches devient le lot 6 (L6.1 single-flight par clé, L6.2 garde de génération contre les écritures post-invalidation, L6.3 refus d'encaisser un vide anormal — response?.entities || [] transforme une réponse transitoirement anormale en cache vide empoisonné pour tout le TTL, cause probable du count 0 mesuré au lot 5). La manifestation bascule de profil reste en point ouvert, hors périmètre du lot. Preuves dans la passation : 6 chargements Metadata parallèles pour une rafale de 6 appels (lot 2), count 0 contre 44 en séquentiel (lot 5), avec la commande de rafale reproductible et l'exigence de rejouer chaque vérification de concurrence trois fois. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+43
-16
@@ -82,6 +82,43 @@ plus riche que `/AD/api/Application/GetAll`), `GET /healthcheck?tenantCode=` et
|
||||
|
||||
---
|
||||
|
||||
## Lot 6 — Chargements paresseux sous concurrence
|
||||
|
||||
Mesuré le 25/08/2026 (révisions des lots 2 et 5) : sans aucune bascule de
|
||||
profil, une rafale d'appels concurrents pendant un chargement paresseux
|
||||
produit des résultats faux en silence — `search_workflows("CST_",
|
||||
application: "CustomApp")` a répondu `count: 0` (contre 44 en séquentiel), et
|
||||
une rafale au lot 2 a déclenché **6 chargements Metadata complets en
|
||||
parallèle** (6 × `[EntityResolver] Cache expired or empty, fetching…`).
|
||||
Rejoués en séquentiel, les mêmes appels sont corrects.
|
||||
|
||||
Trois défauts structurels dans les services à cache (`workflow-service`,
|
||||
`ad-service`, `entity-resolver`) :
|
||||
|
||||
### L6.1 — Dédupliquer les fetchs en vol
|
||||
|
||||
Aucun service ne mémorise la promesse de chargement en cours : N appelants
|
||||
concurrents sur la même clé déclenchent N chaînes de fetch complètes.
|
||||
Single-flight par clé de cache (promesse partagée, libérée au règlement).
|
||||
|
||||
### L6.2 — Protéger l'invalidation contre les fetchs en vol
|
||||
|
||||
Un fetch parti avant `clearCache()`/`invalidateCache()` (bascule de profil,
|
||||
D8) écrit son résultat **après** l'invalidation : cache repeuplé avec les
|
||||
données de l'ancien tenant. Garde de génération (epoch) : un résultat issu
|
||||
d'une génération antérieure est jeté, pas écrit.
|
||||
|
||||
### L6.3 — Ne pas encaisser un vide anormal
|
||||
|
||||
`fetchAllWorkflows` fait `response?.entities || []` puis met en cache le
|
||||
résultat même vide, avec un timestamp valide : toute réponse transitoirement
|
||||
anormale (forme inattendue sous concurrence) devient un **cache vide
|
||||
empoisonné pour tout le TTL** — cause probable du `count: 0` mesuré. Une
|
||||
forme sans `entities` doit lever ; un `entities: []` réel reste cachable
|
||||
(des applications légitimement vides existent, ex. `SmartUI`).
|
||||
|
||||
---
|
||||
|
||||
## Écarté
|
||||
|
||||
| Proposition | Raison |
|
||||
@@ -105,23 +142,13 @@ plus riche que `/AD/api/Application/GetAll`), `GET /healthcheck?tenantCode=` et
|
||||
test.
|
||||
- **Bascule de profil concurrente aux appels en vol** (mesuré le 25/08/2026,
|
||||
révision du lot 2). Le serveur traite les `tools/call` **en concurrence** :
|
||||
en envoyant une rafale de requêtes dans une même session, les réponses
|
||||
reviennent dans le désordre, et un `switch_wms_profile` émis pendant que des
|
||||
requêtes sont en vol les fait partir sur le nouveau profil (observé : une
|
||||
requête destinée à EUROTRAFIC exécutée sur l'hôte `10.255.255.2` après la
|
||||
bascule suivante). Conséquence du singleton d'état global (D8). Sans gravité
|
||||
pour un usage conversationnel séquentiel, mais Claude peut émettre des appels
|
||||
d'outils **en parallèle** : à traiter si un cas réel de mélange de profils
|
||||
un `switch_wms_profile` émis pendant que des requêtes sont en vol les fait
|
||||
partir sur le nouveau profil (observé : une requête destinée à EUROTRAFIC
|
||||
exécutée sur l'hôte `10.255.255.2` après la bascule suivante). Conséquence du
|
||||
singleton d'état global (D8). À traiter si un cas réel de mélange de profils
|
||||
est observé (piste : sérialiser les `tools/call` ou figer le profil résolu au
|
||||
début de chaque appel). **Seconde manifestation mesurée (25/08/2026, révision
|
||||
du lot 5)** : sans aucune bascule de profil, une rafale d'appels concurrents
|
||||
pendant le chargement paresseux du cache workflow renvoie des résultats
|
||||
faux en silence — `search_workflows("CST_", application: "CustomApp")` a
|
||||
répondu `count: 0` (contre 44 en séquentiel), et `get_workflow_details` une
|
||||
réponse anormale — les chargements concurrents du même cache ne sont pas
|
||||
synchronisés (pas de déduplication de fetch en vol). Rejoués en séquentiel,
|
||||
les mêmes appels sont corrects. Piste supplémentaire : mémoriser la promesse
|
||||
de fetch en cours par clé de cache et la partager entre appelants.
|
||||
début de chaque appel). La manifestation « caches » de la même concurrence
|
||||
est traitée par le **lot 6** ci-dessus.
|
||||
- **`select_expression`** : les projections via le paramètre `Select` provoquent
|
||||
des erreurs de compilation côté serveur (D13). Irritant principal restant.
|
||||
- **Déploiement SSH sur la VM** : l'exécutable est validé, la configuration SSH
|
||||
|
||||
Reference in New Issue
Block a user