mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-20 19:25:52 +00:00
fix(vm): QEMU stdout/stderr to shared log file (SIGPIPE survives daemon restart)
QEMU stdout/stderr were piped to daemon-side scanner goroutines. When the daemon exits (e.g. `systemctl restart spinifex-daemon`), the pipe read ends close; QEMU's next write triggers SIGPIPE and the guest dies — defeating DDIL Scenario B's "daemon restart preserves running instances" guarantee. Open `<runtimeDir>/qemu.log` and assign it to cmd.Stdout/cmd.Stderr instead. The FD is dup'd into QEMU at fork, so closing it on the daemon side after Start() leaves QEMU writing into the file indefinitely. POSIX O_APPEND makes concurrent QEMU writes interleave atomically per line, so a single shared log is safe and avoids per-instance clutter in RuntimeDir. Trade-off: QEMU lines no longer land in journalctl -u spinifex-daemon. Operators inspect <runtimeDir>/qemu.log directly (path logged at instance start under the qemu_log field). mulga-siv-58 (DDIL Scenario B iteration 4)
This commit is contained in:
+19
-24
@@ -1,7 +1,6 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -280,6 +279,8 @@ func (m *Manager) startQEMU(instance *VM) error {
|
|||||||
exitChan := make(chan int, 1)
|
exitChan := make(chan int, 1)
|
||||||
startupConfirmed := make(chan bool, 1)
|
startupConfirmed := make(chan bool, 1)
|
||||||
|
|
||||||
|
qemuLogPath := filepath.Join(utils.RuntimeDir(), "qemu.log")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
cmd, err := instance.Config.Execute()
|
cmd, err := instance.Config.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -288,45 +289,39 @@ func (m *Manager) startQEMU(instance *VM) error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
// QEMU stdout/stderr go to a single shared file rather than pipes
|
||||||
|
// back to daemon-side goroutines. If the daemon exits (e.g.
|
||||||
|
// `systemctl restart spinifex-daemon`), a pipe's read end closes
|
||||||
|
// and QEMU's next write triggers SIGPIPE, killing the guest —
|
||||||
|
// defeating DDIL Scenario B's "daemon restart preserves running
|
||||||
|
// instances" guarantee. With a file, the FD is dup'd into QEMU at
|
||||||
|
// fork and survives daemon exit. POSIX O_APPEND makes concurrent
|
||||||
|
// short writes from multiple QEMUs interleave atomically per
|
||||||
|
// line, so a single shared log is safe and avoids per-instance
|
||||||
|
// clutter in RuntimeDir.
|
||||||
|
logFile, err := os.OpenFile(qemuLogPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to pipe STDOUT VM", "err", err)
|
slog.Error("Failed to open QEMU log file", "path", qemuLogPath, "err", err)
|
||||||
processChan <- 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("Failed to pipe STDERR VM", "err", err)
|
|
||||||
processChan <- 0
|
processChan <- 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
cmd.Stdout = logFile
|
||||||
|
cmd.Stderr = logFile
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
slog.Error("Failed to start VM", "err", err)
|
slog.Error("Failed to start VM", "err", err)
|
||||||
|
_ = logFile.Close()
|
||||||
processChan <- 0
|
processChan <- 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
_ = logFile.Close()
|
||||||
|
|
||||||
slog.Info("VM started successfully", "pid", cmd.Process.Pid)
|
slog.Info("VM started successfully", "pid", cmd.Process.Pid, "qemu_log", qemuLogPath)
|
||||||
|
|
||||||
if err := utils.SetOOMScore(cmd.Process.Pid, 500); err != nil {
|
if err := utils.SetOOMScore(cmd.Process.Pid, 500); err != nil {
|
||||||
slog.Warn("Failed to set QEMU OOM score", "pid", cmd.Process.Pid, "err", err)
|
slog.Warn("Failed to set QEMU OOM score", "pid", cmd.Process.Pid, "err", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
|
||||||
scanner := bufio.NewScanner(stdout)
|
|
||||||
for scanner.Scan() {
|
|
||||||
slog.Info("[qemu]", "line", scanner.Text())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
scanner := bufio.NewScanner(stderr)
|
|
||||||
slog.Info("QEMU stderr reader started")
|
|
||||||
for scanner.Scan() {
|
|
||||||
slog.Error("[qemu-stderr]", "line", scanner.Text())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
processChan <- cmd.Process.Pid
|
processChan <- cmd.Process.Pid
|
||||||
|
|
||||||
waitErr := cmd.Wait()
|
waitErr := cmd.Wait()
|
||||||
|
|||||||
Reference in New Issue
Block a user