b975716759
CW2 is a ROCm process, so it registers on the KFD like any contender. Running it as its own systemd unit made mavgpud yield llama-server to it every few seconds. The gemma-4-12b arm was down for eight minutes on 2026-08-09 and routing had silently fallen back to the resident model. So mavgpud takes an `stt` block and runs the transcriber itself. `foreign` now excludes every child rather than one pid, which is the fix. Yielding is all or nothing, because a job that wants the card wants all of it. Idle unloading stays llama-server's alone: CW2 holds 1.6GB and unloading it would only send the next voice turn to the homesrv floor. Maven still talks to the transcriber directly on 8081. There is no proxy, because with no idle timer there is nothing for one to measure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ptwopxyo3Z2kwFckHkLvN
60 lines
1.7 KiB
Go
60 lines
1.7 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// fakeServer writes an executable standing in for llama-server: it ignores
|
|
// SIGTERM the way the real one effectively does — by dying messily rather than
|
|
// cleanly — and reports a non-zero status.
|
|
func fakeServer(t *testing.T, body string) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "fake-llama-server")
|
|
if err := os.WriteFile(path, []byte("#!/bin/sh\n"+body+"\n"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
// A deliberate stop is a yield, and the log has to say so.
|
|
//
|
|
// llama-server aborts inside its own static teardown on SIGTERM, so the exit
|
|
// status of a routine yield is identical to that of a real crash. Reading the
|
|
// mavgpud log, the two were indistinguishable (Vikunja #491).
|
|
func TestStopMarksTheExitAsAYield(t *testing.T) {
|
|
r := newRunner("fake", fakeServer(t, "while : ; do sleep 1 ; done"), nil, "")
|
|
if err := r.start(); err != nil {
|
|
t.Fatalf("start: %v", err)
|
|
}
|
|
r.mu.Lock()
|
|
if r.yielding {
|
|
t.Error("a freshly started server is already marked as yielding")
|
|
}
|
|
r.mu.Unlock()
|
|
|
|
r.stop(2 * time.Second)
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if !r.running() {
|
|
return
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
t.Fatal("the child outlived stop")
|
|
}
|
|
|
|
// Stopping when nothing is running must not arm the flag for the next child.
|
|
// The next exit after that would be a real crash logged as a yield.
|
|
func TestStopWithNoChildDoesNotArmTheFlag(t *testing.T) {
|
|
r := newRunner("fake", "/nonexistent", nil, "")
|
|
r.stop(10 * time.Millisecond)
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.yielding {
|
|
t.Error("stop armed the yield flag with no child running")
|
|
}
|
|
}
|