From cf73a4d6b418ee27429f606a6e3b07306d681d5a Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Mon, 24 Aug 2026 20:44:59 +0000 Subject: [PATCH] Reap the log aggregator's tail processes tailFile started tail -F and never waited on it. The wrapper runs as pid 1 in both the container and the unikernel, so every exited tail stayed a zombie for the life of the instance. A scan that ends on a read error rather than EOF leaves tail alive, so kill it before waiting instead of parking the goroutine forever. --- server/cmd/wrapper/supervisord.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/cmd/wrapper/supervisord.go b/server/cmd/wrapper/supervisord.go index 4672db47..1d2ad256 100644 --- a/server/cmd/wrapper/supervisord.go +++ b/server/cmd/wrapper/supervisord.go @@ -97,6 +97,17 @@ func tailFile(path string) { for scanner.Scan() { fmt.Printf("[%s] %s\n", label, scanner.Text()) } + // A clean scan ends when tail closes its stdout, i.e. when it has exited. + // A scan that ends on an error (a log line past the 1MB cap, say) leaves + // tail running, so kill it rather than block here forever. + // + // Either way we have to collect it: the wrapper is pid 1 in both the + // container and the unikernel, so a tail nobody waits on stays a zombie + // for the life of the instance. + if scanner.Err() != nil { + _ = cmd.Process.Kill() + } + _ = cmd.Wait() } func runStream(label, name string, args ...string) error {