-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
[1.12] Address selectorcache concurrent read/write #29167
[1.12] Address selectorcache concurrent read/write #29167
Conversation
/test-backport-1.12 |
/test-1.17-4.9 failed with an unrelated temporary flake:
https://jenkins.cilium.io/job/Cilium-PR-K8s-1.17-kernel-4.9/274/ |
CI passed and we've assessed the potential for deadlocks through manual code inspection. Marking as ready to review. |
Hi, Is it correct that this was introduced by d68efba? If so this means this is part of both |
Yes, I think that's correct. |
As in 52ace8e, there have been reports of a runtime crash with with following error: fatal error: concurrent map read and map write (This commit message is heavily inspired by the above commit in the hopes of achieving a similar level of clarity.) The path for this issue is printed also in the logs, with the following call stack: github.com/cilium/cilium/pkg/policy.(*SelectorCache).GetNetsLocked(...) github.com/cilium/cilium/pkg/policy.getNets(...) github.com/cilium/cilium/pkg/policy.identityIsSupersetOf(...) github.com/cilium/cilium/pkg/policy.(*mapState).DenyPreferredInsertWithChanges(...) Given the abovementioned commit has fixed the issue on main, there must be a different call stack present in 1.12. Indeed, as shown below, there's the flow involving `addNewRedirects` which isn't covered by locking in `DistillPolicy`. INCOMING CALLS GetNetsLocked github.com/cilium/cilium/pkg/policy • selectorcache.go getNets github.com/cilium/cilium/pkg/policy • mapstate.go identityIsSupersetOf github.com/cilium/cilium/pkg/policy • mapstate.go DenyPreferredInsertWithChanges github.com/cilium/cilium/pkg/policy • mapstate.go addNewRedirectsFromDesiredPolicy github.com/cilium/cilium/pkg/policy • bpf.go addNewRedirects github.com/cilium/cilium/pkg/endpoint • bpf.go <--- No SelectorCache lock DenyPreferredInsert github.com/cilium/cilium/pkg/policy • mapstate.go ToMapState github.com/cilium/cilium/pkg/policy • l4.go addNewRedirectsFromDesiredPolicy github.com/cilium/cilium/pkg/policy • bpf.go addNewRedirects github.com/cilium/cilium/pkg/endpoint • bpf.go <--- No SelectorCache lock computeDirectionL4PolicyMapEntries github.com/cilium/cilium/pkg/policy • resolve.go computeDesiredL4PolicyMapEntries github.com/cilium/cilium/pkg/policy • resolve.go DistillPolicy github.com/cilium/cilium/pkg/policy • resolve.go <--- SelectorCache.RLock() DetermineAllowLocalhostIngress github.com/cilium/cilium/pkg/policy • mapstate.go DistillPolicy github.com/cilium/cilium/pkg/policy • resolve.go <--- SelectorCache.RLock() computeDirectionL4PolicyMapEntries github.com/cilium/cilium/pkg/policy • resolve.go computeDesiredL4PolicyMapEntries github.com/cilium/cilium/pkg/policy • resolve.go DistillPolicy github.com/cilium/cilium/pkg/policy • resolve.go <--- SelectorCache.RLock() consumeMapChanges github.com/cilium/cilium/pkg/policy • mapstate.go ConsumeMapChanges github.com/cilium/cilium/pkg/policy • resolve.go <--- SelectorCache.Lock() Read the above tree as "GetNetsLocked() is called by getNets()", "getNets() is called by entryIdentityIsSupersetOf()", and so on. Siblings at the same level of indent represent alternate callers of the function that is one level of indentation less in the tree, ie addNewRedirectsFromDesiredPolicy and DenyPreferredInsert() both call DenyPreferredInsertWithChanges(). As annotated above, we see that calls through addNewRedirects() do not grab the SelectorCache lock. Given that consumeMapChanges() grabs the SelectorCache lock, we cannot introduce a new lock acquisition in any descendent function, otherwise it would introduce a deadlock in goroutines that follow that call path. This provides us the option to lock at some point from the sibling of consumeMapChanges() or higher in the call stack. As addNewRedirectsFromDesiredPolicy calls DenyPreferredInsertWithChanges both directly and indirectly (via ToMapState), it represents the lower bound of the call stack range where we can lock. The further up we traverse in the call stack, the greater the risk of a deadlock becomes, as more and more code now runs under the SelectorCache lock. Hence, we opt for locking at the lower bound, in addNewRedirectsFromDesiredPolicy. Unfortunately, this function lives in pkg endpoint, not policy, and we thus need to allow pkg-external access to the SelectorCache mutex, which is not great. The only silver lining is that this hack has an expiration time, as it is not present on main. Fixes: d68efba (endpoint: Remove GetLabelsLocked) Co-authored-by: Tobias Klauser <tobias@cilium.io> Signed-off-by: David Bimmler <david.bimmler@isovalent.com>
9a4a4de
to
df0ef4b
Compare
/test-backport-1.12 (no functional changes in push - added a comment, a |
@odinuge started seeing the issue too (see #29167 (comment)) and communicated out-of-band that they're testing the patch in their environment. We wanted to wait on whether this PR fixes the issue for them. @odinuge do you already have any news about this? |
Yes, we have not been able to reproduce the issue with this patch, thanks! We tested it for a while on a cluster that had a lot of issues without this on v1.12.15; and when applying this patch on top of that build, things worked as expected! We will let this soak for quite a while before hitting larger clusters, but so far it looks very promising. |
Thanks a lot @odinuge! Given your feedback I think this PR can be merged. |
See commit message.