Skip to content

test(storage): resolve timeout race condition in stalling client mock tests - #5060

Closed
kislaykishore wants to merge 2 commits into
masterfrom
kislayk/fix-stalling-client-race
Closed

kislaykishore wants to merge 2 commits into
masterfrom
kislayk/fix-stalling-client-race

Conversation

@kislaykishore

@kislaykishore kislaykishore commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes a race condition and non-deterministic behavior in stallingStorageControlClient timeout tests in internal/storage/control_client_wrapper_test.go.

Problem & Root Cause:
In stallingStorageControlClient, Go's select statement between <-time.After() and <-ctx.Done() caused flaky failures when both channels were ready simultaneously under heavy load or GC pauses. Because Go's select evaluates ready channels pseudo-randomly, the timer branch was sometimes chosen even though the context deadline had already expired, causing unintended fall-through to real unmocked control client methods. Additionally, zero/nil stall durations created unnecessary timer overhead.

Solution / Fix Mechanism:

  • Added a guard checking stallDuration != nil && *stallDuration > 0 before entering stall delay.
  • Added explicit post-select context cancellation checks (if err := ctx.Err(); err != nil { return nil, err }) so that even if time.After(*d) unblocks at the exact same instant as ctx.Done(), the subsequent ctx.Err() check guarantees that an expired context is prioritized and returned immediately.

Link to the issue in case of a bug fix.

b/553889914

Testing details

  1. Manual - Verified compilation and linting via make build.
  2. Unit tests - Executed with race detector: go test -v -race -run "TestControlClientWrapperTestSuite|TestControlClientGaxRetryWrapperTestSuite" ./internal/storage (PASS — 0 race warnings, 0 failures).
  3. Integration tests - N/A (Unit test mock fix).

Any backward incompatible change? If so, please explain.

N/A

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the stalling logic in stallingStorageControlClient by adding checks to ensure stall durations are greater than zero and verifying context errors after the select blocks. The reviewer recommended refactoring this duplicated logic into a single helper function to improve maintainability and prevent potential timer resource leaks by using time.NewTimer instead of time.After.

Comment on lines 44 to 112
func (s *stallingStorageControlClient) GetStorageLayout(ctx context.Context, req *controlpb.GetStorageLayoutRequest, opts ...gax.CallOption) (*controlpb.StorageLayout, error) {
if s.stallDurationForGetStorageLayout != nil {
if s.stallDurationForGetStorageLayout != nil && *s.stallDurationForGetStorageLayout > 0 {
select {
case <-time.After(*s.stallDurationForGetStorageLayout):
case <-ctx.Done():
return nil, ctx.Err()
}
if err := ctx.Err(); err != nil {
return nil, err
}
}
return s.wrapped.GetStorageLayout(ctx, req, opts...)
}

func (s *stallingStorageControlClient) DeleteFolder(ctx context.Context, req *controlpb.DeleteFolderRequest, opts ...gax.CallOption) error {
if s.stallDurationForFolderAPIs != nil {
if s.stallDurationForFolderAPIs != nil && *s.stallDurationForFolderAPIs > 0 {
select {
case <-time.After(*s.stallDurationForFolderAPIs):
case <-ctx.Done():
return ctx.Err()
}
if err := ctx.Err(); err != nil {
return err
}
}
return s.wrapped.DeleteFolder(ctx, req, opts...)
}

func (s *stallingStorageControlClient) GetFolder(ctx context.Context, req *controlpb.GetFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) {
if s.stallDurationForFolderAPIs != nil {
if s.stallDurationForFolderAPIs != nil && *s.stallDurationForFolderAPIs > 0 {
select {
case <-time.After(*s.stallDurationForFolderAPIs):
case <-ctx.Done():
return nil, ctx.Err()
}
if err := ctx.Err(); err != nil {
return nil, err
}
}
return s.wrapped.GetFolder(ctx, req, opts...)
}

func (s *stallingStorageControlClient) RenameFolder(ctx context.Context, req *controlpb.RenameFolderRequest, opts ...gax.CallOption) (*control.RenameFolderOperation, error) {
if s.stallDurationForFolderAPIs != nil {
if s.stallDurationForFolderAPIs != nil && *s.stallDurationForFolderAPIs > 0 {
select {
case <-time.After(*s.stallDurationForFolderAPIs):
case <-ctx.Done():
return nil, ctx.Err()
}
if err := ctx.Err(); err != nil {
return nil, err
}
}
return s.wrapped.RenameFolder(ctx, req, opts...)
}

func (s *stallingStorageControlClient) CreateFolder(ctx context.Context, req *controlpb.CreateFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) {
if s.stallDurationForFolderAPIs != nil {
if s.stallDurationForFolderAPIs != nil && *s.stallDurationForFolderAPIs > 0 {
select {
case <-time.After(*s.stallDurationForFolderAPIs):
case <-ctx.Done():
return nil, ctx.Err()
}
if err := ctx.Err(); err != nil {
return nil, err
}
}
return s.wrapped.CreateFolder(ctx, req, opts...)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The stalling logic is duplicated across five different methods (GetStorageLayout, DeleteFolder, GetFolder, RenameFolder, CreateFolder). Additionally, using time.After in a select block can leak the underlying timer resources if the context is cancelled before the timer fires.

We can refactor this repetitive logic into a single helper function stall that uses time.NewTimer and ensures the timer is stopped via defer timer.Stop(). This improves maintainability, readability, and resource efficiency.

func stall(ctx context.Context, duration *time.Duration) error {
	if duration == nil || *duration <= 0 {
		return nil
	}
	timer := time.NewTimer(*duration)
	defer timer.Stop()
	select {
	case <-timer.C:
	case <-ctx.Done():
		return ctx.Err()
	}
	return ctx.Err()
}

func (s *stallingStorageControlClient) GetStorageLayout(ctx context.Context, req *controlpb.GetStorageLayoutRequest, opts ...gax.CallOption) (*controlpb.StorageLayout, error) {
	if err := stall(ctx, s.stallDurationForGetStorageLayout); err != nil {
		return nil, err
	}
	return s.wrapped.GetStorageLayout(ctx, req, opts...)
}

func (s *stallingStorageControlClient) DeleteFolder(ctx context.Context, req *controlpb.DeleteFolderRequest, opts ...gax.CallOption) error {
	if err := stall(ctx, s.stallDurationForFolderAPIs); err != nil {
		return err
	}
	return s.wrapped.DeleteFolder(ctx, req, opts...)
}

func (s *stallingStorageControlClient) GetFolder(ctx context.Context, req *controlpb.GetFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) {
	if err := stall(ctx, s.stallDurationForFolderAPIs); err != nil {
		return nil, err
	}
	return s.wrapped.GetFolder(ctx, req, opts...)
}

func (s *stallingStorageControlClient) RenameFolder(ctx context.Context, req *controlpb.RenameFolderRequest, opts ...gax.CallOption) (*control.RenameFolderOperation, error) {
	if err := stall(ctx, s.stallDurationForFolderAPIs); err != nil {
		return nil, err
	}
	return s.wrapped.RenameFolder(ctx, req, opts...)
}

func (s *stallingStorageControlClient) CreateFolder(ctx context.Context, req *controlpb.CreateFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) {
	if err := stall(ctx, s.stallDurationForFolderAPIs); err != nil {
		return nil, err
	}
	return s.wrapped.CreateFolder(ctx, req, opts...)
}

@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.93%. Comparing base (fd8281d) to head (6d81ef6).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #5060   +/-   ##
=======================================
  Coverage   83.92%   83.93%           
=======================================
  Files         176      176           
  Lines       21563    21563           
=======================================
+ Hits        18096    18098    +2     
+ Misses       2766     2765    -1     
+ Partials      701      700    -1     
Flag Coverage Δ
unittests 83.93% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kislaykishore
kislaykishore marked this pull request as ready for review August 30, 2026 16:47
@kislaykishore
kislaykishore requested a review from a team as a code owner August 30, 2026 16:47
@kislaykishore
kislaykishore requested a review from geertj August 30, 2026 16:47
@github-actions github-actions Bot added the remind-reviewers Auto remind reviewers in attention set for review post 24hrs of inactivity on PR. label Aug 30, 2026
@kislaykishore
kislaykishore requested a review from meet2mky August 30, 2026 16:47
@kislaykishore
kislaykishore force-pushed the kislayk/fix-stalling-client-race branch from 97f84e4 to 6d81ef6 Compare August 30, 2026 16:50
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

14 similar comments
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

@github-actions

Copy link
Copy Markdown

Hi @geertj, @meet2mky, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over 24 hours. Please provide your input when you have a moment. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

remind-reviewers Auto remind reviewers in attention set for review post 24hrs of inactivity on PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant