From 757ae5cd377e97f84ff97d2a17c20347d8ae8a2d Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 11 Mar 2026 13:53:21 -0600 Subject: [PATCH 1/2] fix: protect FailingKubeClient.RecordedWaitOptions from concurrent access Add a sync.Mutex to guard the append to RecordedWaitOptions in GetWaiterWithOptions, fixing a data race detected by -race when concurrent goroutines (e.g. upgrade + rollback) both call GetWaiterWithOptions on the same FailingKubeClient instance. Fixes race failures in TestUpgradeRelease_Interrupted_RollbackOnFailure and TestInstallRelease_RollbackOnFailure_Interrupted. Signed-off-by: Terry Howe --- pkg/kube/fake/failing_kube_client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/kube/fake/failing_kube_client.go b/pkg/kube/fake/failing_kube_client.go index 0f7787f79..1289b35c6 100644 --- a/pkg/kube/fake/failing_kube_client.go +++ b/pkg/kube/fake/failing_kube_client.go @@ -19,6 +19,7 @@ package fake import ( "io" + "sync" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -49,6 +50,7 @@ type FailingKubeClient struct { WaitDuration time.Duration // RecordedWaitOptions stores the WaitOptions passed to GetWaiter for testing RecordedWaitOptions []kube.WaitOption + mu sync.Mutex } var _ kube.Interface = &FailingKubeClient{} @@ -160,7 +162,9 @@ func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) func (f *FailingKubeClient) GetWaiterWithOptions(ws kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { // Record the WaitOptions for testing + f.mu.Lock() f.RecordedWaitOptions = append(f.RecordedWaitOptions, opts...) + f.mu.Unlock() waiter, _ := f.PrintingKubeClient.GetWaiterWithOptions(ws, opts...) printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter) return &FailingKubeWaiter{ From c0119fb1ec0e53073e7f085ddce3a911696a7711 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sun, 26 Apr 2026 10:16:01 -0600 Subject: [PATCH 2/2] fix: extract appendRecordedWaitOptionsLocked helper with defer unlock Signed-off-by: Terry Howe --- pkg/kube/fake/failing_kube_client.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/kube/fake/failing_kube_client.go b/pkg/kube/fake/failing_kube_client.go index 1289b35c6..75d0c8de1 100644 --- a/pkg/kube/fake/failing_kube_client.go +++ b/pkg/kube/fake/failing_kube_client.go @@ -160,11 +160,14 @@ func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) return f.GetWaiterWithOptions(ws) } -func (f *FailingKubeClient) GetWaiterWithOptions(ws kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { - // Record the WaitOptions for testing +func (f *FailingKubeClient) appendRecordedWaitOptionsLocked(opts ...kube.WaitOption) { f.mu.Lock() + defer f.mu.Unlock() f.RecordedWaitOptions = append(f.RecordedWaitOptions, opts...) - f.mu.Unlock() +} + +func (f *FailingKubeClient) GetWaiterWithOptions(ws kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { + f.appendRecordedWaitOptionsLocked(opts...) waiter, _ := f.PrintingKubeClient.GetWaiterWithOptions(ws, opts...) printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter) return &FailingKubeWaiter{