Optimiseur : la reprise toutes les 30 s ne prend plus un verrou vivant pour périmé

Depuis que le processus web refait la reprise des optimisations
interrompues toutes les 30 s, elle tourne pendant que ce même processus
prend et rend ses propres verrous (bouton « Optimiser… », application d'un
résultat). Trois fenêtres lui faisaient écarter un verrou bien vivant : le
fichier créé avant l'inscription du verrou comme nôtre, l'inscription
retirée avant le fichier (sous Windows, un fichier qu'un autre lit ne se
supprime pas), et le renommage d'un verrou neuf pris entre la lecture et le
ménage. En phase « application », le preset tout juste vérifié était alors
défait, et le moteur relancé.

- Inscription avant la création du fichier, retrait après lui ; un
  identifiant de prise (nonce) distingue deux verrous du même processus dans
  la même seconde.
- Au moment de le rendre, le verrou est réécrit neutre (rien à défaire,
  rien à relancer, aucun essai), puis retiré avec quelques essais.
- Le ménage relit ce qu'il vient de déplacer : un autre verrou, ou un verrou
  vivant, est remis en place par un lien (jamais par-dessus un verrou neuf).
- La relance d'après reprise passe par serviceAction : une optimisation
  neuve qui a pris le verrou entre-temps garde le moteur arrêté.
- Test : 60 prises et rendus en phase « application » sous une reprise en
  boucle, aucune relance ; échoue sur l'ancien code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 15:24:20 +02:00
1 parent c24472b91f
commit 5b834d54b2
2 files changed
+117 -9

No files matched your search

+43
View File
@@ -12,6 +12,7 @@ import (
"reflect"
"strconv"
"strings"
"sync/atomic"
"testing"
"time"
)
@@ -957,3 +958,45 @@ func TestTuneForeignEngine(t *testing.T) {
}
}
}
// La reprise du processus web tourne toutes les 30 s, pendant que ce même
// processus prend et rend ses propres verrous (bouton « Optimiser… »,
// application d'un résultat). Elle ne doit jamais tenir l'un d'eux pour
// périmé : entre la création du fichier et l'inscription, ou entre
// l'inscription retirée et le fichier retiré, elle aurait défait un preset
// tout juste vérifié (phase « application ») et relancé le moteur.
func TestTuneRecoverNeverReapsOwnLock(t *testing.T) {
testHome(t)
prevAlive, prevStart := tuneProcAlive, tuneRecoverStart
tuneProcAlive = func(p tuneProc) bool { return p.PID == os.Getpid() }
var starts atomic.Int32
tuneRecoverStart = func() { starts.Add(1) }
t.Cleanup(func() { tuneProcAlive, tuneRecoverStart = prevAlive, prevStart })
stop := make(chan struct{})
reaped := make(chan struct{})
go func() {
defer close(reaped)
for {
select {
case <-stop:
return
default:
tuneRecoverStale()
}
}
}()
for i := 0; i < 60; i++ {
l, err := tuneLockAcquire("apply", true)
if err != nil {
t.Fatalf("tour %d : %v", i, err)
}
l.setPhase("application")
l.release()
}
close(stop)
<-reaped
if n := starts.Load(); n != 0 {
t.Errorf("verrou vivant de ce processus écarté %d fois", n)
}
}