mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Optimiseur : le ménage du verrou ne croise plus sa prise ni son rendu
TestTuneRecoverNeverReapsOwnLock échouait au hasard sous Linux (2 fois sur 5 déjà en v0.15.0, CI rouge). Le ménage (tuneReapStale) déplaçait un verrou vivant, le relisait puis le remettait en place ; si release() passait entre les deux, il ne trouvait plus le fichier et rendait l'inscription. Le verrou remis en place devenait orphelin, et le ménage suivant le prenait pour une optimisation interrompue : moteur relancé, preset « rétabli » à tort. Un mutex de processus (tuneFileMu) sérialise désormais les opérations sur le fichier — prise, réécriture, rendu, déplacement-relecture du ménage. L'arrêt d'un essai orphelin et le retour du preset restent hors du mutex. Vérifié sous Linux : 40 passages sans échec, suite complète verte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e7fb63b1dd
commit
853e59c085
1 file changed
+46
-11
@@ -146,6 +146,15 @@ func tuneActive() (tuneOwner, bool) {
|
|||||||
// tuneHeld : verrous pris par CE processus et pas encore rendus (tuneHeldKey).
|
// tuneHeld : verrous pris par CE processus et pas encore rendus (tuneHeldKey).
|
||||||
var tuneHeld sync.Map
|
var tuneHeld sync.Map
|
||||||
|
|
||||||
|
// tuneFileMu sérialise, DANS CE PROCESSUS, les opérations sur le fichier de
|
||||||
|
// verrou : prise, réécriture, rendu, et le déplacement-relecture du ménage
|
||||||
|
// (tuneReapStale). Sans lui, le ménage pouvait déplacer un verrou vivant
|
||||||
|
// pendant que release() le cherchait : release, ne le trouvant plus, rendait
|
||||||
|
// l'inscription ; le ménage le remettait ensuite en place, désormais orphelin,
|
||||||
|
// et le ménage suivant le prenait pour une optimisation interrompue (moteur
|
||||||
|
// relancé, preset « rétabli »). Vu en CI : TestTuneRecoverNeverReapsOwnLock.
|
||||||
|
var tuneFileMu sync.Mutex
|
||||||
|
|
||||||
func tuneHeldKey(o tuneOwner) string { return fmt.Sprintf("%s|%d|%d", o.Via, o.Since, o.Nonce) }
|
func tuneHeldKey(o tuneOwner) string { return fmt.Sprintf("%s|%d|%d", o.Via, o.Since, o.Nonce) }
|
||||||
|
|
||||||
// tuneOwnerAlive : le propriétaire d'un verrou vit-il encore ? Un verrou qui
|
// tuneOwnerAlive : le propriétaire d'un verrou vit-il encore ? Un verrou qui
|
||||||
@@ -201,6 +210,7 @@ func tuneLockAcquire(via string, mainActive bool) (*tuneLock, error) {
|
|||||||
key := tuneHeldKey(l.owner)
|
key := tuneHeldKey(l.owner)
|
||||||
tuneHeld.Store(key, true)
|
tuneHeld.Store(key, true)
|
||||||
for attempt := 0; attempt < 3; attempt++ {
|
for attempt := 0; attempt < 3; attempt++ {
|
||||||
|
tuneFileMu.Lock()
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644)
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
b, _ := json.Marshal(l.owner)
|
b, _ := json.Marshal(l.owner)
|
||||||
@@ -209,11 +219,14 @@ func tuneLockAcquire(via string, mainActive bool) (*tuneLock, error) {
|
|||||||
if werr != nil || cerr != nil {
|
if werr != nil || cerr != nil {
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
tuneHeld.Delete(key)
|
tuneHeld.Delete(key)
|
||||||
|
tuneFileMu.Unlock()
|
||||||
return nil, fmt.Errorf("verrou de l'optimiseur : %v", errors.Join(werr, cerr))
|
return nil, fmt.Errorf("verrou de l'optimiseur : %v", errors.Join(werr, cerr))
|
||||||
}
|
}
|
||||||
l.held = true
|
l.held = true
|
||||||
|
tuneFileMu.Unlock()
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
tuneFileMu.Unlock()
|
||||||
if !os.IsExist(err) {
|
if !os.IsExist(err) {
|
||||||
tuneHeld.Delete(key)
|
tuneHeld.Delete(key)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -232,6 +245,13 @@ func tuneLockAcquire(via string, mainActive bool) (*tuneLock, error) {
|
|||||||
// write réécrit le verrou d'un bloc (fichier voisin puis renommage) : un
|
// write réécrit le verrou d'un bloc (fichier voisin puis renommage) : un
|
||||||
// lecteur ne voit jamais un JSON à moitié écrit.
|
// lecteur ne voit jamais un JSON à moitié écrit.
|
||||||
func (l *tuneLock) write() error {
|
func (l *tuneLock) write() error {
|
||||||
|
tuneFileMu.Lock()
|
||||||
|
defer tuneFileMu.Unlock()
|
||||||
|
return l.writeLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeLocked : write, tuneFileMu déjà tenu par l'appelant.
|
||||||
|
func (l *tuneLock) writeLocked() error {
|
||||||
b, err := json.Marshal(l.owner)
|
b, err := json.Marshal(l.owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -291,6 +311,8 @@ func (l *tuneLock) setBackup(backup, id, name string) {
|
|||||||
func (l *tuneLock) release() {
|
func (l *tuneLock) release() {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
tuneFileMu.Lock()
|
||||||
|
defer tuneFileMu.Unlock()
|
||||||
if l.held {
|
if l.held {
|
||||||
// Un fichier absent un instant (renommé par un ménage qui le remet en
|
// Un fichier absent un instant (renommé par un ménage qui le remet en
|
||||||
// place, voir tuneReapStale) n'est pas un fichier rendu : on relit.
|
// place, voir tuneReapStale) n'est pas un fichier rendu : on relit.
|
||||||
@@ -305,7 +327,7 @@ func (l *tuneLock) release() {
|
|||||||
if ours {
|
if ours {
|
||||||
l.owner.Phase, l.owner.Trial, l.owner.MainWasActive = "rendu", nil, false
|
l.owner.Phase, l.owner.Trial, l.owner.MainWasActive = "rendu", nil, false
|
||||||
l.owner.Backup, l.owner.PresetID, l.owner.PresetName = "", "", ""
|
l.owner.Backup, l.owner.PresetID, l.owner.PresetName = "", "", ""
|
||||||
_ = l.write()
|
_ = l.writeLocked()
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
if err := os.Remove(l.path); err == nil || os.IsNotExist(err) {
|
if err := os.Remove(l.path); err == nil || os.IsNotExist(err) {
|
||||||
break
|
break
|
||||||
@@ -328,6 +350,24 @@ func (l *tuneLock) release() {
|
|||||||
// périmé). Le renommage d'abord : de deux processus qui trouvent le même verrou
|
// périmé). Le renommage d'abord : de deux processus qui trouvent le même verrou
|
||||||
// périmé, un seul l'emporte, et aucun ne retire un verrou neuf posé entre-temps.
|
// périmé, un seul l'emporte, et aucun ne retire un verrou neuf posé entre-temps.
|
||||||
func tuneReapStale() (mainWasActive bool) {
|
func tuneReapStale() (mainWasActive bool) {
|
||||||
|
o, ok := tuneReapClaim()
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if o.Trial != nil && tuneProcAlive(*o.Trial) {
|
||||||
|
fmt.Fprintf(os.Stderr, "[loki tune] essai orphelin (PID %d) d'une optimisation interrompue : arrêt\n", o.Trial.PID)
|
||||||
|
tuneKillTree(o.Trial.PID, nil)
|
||||||
|
}
|
||||||
|
tuneUndoApply(o)
|
||||||
|
return o.MainWasActive
|
||||||
|
}
|
||||||
|
|
||||||
|
// tuneReapClaim : la partie de tuneReapStale qui touche au fichier, sous
|
||||||
|
// tuneFileMu — lecture, déplacement, relecture, remise en place. Rend le
|
||||||
|
// verrou périmé effectivement retiré (ok=false : rien à écarter).
|
||||||
|
func tuneReapClaim() (tuneOwner, bool) {
|
||||||
|
tuneFileMu.Lock()
|
||||||
|
defer tuneFileMu.Unlock()
|
||||||
path := tuneLockPath()
|
path := tuneLockPath()
|
||||||
o, ok := readTuneOwner(path)
|
o, ok := readTuneOwner(path)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -338,14 +378,14 @@ func tuneReapStale() (mainWasActive bool) {
|
|||||||
if fi, err := os.Stat(path); err == nil && time.Since(fi.ModTime()) > tuneLockFreshness {
|
if fi, err := os.Stat(path); err == nil && time.Since(fi.ModTime()) > tuneLockFreshness {
|
||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
}
|
}
|
||||||
return false
|
return tuneOwner{}, false
|
||||||
}
|
}
|
||||||
if tuneOwnerAlive(o) {
|
if tuneOwnerAlive(o) {
|
||||||
return false
|
return tuneOwner{}, false
|
||||||
}
|
}
|
||||||
stale := fmt.Sprintf("%s.stale-%d-%d", path, os.Getpid(), time.Now().UnixNano())
|
stale := fmt.Sprintf("%s.stale-%d-%d", path, os.Getpid(), time.Now().UnixNano())
|
||||||
if os.Rename(path, stale) != nil {
|
if os.Rename(path, stale) != nil {
|
||||||
return false
|
return tuneOwner{}, false
|
||||||
}
|
}
|
||||||
defer os.Remove(stale)
|
defer os.Remove(stale)
|
||||||
// Entre la lecture et le renommage, le verrou lu a pu être rendu et un
|
// Entre la lecture et le renommage, le verrou lu a pu être rendu et un
|
||||||
@@ -358,14 +398,9 @@ func tuneReapStale() (mainWasActive bool) {
|
|||||||
if err := os.Link(stale, path); err != nil {
|
if err := os.Link(stale, path); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "[loki tune] verrou vivant déplacé par erreur, remise en place impossible : %v\n", err)
|
fmt.Fprintf(os.Stderr, "[loki tune] verrou vivant déplacé par erreur, remise en place impossible : %v\n", err)
|
||||||
}
|
}
|
||||||
return false
|
return tuneOwner{}, false
|
||||||
}
|
}
|
||||||
if o.Trial != nil && tuneProcAlive(*o.Trial) {
|
return o, true
|
||||||
fmt.Fprintf(os.Stderr, "[loki tune] essai orphelin (PID %d) d'une optimisation interrompue : arrêt\n", o.Trial.PID)
|
|
||||||
tuneKillTree(o.Trial.PID, nil)
|
|
||||||
}
|
|
||||||
tuneUndoApply(o)
|
|
||||||
return o.MainWasActive
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sameTuneLock : a et b désignent-ils la même prise de verrou (propriétaire et
|
// sameTuneLock : a et b désignent-ils la même prise de verrou (propriétaire et
|
||||||
|
|||||||
Reference in new issue
Block a user