From a5db5be3863846e4b2f2376aaf0942051c6c05e0 Mon Sep 17 00:00:00 2001 From: Harsha Vardhan Date: Thu, 13 Aug 2026 19:45:46 +0530 Subject: [PATCH 1/2] events: fix goroutine leak when using --until with dropped contexts When fetching events with the --until flag on a system using the file events backend, libpod/events/logfile.go spawned an unmanaged goroutine running time.Sleep(time.Until(untilTime)). Because time.Sleep is not context-aware, if a client cancelled the request or dropped the connection, the goroutine remained sleeping in the background for the full until duration. Fix this by using time.NewTimer and selecting on ctx.Done() so the background goroutine exits immediately when the context is cancelled. Fixes: #29491 Signed-off-by: Harsha Vardhan --- libpod/events/logfile.go | 12 +++++++--- libpod/events/logfile_test.go | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index a39818a1bc..489860dd4e 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -122,9 +122,15 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { return err } go func() { - time.Sleep(time.Until(untilTime)) - if err := t.Stop(); err != nil { - logrus.Errorf("Stopping logger: %v", err) + timer := time.NewTimer(time.Until(untilTime)) + defer timer.Stop() + select { + case <-timer.C: + if err := t.Stop(); err != nil { + logrus.Errorf("Stopping logger: %v", err) + } + case <-ctx.Done(): + return } }() } diff --git a/libpod/events/logfile_test.go b/libpod/events/logfile_test.go index 4c06704d3e..796e093c38 100644 --- a/libpod/events/logfile_test.go +++ b/libpod/events/logfile_test.go @@ -3,9 +3,11 @@ package events import ( + "context" "os" "strings" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -172,3 +174,42 @@ func TestRenameLog(t *testing.T) { require.NoError(t, os.Remove(target.Name())) require.Equal(t, beforeRename, afterRename) } + +func TestReadUntilContextCancelled(t *testing.T) { + tmp, err := os.CreateTemp(t.TempDir(), "logfile-test-") + require.NoError(t, err) + defer tmp.Close() + + e := EventLogFile{ + options: EventerOptions{ + LogFilePath: tmp.Name(), + }, + } + + ctx, cancel := context.WithCancel(context.Background()) + eventChan := make(chan ReadResult) + options := ReadOptions{ + EventChannel: eventChan, + Until: time.Now().Add(24 * time.Hour).Format(time.RFC3339), + Stream: true, + } + + readErrChan := make(chan error, 1) + go func() { + readErrChan <- e.Read(ctx, options) + }() + + // Give Read time to initialize and spawn the until timer goroutine + time.Sleep(50 * time.Millisecond) + + // Cancel context (simulating client disconnect / Ctrl+C) + cancel() + + select { + case <-readErrChan: + // Read returned as expected on context cancellation + case <-time.After(2 * time.Second): + t.Fatal("EventLogFile.Read did not return after context cancellation") + } +} + From f7caec8ed6a8c12bb6e32f1bc95869d71a928bec Mon Sep 17 00:00:00 2001 From: Harsha Vardhan Date: Mon, 17 Aug 2026 01:51:26 +0530 Subject: [PATCH 2/2] test(events): remove redundant until cancellation test Remove the redundant context cancellation test as requested during review. Fixes: https://github.com/podman-container-tools/podman/issues/29491 Signed-off-by: Harsha Vardhan --- libpod/events/logfile_test.go | 41 ----------------------------------- 1 file changed, 41 deletions(-) diff --git a/libpod/events/logfile_test.go b/libpod/events/logfile_test.go index 796e093c38..4c06704d3e 100644 --- a/libpod/events/logfile_test.go +++ b/libpod/events/logfile_test.go @@ -3,11 +3,9 @@ package events import ( - "context" "os" "strings" "testing" - "time" "github.com/stretchr/testify/require" ) @@ -174,42 +172,3 @@ func TestRenameLog(t *testing.T) { require.NoError(t, os.Remove(target.Name())) require.Equal(t, beforeRename, afterRename) } - -func TestReadUntilContextCancelled(t *testing.T) { - tmp, err := os.CreateTemp(t.TempDir(), "logfile-test-") - require.NoError(t, err) - defer tmp.Close() - - e := EventLogFile{ - options: EventerOptions{ - LogFilePath: tmp.Name(), - }, - } - - ctx, cancel := context.WithCancel(context.Background()) - eventChan := make(chan ReadResult) - options := ReadOptions{ - EventChannel: eventChan, - Until: time.Now().Add(24 * time.Hour).Format(time.RFC3339), - Stream: true, - } - - readErrChan := make(chan error, 1) - go func() { - readErrChan <- e.Read(ctx, options) - }() - - // Give Read time to initialize and spawn the until timer goroutine - time.Sleep(50 * time.Millisecond) - - // Cancel context (simulating client disconnect / Ctrl+C) - cancel() - - select { - case <-readErrChan: - // Read returned as expected on context cancellation - case <-time.After(2 * time.Second): - t.Fatal("EventLogFile.Read did not return after context cancellation") - } -} -