-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
go/vt/vtgate/engine: use sync.Once to resolve nilness issue #14813
Conversation
Signed-off-by: Matt Layher <[email protected]>
Review ChecklistHello reviewers! 👋 Please follow this checklist when reviewing this Pull Request. General
Tests
Documentation
New flags
If a workflow is added or modified:
Backward compatibility
|
if routedKs == nil { | ||
routedKs = routedTable.Keyspace | ||
} | ||
once.Do(func() { routedKs = routedTable.Keyspace }) |
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.
@vitessio/query-serving Is this path (very) performance sensitive? Since afaik this is a more expensive way to express the same thing there and it might not be worth it if it's very performance critical?
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.
Good question. FWIW the implementation of once is quite small and I would expect it to be basically free (just an atomic load) following the first iteration that stores a result.
func (o *Once) Do(f func()) {
// Note: Here is an incorrect implementation of Do:
//
// if atomic.CompareAndSwapUint32(&o.done, 0, 1) {
// f()
// }
//
// Do guarantees that when it returns, f has finished.
// This implementation would not implement that guarantee:
// given two simultaneous calls, the winner of the cas would
// call f, and the second would return immediately, without
// waiting for the first's call to f to complete.
// This is why the slow path falls back to a mutex, and why
// the atomic.StoreUint32 must be delayed until after f returns.
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
Description
This is a tricky spot and the linter here doesn't seem to realize the outer loop may alter the value of
routedKs
in a prior iteration.Even if this is a false positive, the current way this logic is written seems a bit subtle and the intent was not obvious to me at first read.
Using a
sync.Once
here clearly conveys the intent that the first keyspace name detected is the one that should be used for the remainder of loop iterations.Related Issue(s)
Assuming #14812 is also merged, this fixes #14684.
Checklist
Deployment Notes
n/a