diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2025-04-17 19:45:47 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2025-04-17 19:46:40 +0000 |
| commit | 2a20f901dbd7922a27b5625a8a442587c8c3df2c (patch) | |
| tree | 9e0cdcbf507597afd7e0e40bd017fb3de14e5d92 /pkg | |
| parent | 2a6ededbf54a9f8ac036ad0ebfc673934f93fde9 (diff) | |
pkg/manager: prevent deadlock on ReproLoop cancellation
When the context is cancelled, no one may be polling the pingQueue, yet
we write to it synchronously.
Wrap it in a select{} statement and add a test to ensure that the loop
reacts properly to the context cancellation.
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/manager/repro.go | 6 | ||||
| -rw-r--r-- | pkg/manager/repro_test.go | 22 |
2 files changed, 27 insertions, 1 deletions
diff --git a/pkg/manager/repro.go b/pkg/manager/repro.go index 1531f3d8d..c9eb2f9a9 100644 --- a/pkg/manager/repro.go +++ b/pkg/manager/repro.go @@ -230,7 +230,11 @@ func (r *ReproLoop) Loop(ctx context.Context) { r.mu.Unlock() r.parallel <- struct{}{} - r.pingQueue <- struct{}{} + // If the context is cancelled, no one is listening on pingQueue. + select { + case r.pingQueue <- struct{}{}: + default: + } }() } } diff --git a/pkg/manager/repro_test.go b/pkg/manager/repro_test.go index def436e99..d66bee8d0 100644 --- a/pkg/manager/repro_test.go +++ b/pkg/manager/repro_test.go @@ -123,6 +123,28 @@ func TestReproRWRace(t *testing.T) { mock.onVMShutdown(t, obj) } +func TestCancelRunningRepro(t *testing.T) { + mock := &reproMgrMock{ + run: make(chan runCallback), + } + obj := NewReproLoop(mock, 1, false) + ctx, done := context.WithCancel(context.Background()) + complete := make(chan struct{}) + go func() { + obj.Loop(ctx) + close(complete) + }() + + defer func() { + <-complete + }() + + obj.Enqueue(&Crash{Report: &report.Report{Title: "A"}}) + obj.Enqueue(&Crash{Report: &report.Report{Title: "B"}}) + <-mock.run + done() +} + type reproMgrMock struct { reserved atomic.Int64 run chan runCallback |
