Skip to content
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

Merged
merged 4 commits into from
Sep 11, 2018

Conversation

sougou
Copy link
Contributor

@sougou sougou commented Sep 9, 2018

@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

Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
return io.EOF
}
success := func() bool {
c.startBuffering()

This comment was marked as resolved.

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 {

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?

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

Copy link
Contributor Author

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 {

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.

Copy link
Contributor Author

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.

@danieltahara
Copy link

danieltahara commented Sep 9, 2018 via email

@sougou sougou changed the title Proposed alternate way to use write buffer. mysql: improve code structure for buffered vs unbuffered writes Sep 9, 2018
@sougou
Copy link
Contributor Author

sougou commented Sep 9, 2018

This is ready for review.

@sougou sougou force-pushed the mysql branch 2 times, most recently from 9076e74 to 6e92bd6 Compare September 9, 2018 18:04
Signed-off-by: Sugu Sougoumarane <ssougou@gmail.com>
go/mysql/conn.go Outdated Show resolved Hide resolved
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)

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?

Copy link
Contributor Author

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.

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?

Copy link
Contributor Author

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.

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

go/mysql/server.go Outdated Show resolved Hide resolved
go/mysql/server.go Outdated Show resolved Hide resolved
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>
Copy link

@danieltahara danieltahara left a 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)

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() {
Copy link
Contributor

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

Copy link
Contributor Author

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.

go/mysql/server.go Show resolved Hide resolved
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>
@LK4D4
Copy link
Contributor

LK4D4 commented Sep 10, 2018

@sougou LGTM

@sougou
Copy link
Contributor Author

sougou commented Sep 11, 2018

For some reason, the build result is not feeding back into the checks eventhough it's green. I'm going to force merge this.

@sougou sougou merged commit fc41fc3 into vitessio:master Sep 11, 2018
@sougou sougou deleted the mysql branch September 11, 2018 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants