Skip to content

馃悰 Fix goroutine leaks in cache Start() methods - #3565

Open
dongjiang1989 wants to merge 1 commit into
kubernetes-sigs:mainfrom
dongjiang1989:fix-leak
Open

馃悰 Fix goroutine leaks in cache Start() methods#3565
dongjiang1989 wants to merge 1 commit into
kubernetes-sigs:mainfrom
dongjiang1989:fix-leak

Conversation

@dongjiang1989

@dongjiang1989 dongjiang1989 commented Aug 3, 2026

Copy link
Copy Markdown
Member

What does this PR do?

This PR fixes goroutine leaks in multiNamespaceCache.Start() and delegatingByGVKCache.Start() methods in the pkg/cache package.

Why do we need it?

Problem

Both Start() methods used unbuffered error channels (errs := make(chan error)) to collect errors from goroutines. This caused goroutine leaks in two scenarios:

  1. Multiple caches return errors simultaneously: The select statement only receives the first error and returns, leaving other goroutines blocked forever on errs <- err with no receiver.

  2. Context cancelled while goroutines try to send errors: When ctx.Done() triggers first, the function returns immediately. Any subsequent error sends from goroutines will block forever.

Example

// Original problematic code
errs := make(chan error)  // unbuffered
for _, cache := range caches {
    go func() {
        if err := cache.Start(ctx); err != nil {
            errs <- err  // blocks forever if no receiver!
        }
    }()
}
select {
case err := <-errs:
    return err  // only receives ONE error, others leak
case <-ctx.Done():
    return nil  // returns immediately, senders leak
}

Fix

  • Use buffered channels sized to the number of caches
  • Use sync.WaitGroup to track all goroutines
  • Wait for all goroutines to complete before returning
// Fixed code
errs := make(chan error, len(caches))  // buffered
var wg sync.WaitGroup
for _, cache := range caches {
    wg.Go(func() {
        if err := cache.Start(ctx); err != nil {
            errs <- err  // never blocks
        }
    })
}
done := make(chan struct{})
go func() { wg.Wait(); close(done) }()
// ... wait for all goroutines to complete

The multiNamespaceCache.Start() and delegatingByGVKCache.Start() methods
used unbuffered error channels which could cause goroutine leaks when:
1. Multiple caches return errors simultaneously (only first error received)
2. Context is cancelled while goroutines try to send errors

Fix by using buffered channels sized to the number of caches, combined
with WaitGroup to ensure all goroutines complete before returning.

Added goleak-based unit tests to verify no goroutine leaks occur.

Signed-off-by: dongjiang <dongjiang1989@126.com>
@kubernetes-prow kubernetes-prow Bot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Aug 3, 2026
@kubernetes-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: dongjiang1989
Once this PR has been reviewed and has the lgtm label, please assign alvaroaleman for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

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

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant