-
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
Bug Fix: Prepared Statements Bind Variables Nil Error #5441
Bug Fix: Prepared Statements Bind Variables Nil Error #5441
Conversation
… Also added a recover to catch panics that occur inside Logf due to FormatBindVariables call potentially panicking. Signed-off-by: Peter Farr <Peter@PrismaPhonic.com>
3b38e60
to
e83a02c
Compare
Signed-off-by: Peter Farr <Peter@PrismaPhonic.com>
if prepare.BindVars != nil { | ||
for k := range prepare.BindVars { | ||
prepare.BindVars[k] = nil | ||
} | ||
} |
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.
Though I agree that this is a cleaner and safer way of resetting PrepareData, I'm concerned that the fix might mask a deeper problem. Is it possible that there is a mismatch between ParamsCount and BindVars? Maybe we are being told to expect a certain number of bind variables (ParamsCount) but receiving a fewer number.
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.
ok, so should we mask and report errors or something? or would rather have things immediately fail?
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.
@deepthi That's a good point. Maybe the problem is that we are reusing stmtID
values for different statements?
@PrismaPhonic Were you able to reproduce the problem with the reduced test case?
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.
Also, although this is cleaner and safer than setting everything to nil, there is a performance cost to that. It was probably written this way to avoid allocating a new map on every query. This "fix" will put more load on the GC, which is something we shouldn't do in the hot path unless there's a very good reason.
The assumption written into this optimization seems to be that for a given stmtID
, the set of bind var names will never change. Does anyone know of a valid situation in which that assumption is violated? If there is no such situation, then @deepthi is right that there is another bug here that we shouldn't cover up.
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.
@deepthi will as far as I know we set the number of parameters when we check the :v
prefix and send it back to the client. Therefore, if there was a mismatch I think the client might catch that.
@enisoc I dont know of a situation that will change the query and number of parameters for a given sstmtID
.
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.
OK this finally makes sense. The clue about vtg1
in #5441 (comment) was the key to finding the root cause.
The vtg1
entry in the map is getting added by the normalizer after we pass the map to VTGate.Execute()
. The way to reproduce this is to prepare a query that also has some raw values, such as the 'blah'
in select foo from bar where name in (?,'blah')
. The normalizer will replace the 'blah'
with :vtg1
and add vtg1
to the bindvars map with value 'blah'
.
After executing, we set all bindvar map values to nil
without deleting them, which would have been fine if the normalizer had not mutated our map in-place to add vtg1
. Instead, the next time we call VTGate.Execute()
, we pass a bindvars map with vtg1
already present (and set to nil
). The normalizer sees this and thinks you have a pre-existing bindvar called vtg1
so when it needs to generate a bindvar name for 'blah'
, it increments to vtg2
to avoid a collision. Now vtg1
remains set in the map with value nil
.
The fix for that is what this PR is already doing: make a new map every time. The only suggestion I have then is to add a comment explaining why this is necessary:
// Allocate a new bindvar map every time since VTGate.Execute() mutates it.
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.
Added the comment with 4411cda.
I noticed something weird about the example error message in #5255:
As far as I can tell, the bind vars generated by the prepared statement code should remain Does anyone have any other example error messages for this case to compare? |
go/mysql/conn.go
Outdated
prepare.BindVars[k] = nil | ||
} | ||
} | ||
paramsCount := len(prepare.BindVars) |
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 should just use prepare.ParamsCount
. The value of len(prepare.BindVars)
could actually be greater than prepare.ParamsCount
when there are raw values in the query, which get converted into additional bindvars by the normalizer.
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 call out. Will adjust after the party tonight. Thanks!
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.
Adjusted with 0da79a8.
Signed-off-by: Peter Farr <Peter@PrismaPhonic.com>
af722d8
to
0da79a8
Compare
Signed-off-by: Peter Farr <Peter@PrismaPhonic.com>
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 took a deeper look on this and it looks good to me!
go/vt/vtgate/logstats.go
Outdated
@@ -125,6 +127,15 @@ func (stats *LogStats) Logf(w io.Writer, params url.Values) error { | |||
return nil | |||
} | |||
|
|||
// FormatBindVariables call might panic so we're going to catch it here and print out the stack trace to help us |
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.
Doesn't an uncaught panic already print a stack trace? Or is the problem that someone else has been recovering the panic somewhere else in the call stack?
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.
You're right that an uncaught panic already prints a stack trace. @sougou had asked me offline to use recover()
to handle the place where the panic was occurring as an extra safety net. I was hoping he would comment here on the PR to clarify the full extent of what he wanted to achieve with the recover()
. I've yet to have an opportunity to sync up with him about this part. Perhaps he meant without the explicit panic. @sougou?
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 heard that @sougou can be found at the PlanetScale booth at KubeCon. You should try to find him there. :)
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.
Chatted with him. He meant without an explicit panic and deepthi clarified that vtgate by design should never panic. Correcting now
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.
Adjusted c4270d2. PTAL
Signed-off-by: Peter Farr <Peter@PrismaPhonic.com>
fe7c867
to
c4270d2
Compare
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
nice work @PrismaPhonic! |
This PR fixes the bind variables nil error first discovered in issue #5255.
The fix involved explicitly setting
BindVars
to an empty map, rather than looping through and setting eachBindVar
to nil in the defer function.I also added a
recover
to catch any panics that might happen as a result of nilBindVars
when usingFormatBindVariables
inLogf
.