Bug Fix: Prepared Statements Bind Variables Nil Error#5441
Bug Fix: Prepared Statements Bind Variables Nil Error#5441enisoc merged 5 commits intovitessio:masterfrom
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.
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.
ok, so should we mask and report errors or something? or would rather have things immediately fail?
There was a problem hiding this comment.
@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.
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.
@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.
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.|
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.
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.
Good call out. Will adjust after the party tonight. Thanks!
Signed-off-by: Peter Farr <Peter@PrismaPhonic.com>
af722d8 to
0da79a8
Compare
Signed-off-by: Peter Farr <Peter@PrismaPhonic.com>
saifalharthi
left a comment
There was a problem hiding this comment.
I took a deeper look on this and it looks good to me!
go/vt/vtgate/logstats.go
Outdated
| 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.
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.
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.
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.
Chatted with him. He meant without an explicit panic and deepthi clarified that vtgate by design should never panic. Correcting now
Signed-off-by: Peter Farr <Peter@PrismaPhonic.com>
fe7c867 to
c4270d2
Compare
|
nice work @PrismaPhonic! |
This PR fixes the bind variables nil error first discovered in issue #5255.
The fix involved explicitly setting
BindVarsto an empty map, rather than looping through and setting eachBindVarto nil in the defer function.I also added a
recoverto catch any panics that might happen as a result of nilBindVarswhen usingFormatBindVariablesinLogf.