mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Optimiseur : un « loki tune » tué par kill -9 ne laisse plus le moteur arrêté
Le verrou périmé n'était repris qu'au démarrage du processus web. Un « loki tune » en ligne de commande tué sans ses defers (kill -9, terminal perdu) laissait donc le vrai moteur arrêté — et peut-être l'essai en VRAM — jusqu'au prochain redémarrage de l'interface. - Le processus web refait la reprise toutes les 30 s, avec la même logique qu'au démarrage : essai orphelin arrêté (identité complète vérifiée), application interrompue défaite, moteur relancé seulement s'il tournait avant, ni sur un preset externe ni s'il est déjà reparti. - Coût : un stat par tic tant qu'aucun verrou n'existe ; pendant une optimisation vivante, la lecture du verrou et de l'identité de son propriétaire. Une seule goroutine, endormie sur le tic. - Test : relance au tic qui suit la mort du propriétaire, une fois ; rien sans verrou, rien pendant une optimisation vivante, rien pour un moteur qui ne tournait pas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f0fef91773
commit
a325e98dad
4 files changed
+104
-10
No files matched your search
@@ -679,8 +679,10 @@ Ajoutées par ce fork :
|
|||||||
claire ; les tâches planifiées attendent. Un verrou dont le propriétaire est
|
claire ; les tâches planifiées attendent. Un verrou dont le propriétaire est
|
||||||
mort (Loki tué en plein essai) est écarté avant tout démarrage du moteur :
|
mort (Loki tué en plein essai) est écarté avant tout démarrage du moteur :
|
||||||
l'essai orphelin n'est arrêté que si PID, instant de démarrage ET binaire
|
l'essai orphelin n'est arrêté que si PID, instant de démarrage ET binaire
|
||||||
concordent (`taskkill /T` sous Windows), et le moteur est relancé au
|
concordent (`taskkill /T` sous Windows), et le moteur est relancé s'il
|
||||||
démarrage de l'interface s'il tournait avant. **Refus d'entrée** : preset
|
tournait avant — au démarrage de l'interface, puis toutes les 30 s par le
|
||||||
|
processus web (un `stat` tant qu'aucun verrou n'existe) : un `loki tune` tué
|
||||||
|
par `kill -9` ne laisse plus le moteur arrêté. **Refus d'entrée** : preset
|
||||||
externe, aucun preset actif, génération, tâche, bench ou job `bash_bg` en
|
externe, aucun preset actif, génération, tâche, bench ou job `bash_bg` en
|
||||||
cours, moteur occupé (`/slots`) — en ligne de commande, seul ce dernier
|
cours, moteur occupé (`/slots`) — en ligne de commande, seul ce dernier
|
||||||
contrôle voit le processus web : préférer le bouton. **Essais** (descente étape par étape depuis
|
contrôle voit le processus web : préférer le bouton. **Essais** (descente étape par étape depuis
|
||||||
|
|||||||
@@ -332,15 +332,25 @@ func tuneUndoApply(o tuneOwner) {
|
|||||||
fmt.Fprintf(os.Stderr, "[loki tune] application interrompue avant sa vérification : ancienne version du preset rétablie\n")
|
fmt.Fprintf(os.Stderr, "[loki tune] application interrompue avant sa vérification : ancienne version du preset rétablie\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// tuneRecoverAtBoot : au démarrage du processus web, une optimisation
|
// tuneRecoverStale : une optimisation interrompue (Loki redémarré en plein
|
||||||
// interrompue (Loki tué en plein essai) a laissé le vrai moteur arrêté et peut-
|
// essai, « loki tune » tué par kill -9) a laissé le vrai moteur arrêté et peut-
|
||||||
// être un essai en VRAM. On arrête l'essai, puis on relance le moteur s'il
|
// être un essai en VRAM. On arrête l'essai, puis on relance le moteur s'il
|
||||||
// tournait avant — ce que l'optimisation aurait fait en finissant.
|
// tournait avant — ce que l'optimisation aurait fait en finissant. Sans
|
||||||
func tuneRecoverAtBoot() {
|
// verrou, un simple stat : rien d'autre n'est lu.
|
||||||
|
func tuneRecoverStale() {
|
||||||
if _, err := os.Stat(tuneLockPath()); err != nil {
|
if _, err := os.Stat(tuneLockPath()); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !tuneReapStale() || externalActive() || serviceIsActive() {
|
if tuneReapStale() {
|
||||||
|
tuneRecoverStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tuneRecoverStart relance le moteur après une optimisation interrompue, sauf
|
||||||
|
// preset externe, moteur déjà reparti ou configuration incomplète. Variable
|
||||||
|
// pour les tests.
|
||||||
|
var tuneRecoverStart = func() {
|
||||||
|
if externalActive() || serviceIsActive() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := preflightEngine(); err != nil {
|
if err := preflightEngine(); err != nil {
|
||||||
@@ -349,3 +359,19 @@ func tuneRecoverAtBoot() {
|
|||||||
fmt.Println(dim("[loki tune] optimisation interrompue : relance du moteur"))
|
fmt.Println(dim("[loki tune] optimisation interrompue : relance du moteur"))
|
||||||
_ = serviceActionOS("start")
|
_ = serviceActionOS("start")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tuneRecoverEvery : la période de la reprise dans le processus web.
|
||||||
|
const tuneRecoverEvery = 30 * time.Second
|
||||||
|
|
||||||
|
// tuneRecoverWatch : au démarrage du processus web, puis à chaque tic. Le
|
||||||
|
// démarrage seul ne suffisait pas : un « loki tune » en ligne de commande tué
|
||||||
|
// sans ses defers (kill -9, terminal perdu) laissait le moteur arrêté jusqu'au
|
||||||
|
// prochain redémarrage de l'interface. Le tic ne coûte qu'un stat tant
|
||||||
|
// qu'aucun verrou n'existe ; pendant une optimisation vivante, une lecture du
|
||||||
|
// verrou et de l'identité de son propriétaire.
|
||||||
|
func tuneRecoverWatch(tick <-chan time.Time) {
|
||||||
|
tuneRecoverStale()
|
||||||
|
for range tick {
|
||||||
|
tuneRecoverStale()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -832,3 +832,68 @@ func TestTuneUndoInterruptedApply(t *testing.T) {
|
|||||||
t.Error("configuration modifiée hors phase d'application")
|
t.Error("configuration modifiée hors phase d'application")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reprise dans le processus web : un « loki tune » tué sans ses defers ne
|
||||||
|
// laisse plus le moteur arrêté jusqu'au prochain démarrage de l'interface. Le
|
||||||
|
// tic suivant écarte le verrou périmé et relance le moteur — une fois, et
|
||||||
|
// seulement s'il tournait avant. Sans verrou, ni lecture ni relance.
|
||||||
|
func TestTuneRecoverWatch(t *testing.T) {
|
||||||
|
testHome(t)
|
||||||
|
alive := true
|
||||||
|
prevAlive, prevStart := tuneProcAlive, tuneRecoverStart
|
||||||
|
tuneProcAlive = func(p tuneProc) bool { return alive && p.PID == os.Getpid() }
|
||||||
|
starts := 0
|
||||||
|
tuneRecoverStart = func() { starts++ }
|
||||||
|
t.Cleanup(func() { tuneProcAlive, tuneRecoverStart = prevAlive, prevStart })
|
||||||
|
|
||||||
|
tick := make(chan time.Time)
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() { tuneRecoverWatch(tick); close(done) }()
|
||||||
|
// Un envoi est reçu quand le tic précédent a fini : deux de suite, et le
|
||||||
|
// premier est traité en entier.
|
||||||
|
step := func() { tick <- time.Now() }
|
||||||
|
step()
|
||||||
|
step()
|
||||||
|
if starts != 0 {
|
||||||
|
t.Fatal("relance sans verrou")
|
||||||
|
}
|
||||||
|
l, err := tuneLockAcquire("cli", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
step()
|
||||||
|
step()
|
||||||
|
if starts != 0 {
|
||||||
|
t.Fatal("relance pendant une optimisation vivante")
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(tuneLockPath()); err != nil {
|
||||||
|
t.Fatal("verrou vivant retiré")
|
||||||
|
}
|
||||||
|
alive = false // kill -9 : le verrou reste, son propriétaire est mort
|
||||||
|
step()
|
||||||
|
step()
|
||||||
|
if starts != 1 {
|
||||||
|
t.Fatalf("relances : %d, attendu 1", starts)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(tuneLockPath()); !os.IsNotExist(err) {
|
||||||
|
t.Error("verrou périmé non retiré")
|
||||||
|
}
|
||||||
|
l.release()
|
||||||
|
|
||||||
|
// Le moteur ne tournait pas avant : verrou retiré, pas de relance.
|
||||||
|
alive = true
|
||||||
|
if _, err := tuneLockAcquire("cli", false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
alive = false
|
||||||
|
step()
|
||||||
|
step()
|
||||||
|
close(tick)
|
||||||
|
<-done
|
||||||
|
if starts != 1 {
|
||||||
|
t.Errorf("relance d'un moteur qui ne tournait pas : %d", starts)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(tuneLockPath()); !os.IsNotExist(err) {
|
||||||
|
t.Error("verrou périmé non retiré")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -139,9 +139,10 @@ func newWebMux() *http.ServeMux {
|
|||||||
// Jeton de tentative du décodage spéculatif automatique : effacé dès que le
|
// Jeton de tentative du décodage spéculatif automatique : effacé dès que le
|
||||||
// moteur répond, sans dépendre d'une page ouverte (voir backend_serve_spec.go).
|
// moteur répond, sans dépendre d'une page ouverte (voir backend_serve_spec.go).
|
||||||
startSpecAttemptWatch()
|
startSpecAttemptWatch()
|
||||||
// Optimisation interrompue (Loki tué en plein essai) : essai orphelin arrêté,
|
// Optimisation interrompue (Loki ou « loki tune » tué en plein essai) : essai
|
||||||
// moteur relancé s'il tournait avant (backend_tune_lock.go).
|
// orphelin arrêté, moteur relancé s'il tournait avant (backend_tune_lock.go)
|
||||||
go tuneRecoverAtBoot()
|
// — au démarrage, puis toutes les 30 s (un stat sans verrou).
|
||||||
|
go tuneRecoverWatch(time.NewTicker(tuneRecoverEvery).C)
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
// Pages publiques : le HTML et le JS ne contiennent aucun secret. Toute la
|
// Pages publiques : le HTML et le JS ne contiennent aucun secret. Toute la
|
||||||
// donnée et toutes les actions passent par /api/* qui, lui, exige la clé.
|
// donnée et toutes les actions passent par /api/* qui, lui, exige la clé.
|
||||||
|
|||||||
Reference in new issue
Block a user