From 6fb68c22c5be98fa93fe4a48886a93086555567c Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Wed, 22 Apr 2026 11:00:15 +0200 Subject: [PATCH] fix: correctly wait for journalctl processes to not leave zombies when streaming machine logs (#325) * fix: call cmd.Wait() Even though the context is cancelled we still need to call cmd.Wait() after a cmd.Start() to clean up the child (reap) process. Not doing so results in a zombie journalctl. I have manually tested this, as I'm still not sure how to e2e test for this in a simple manner. Before: ``` root@uncloud2:~# ps aux|grep jou root 313 0.0 1.2 42272 23352 ? S root 15754 0.0 0.0 0 0 pts/0 Z+ 07:02 0:00 [journalctl] ``` After: ``` root 313 0.0 1.2 42272 23592 ? S --------- Signed-off-by: Miek Gieben --- internal/journal/journal.go | 10 +++++----- internal/journal/logs.go | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/journal/journal.go b/internal/journal/journal.go index b9b9f917..7a622d3f 100644 --- a/internal/journal/journal.go +++ b/internal/journal/journal.go @@ -31,9 +31,9 @@ const journalctl = "journalctl" var commandContext = exec.CommandContext // allow override for test -func logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (io.ReadCloser, error) { +func logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (io.ReadCloser, func() error, error) { if !ValidUnit(unit) { - return nil, fmt.Errorf("journal logs: invalid unit: %s", unit) + return nil, nil, fmt.Errorf("journal logs: invalid unit: %s", unit) } args := []string{"-u", unit, "--no-hostname"} args = append(args, "-n") @@ -61,14 +61,14 @@ func logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (io.Rea cmd := commandContext(ctx, journalctl, args...) p, err := cmd.StdoutPipe() if err != nil { - return nil, err + return nil, nil, err } if err := cmd.Start(); err != nil { - return nil, err + return nil, nil, err } - return p, nil + return p, cmd.Wait, nil } // follow synchronously follows the io.Reader, writing each new journal entry to channel. diff --git a/internal/journal/logs.go b/internal/journal/logs.go index dfdf8618..2977eff0 100644 --- a/internal/journal/logs.go +++ b/internal/journal/logs.go @@ -16,7 +16,7 @@ func Logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (<-chan return nil, fmt.Errorf("journal logs: invalid unit: %s", unit) } - reader, err := logs(ctx, unit, opts) + reader, wait, err := logs(ctx, unit, opts) if err != nil { return nil, err } @@ -26,6 +26,7 @@ func Logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (<-chan go func() { defer close(outCh) follow(ctx, reader, outCh) + wait() }() return outCh, nil