-
Notifications
You must be signed in to change notification settings - Fork 424
Eliminate looped time.After timer leaks, propagate cancellation correctly, and enforce timeafterleak in CI
#39188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,13 +32,15 @@ func (c *AddInteractiveConfig) checkStatusAndOfferRun(ctx context.Context) error | |
| var workflowFound bool | ||
| for i := range 5 { | ||
| // Wait 2 seconds before each check (including the first) | ||
| timer := time.NewTimer(2 * time.Second) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] No regression test covers context cancellation in this polling loop — the most common real-world code path for the timer fix. 💡 Suggested test skeletonfunc TestCheckStatusAndOfferRun_ContextCancelled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel() // pre-cancel
cfg := &AddInteractiveConfig{Verbose: true}
err := cfg.checkStatusAndOfferRun(ctx)
if !errors.Is(err, context.Canceled) {
t.Errorf("expected context.Canceled, got %v", err)
}
}The current timer fix is correct, but without a test a future refactor could reintroduce |
||
| select { | ||
| case <-ctx.Done(): | ||
| timer.Stop() | ||
| if spinner != nil { | ||
| spinner.Stop() | ||
| } | ||
| return ctx.Err() | ||
| case <-time.After(2 * time.Second): | ||
| case <-timer.C: | ||
| // Continue with check | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -195,10 +195,12 @@ func StartDockerImageDownload(ctx context.Context, image string) bool { | |
| dockerImagesLog.Printf("Failed to download image %s (attempt %d/%d). Retrying in %ds...", image, attempt, maxAttempts, waitTime) | ||
|
|
||
| // Use context-aware sleep | ||
| timer := time.NewTimer(time.Duration(waitTime) * time.Second) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/improve-codebase-architecture] This is the third file in this PR that implements the same 💡 Suggested helper// contextSleep pauses for d or until ctx is cancelled.
// Returns ctx.Err() if the context fires first, nil otherwise.
func contextSleep(ctx context.Context, d time.Duration) error {
timer := time.NewTimer(d)
select {
case <-ctx.Done():
timer.Stop()
return ctx.Err()
case <-timer.C:
return nil
}
}All three call sites ( |
||
| select { | ||
| case <-time.After(time.Duration(waitTime) * time.Second): | ||
| case <-timer.C: | ||
| // Continue to next retry | ||
| case <-ctx.Done(): | ||
| timer.Stop() | ||
| // Context cancelled during sleep | ||
| dockerImagesLog.Printf("Download of image %s cancelled during retry wait: %v", image, ctx.Err()) | ||
| return | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,10 +72,12 @@ func waitForServerReady(ctx context.Context, port int, timeout time.Duration, ve | |
| } | ||
| return true | ||
| } | ||
| timer := time.NewTimer(mcpScriptsServerStartupDelay) | ||
| select { | ||
| case <-ctx.Done(): | ||
| timer.Stop() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/diagnose] 💡 Suggested fix: return error instead of boolChanging the function signature to case <-ctx.Done():
timer.Stop()
return ctx.Err() // propagates cancellation
case <-timer.C:
}
// ...
mcpInspectLog.Printf("Server did not become ready within timeout")
return fmt.Errorf("server did not become ready within timeout")With |
||
| return false | ||
| case <-time.After(mcpScriptsServerStartupDelay): | ||
| case <-timer.C: | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/diagnose] -test=false means the timeafterleak linter skips all test files. Test helpers, integration test fixtures, and fake clocks that use time.After in a loop will not be caught by this gate. Consider adding a separate lint step (or a comment in the CI YAML) noting this intentional scope limit so future contributors know test files are unprotected.