L6.2 : garde de generation contre les ecritures post-invalidation
Un fetch parti AVANT une invalidation (clearCache/invalidateCache, declenchees
par onSwitch a la bascule de profil, D8) terminait APRES elle et ecrivait quand
meme son resultat : le cache repartait peuple avec les donnees de l'ancien
tenant, timestamp neuf, valid: true. Defaut latent avant L6.1 ; deterministe
apres, puisque la promesse en vol survit desormais a l'invalidation.
Compteur de generation par service, incremente a chaque invalidation. Le fetch
capture la generation au depart et, au moment de publier, jette son resultat si
elle a bouge. Surtout : les fonctions de chargement n'ecrivent plus rien en
cache — la publication est un `commit` passe a singleFlight.run, appele
seulement si la generation n'a pas change. La garde devient structurelle, pas
conventionnelle : un chargement ne peut plus publier par inadvertance.
L'invalidation vide aussi la Map des promesses en vol. L'appelant deja en
attente recoit quand meme son resultat — il l'a demande avant la bascule ;
c'est sa mise en cache qui est refusee.
--- Verifications (LIMAGRAIN) ---
Rafale [search_workflows(EasyWMS), switch_wms_profile(EUROTRAFIC)] envoyee d'un
bloc, puis get_application_summary SEQUENTIEL apres les deux reponses.
AVANT la garde (HEAD = L6.1), 3 rejeux — le cache survit a la bascule :
===== RUN 1 =====
search_workflows : success=true count=50
switch_wms_profile : success=true
get_application_summary -> workflowCachesByApplication =
{"EasyWMS":{"cached":true,"count":3944,"timestamp":1787665677594,
"age":0,"valid":true}}
adElementsByApplication = {}
=> caches peuples : 1 (attendu 0)
===== RUN 2 ===== idem, count 3944, timestamp 1787665687744, valid true
===== RUN 3 ===== idem, count 3944, timestamp 1787665703945, valid true
APRES la garde, 3 rejeux — aucun cache peuple :
===== RUN 1 =====
search_workflows : success=true count=50
switch_wms_profile : success=true
get_application_summary -> workflowCachesByApplication = {}
adElementsByApplication = {}
=> caches peuples : 0 (attendu 0)
-- stderr : 1 resultat(s) jete(s) | 2 'Cache cleared'
===== RUN 2 ===== identique : caches peuples 0, 1 resultat jete
===== RUN 3 ===== identique : caches peuples 0, 1 resultat jete
Test direct et deterministe (invalidation declenchee pendant un fetch tenu
ouvert par une porte) :
[Workflow] Cache expired or empty for "TestApp", fetching from API...
--- invalidation PENDANT le fetch (clearCache) ---
[Workflow] Cache cleared
[Workflow] Fetched 1 workflows (total: 1)
[Workflow] Result for "workflows::TestApp" discarded, not cached:
cache invalidated during fetch (generation 0 -> 1)
l'appelant recoit bien son resultat : 1 workflow(s)
cache apres coup : {} (attendu {})
Non-regression L6.1, 3 rejeux de la rafale de 6 (CustomApp) :
== RUN 1 : 6 reponses a 44 | fetch=1 joins=5 caches=1
== RUN 2 : 6 reponses a 44 | fetch=1 joins=5 caches=1
== RUN 3 : 6 reponses a 44 | fetch=1 joins=5 caches=1
Chemin sequentiel nominal, inchange :
[search_workflows] success=true application=EasyWMS count=50 len=14220
[search_workflows] success=true application=EasyWMS count=50 len=14220
fetch=1 cached=1 joins=0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -95,13 +95,6 @@ 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.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
|
||||
|
||||
Reference in New Issue
Block a user