mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -280,6 +279,8 @@ func (m *Manager) startQEMU(instance *VM) error {
|
||||
exitChan := make(chan int, 1)
|
||||
startupConfirmed := make(chan bool, 1)
|
||||
|
||||
qemuLogPath := filepath.Join(utils.RuntimeDir(), "qemu.log")
|
||||
|
||||
go func() {
|
||||
cmd, err := instance.Config.Execute()
|
||||
if err != nil {
|
||||
@@ -288,45 +289,39 @@ func (m *Manager) startQEMU(instance *VM) error {
|
||||
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 {
|
||||
slog.Error("Failed to pipe STDOUT VM", "err", err)
|
||||
processChan <- 0
|
||||
return
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
slog.Error("Failed to pipe STDERR VM", "err", err)
|
||||
slog.Error("Failed to open QEMU log file", "path", qemuLogPath, "err", err)
|
||||
processChan <- 0
|
||||
return
|
||||
}
|
||||
cmd.Stdout = logFile
|
||||
cmd.Stderr = logFile
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
slog.Error("Failed to start VM", "err", err)
|
||||
_ = logFile.Close()
|
||||
processChan <- 0
|
||||
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 {
|
||||
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
|
||||
|
||||
waitErr := cmd.Wait()
|
||||
|
||||
Reference in New Issue
Block a user