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
fix goroutine leak in the DeleteCollection #105606
Conversation
44144f4
to
2256c83
Compare
/release-note-edit
|
from what I can see, this should only be possible in unexpected panic cases... do you see panics logged? |
I only found the timeout filter related panic log. I think the goroutine leak here will not trigger panic. Please correct me if i am wrong |
/assign @CatherineF-dev @caesarxuchao |
@fedebongio: GitHub didn't allow me to assign the following users: CatherineF-dev. Note that only kubernetes members, repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
/ping @liggitt |
yeah, it looks like passing deleteoptions with a precondition (like a uid precondition) that causes the delete to return an error will exit workers early this looks reasonable, but needs to be exercised via a test |
staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go
Outdated
Show resolved
Hide resolved
staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go
Outdated
Show resolved
Hide resolved
2256c83
to
81861d6
Compare
case toProcess <- i: | ||
case <-workersExited: | ||
klog.V(4).InfoS("workers already exited, and there are some items waiting to be processed", "finished", i, "total", len(items)) | ||
return |
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.
or just break instead of return?
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.
I think the results of these two operations here are the same. ~ What do you think?
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.
I maybe was overthinking it, with this return
instead of break
you don't close(toProcess)
,
... but if you return
before distributing all items , that means that all workers already finished, so no need to close toProcess
/lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: sxllwx, wojtek-t The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
@@ -1126,14 +1126,22 @@ func (e *Store) DeleteCollection(ctx context.Context, deleteValidation rest.Vali | |||
} | |||
wg := sync.WaitGroup{} | |||
toProcess := make(chan int, 2*workersNumber) | |||
errs := make(chan error, workersNumber+1) | |||
errs := make(chan error, workersNumber) |
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.
I think I understand this +1 now, this is the number of workers goroutines + the distributor goroutine, all of them can send errors to this channel ... interestingly, we only report the first one ... should we drain the channel, aggregate and report all errors?
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.
Very interesting... 😄 ...
I don't think it is necessary to aggregate all errors here. If a operation fails, it should report directly. In order to ensure the fastest response. But I think drain the err chan is necessary here, logging can help debug.
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.
we need to add the +1 back... there are workersNumber + 1 things that can send errors to the errs channel... if they all send an error, the channel will block, wg.Wait() won't return, and we'll hang
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.
We were discussing it here: #105606 (comment)
The only way the dispatcher function can emit an error is when it crash.
But I can't imagine any reason why it may crash.
Thoughts?
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.
I haven't dug in to figure out detailed reasons it might crash. if we have panic handler code and workersNumber+1 things that could send errors, just size the channel buffer to match
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.
I opened #105872 for this.
Though - I think that the HandleCrash here is more for "being on the safe side" reason - I don't know how I could make it crash even with any conspiration theory (which is also why I don't see how we can even test that).
case err := <-errs: | ||
return nil, err |
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.
/lgtm cancel |
Heh... - I didn't make the cancelling on time. @aojea - #105606 (comment) is a valid comment |
yeah, me neither, I'm fine with it, |
hmm @sxllwx this test pass with and without this patch 🤔 |
Yeah - it's somewhat expected. Because the changes in the code are actually two things:
This test wouldn't pass only if we had a PR with the first one, but without the second. |
Without this patch, the new UT TestStoreDeleteCollectionWorkDistributorExited cannot pass. |
it does, I run the test only without the patch and passes |
Humm...Ignore my answer. Although the unit test can pass without this patch, there is still a leak of goroutine. (this is due to the concurrency mechanism of the previous version.) |
I think the code can be simplified as follows.
|
Whatever wojtek or Jordan say :) |
I think this code is simpler and faster. But the buffer size of the errs channel needs to be adjusted to len(items). |
I'm against the above - this potentially may start deletion of all items at once, which may overload etcd. |
@wojtek-t The above is essentially the same as the existed. The goroutine can be executed only on the premise of obtaining a token, thereby ensuring that at most workersNumber requests can be executed each time. |
OK sorry - I misread it. Yes - that looks fine |
…!1009) 1.20: 移植105606&105872,修复DeleteCollection导致的goroutine泄露问题 kubernetes#105606 kubernetes#105872
What type of PR is this?
/kind bug
What this PR does / why we need it:
In our k8s cluster, 3 kube-apiservers are running and consume a lot of memory resources. Through analysis of Heap and Goroutine Profile, we can find that a lot of memory is used by DeleteCollection and has not been released. After analysis, I think there is a goroutine leak in DeleteCollection.
Which issue(s) this PR fixes:
Fixes #
Special notes for your reviewer:
All workers exit, which will cause the permanent block of the task producer to be in the sent code block.
https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go#L1131-L1139
I think that in the process of requesting the
DeleteCollection
interface to kube-apiserver, if a Leader switch or other error occurs in etcd, this bug will be triggered.Does this PR introduce a user-facing change?
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: