-
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
mysql: improve code structure for buffered vs unbuffered writes #4190
Conversation
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
go/mysql/server.go
Outdated
return io.EOF | ||
} | ||
success := func() bool { | ||
c.startBuffering() |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
go/mysql/conn.go
Outdated
// With direct=true, the caller expects a flush, so we call it | ||
// manually. | ||
if err := c.writePacket(*c.currentEphemeralWriteBuffer); err != nil { | ||
if err := c.writePacket(w, *c.currentEphemeralWriteBuffer); err != 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.
perhaps make this not a function on conn
anymore?
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.
ah. we need sequence number. that said, if we take suggestion above of swapping out writer at a higher level (when we start buffering), then we no longer need to pass the writer in as an argument to writePacket
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.
getWriter
should address this concern also.
go/mysql/conn.go
Outdated
if direct { | ||
w = c.conn | ||
var w io.Writer = c.conn | ||
if c.writer != 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.
i'd almost like to see this swapout happen with startBuffering
call, and then instead of calling flush
directly, startBuffering
returns a finisher function func()
that resets c.conn to the direct connectin appropriately and then calls flush
.
This seems like a lot of trickiness, but I think what I'm trying to go for is to completely abstract away whether or not the connection is buffered or not from the writeXXX
functions. @LK4D4 initially did this by pushing the buffering down into the writer
object, and what you're doinig here is actually pulling the buffering control UP into the caller. i think the advantages of making flush
easier to reason about and see make this approach worthwhile, in which case i'd like to go the rest of the way and get rid of the buffered writer juggling down here.
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.
c.writer
being nil also acts as a state to indicate that we're buffering. If we hide that, we'll need a different variable to track the state.
But I can wrap this inside a getWriter
function which will remove the awkwardness of writePacket; It can also call it at the beginning.
As for the finisher function, c.flush
is that. I don't see much value in returning it as is for startBuffering
. Finishers make more sense if they did different things or acted on different objects, like in the case of contexts.
I think getWriter hits what I'm after :)
--
Daniel
dktahara@gmail.com
(646) 397-6379
|
This is ready for review. |
9076e74
to
6e92bd6
Compare
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
switch c.currentEphemeralPolicy { | ||
case ephemeralWriteSingleBuffer: | ||
// Write the allocated buffer as a single buffer. | ||
// It has both header and data. | ||
if n, err := w.Write(*c.currentEphemeralWriteBuffer); err != nil { | ||
if n, err := c.getWriter().Write(*c.currentEphemeralWriteBuffer); err != nil { | ||
return fmt.Errorf("Conn %v: Write(*c.currentEphemeralWriteBuffer) failed: %v", c.ID(), 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.
do you remember why there was no flush() path here?
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.
If direct
is true, then we directly write to c.conn. So, there's no need to flush. There is a subtle futuristic bug: if someone had previously invoked writeEphemeral with false
, then something would be sitting in the write buffer, and the next call with true
would skip those buffered items, and directly write the current packet to the conn.
The new code protects from this possibility.
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.
hrmm, then why was there one on line 493?
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.
That one was there because the code decides to use bufio in spite of the direct
flag. So, it had to flush to maintain the contract. You'll see that in the old code, writePacket always used c.writer
without regard to the direct
flag.
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.
ah okay. thanks for explanation
Handling error from flush required me to pull it out of the wrapped function, and this ended up making it unnecessary. The flush is now called at the end of the block. Signed-off-by: Sugu Sougoumarane <ssougou@gmail.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.
This looks good to me. If @LK4D4 could do a pass as well before landing, that'd be great.
switch c.currentEphemeralPolicy { | ||
case ephemeralWriteSingleBuffer: | ||
// Write the allocated buffer as a single buffer. | ||
// It has both header and data. | ||
if n, err := w.Write(*c.currentEphemeralWriteBuffer); err != nil { | ||
if n, err := c.getWriter().Write(*c.currentEphemeralWriteBuffer); err != nil { | ||
return fmt.Errorf("Conn %v: Write(*c.currentEphemeralWriteBuffer) failed: %v", c.ID(), 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.
ah okay. thanks for explanation
} | ||
|
||
defer func() { |
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 defer here adds to the complexity.
Can we just
err := c.bufferedWriter.Flush()
...
return 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.
The defer protects us from accidental panics. It's the recommended pattern unless there is a specific reason like performance, etc.
If there are write errors while writing responses, then the handle function may return without flush getting called. This change calls flush on the defer to make sure we return the write buffer back to the pool. Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
@sougou LGTM |
For some reason, the build result is not feeding back into the checks eventhough it's green. I'm going to force merge this. |
@danieltahara @LK4D4: can you take a look at this approach?
The previous code had context sensitive functions that could not be freely reused because some assumed that they were using bufio and some didn't. This is now changed in such a way that the
buffering decision is made at a high level (only for ComQuery results).
This also changes how we would pool bufio.Writer compared to #4188.
Signed-off-by: Sugu Sougoumarane ssougou@gmail.com