Skip to content

Implement Pinger interface #572

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

Closed
wants to merge 8 commits into from
Closed

Implement Pinger interface #572

wants to merge 8 commits into from

Conversation

photon3108
Copy link

@photon3108 photon3108 commented Apr 26, 2017

Description

Implement Pinger interface #505

Checklist

  • Code compiles correctly
  • Created tests which fail without the change (if possible)
  • All tests passing
  • Extended the README / documentation, if necessary
  • Added myself / the copyright holder to the AUTHORS file

@photon3108 photon3108 changed the title Go1.8 pinger Implement Pinger interface Apr 26, 2017
@@ -81,7 +81,8 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
}
}

mc.buf = newBuffer(mc.netConn)
// Make the buffer get the context from mysqlConn.
mc.buf = newBufferContext(mc.netConn, &ContextWrapper{mc: mc})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do both the buffer and the mysqlConn need a Context?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buffer.fill() sets the timeout by SetReadDeadline() and mysqlConn.writePacket() sets the timeout by SetWriteDeadline. Context.Deadline() may be earlier than those two timeouts. So Context.Deadline() must be passed into these two functions. There are two ways to pass it. The first one is to pass it as a parameter of a function. The second one is to pass it as a member of a struct. If I chose the first one, I would have to modify every functions from mysqlConn.Ping() to buffer.fill() and mysqlConn.writePacket(). The second one is much easier and there is no need to wrap Context by something like context.withCancel() in this case.

The real context is stored at mysqlConn.ctx and the buffer.ctx is just a wrapper/handler which points to the previous one. I use wrapper/handler because copying a value everywhere is not a good idea. People may modify some and forget the others.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context.Deadline() may be earlier than those two timeouts. So Context.Deadline() must be passed into these two functions.

How about calling .Close() instead of .SetDeadline() when <- ctx.Done()?
Cancelled connection is mostly unusable furthermore.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two reasons why I didn't use netConn.Close() when <- ctx.Done(). One is even if ctx.Done() happens and no matter it calls netConn.Close() or netConn.SetDeadline(), mysqlConn.pingImpl() may still send nil error to line 65 before these two functions interrupt it. In this situation, netConn doesn't have to be closed and it's safe to reuse this connection. BTW, mysqlConn.Ping() checks the received err at line 72. If the err shows the connection timed out, it will returns ErrBadConn and let caller close it.

The another reason is about the life cycle of mysqlConn. I think It is created and managed in database/sql/sql.go. It should be destroyed in the same level/place. Actually it does in DB.putConn() if Pinger.Ping() returns ErrBadConn. Let DB.putConn() does what its duty is and mysqlConn.Ping() should not make DB.putConn() close it again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One is even if ctx.Done() happens and no matter it calls netConn.Close() or netConn.SetDeadline(), mysqlConn.pingImpl() may still send nil error to line 65 before these two functions interrupt it. In this situation, netConn doesn't have to be closed and it's safe to reuse this connection.

Cancelleration is rare situation. Code simplicity (maintenance cost) is more important than how cancellation efficient.

The another reason is about the life cycle of mysqlConn. I think It is created and managed in database/sql/sql.go. It should be destroyed in the same level/place.

But TCP lifetime can be shorter than mysqlConn's lifetime.

Copy link
Author

@photon3108 photon3108 Apr 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cancelleration is rare situation. Code simplicity (maintenance cost) is more important than how cancellation efficient.

I like KISS principle and also think simplicity is important but from my point of view, I still think there is a short and blurry distance between simplicity and correctness in this case. The key point is that force to close any connections no matter they are normal or unusable is good here? I prefer that let normal one reuse again and let caller close the unusable one. It's more logical. Actually netConn.SetDeadline() and UpdateDeadlineLoop are not so hard to understand. I wrote the comments. But this is from my perspective. If you ask me to use netConn.Close() and remove UpdateDeadlineLoop, I could do it for you.

But TCP lifetime can be shorter than mysqlConn's lifetime.

Technically, yes, but mysqlConn.netConn, a TCP connection, will be closed in mysqlConn.Close() and mysqlConn.Close() will be called by DB.putConn() if Pinger.Ping() returns ErrBadConn. In other words, Golang database/sql/sql.go has already provided the mechanism to close a driver.Conn(mysql.mysqlConn), and indirectly destroy its underlying resource. Why should we do the same job again at each corner and duplicate the codes and the logics? Are there any bugs about compatibility from the earlier versions of Golang?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if Pinger.Ping() returns ErrBadConn. In other words, Golang database/sql/sql.go has already provided the mechanism to close a driver.Conn(mysql.mysqlConn), and indirectly destroy its underlying resource.

It's true only in Ping. We can't use ErrBadConn for QueryContext() or other APIs to close connection from caller soon.
I want to use same pattern as possible for Ping, QueryContext() and other APIs.

https://golang.org/pkg/database/sql/driver/
To prevent duplicate operations, ErrBadConn should NOT be returned if there's a possibility that the database server might have performed the operation. Even if the server sends back an error, you shouldn't return ErrBadConn.

Copy link
Author

@photon3108 photon3108 Apr 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to use same pattern as possible for Ping, QueryContext() and other APIs.

I see what you concern. How about still letting normal connection reuse agagin but replacing line 72~80 with these:

if err == nil {
  return nil
}
if mc.netConn != nil {
  errClose := mc.netConn.Close()
  if errClose != nil {
    errLog.Print(errClose)
  }
  mc.netConn = nil
}
return err

}
}

if ctx.Done() == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would ctx.Done() return nil?
Example code in this document doesn't check nil.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, context.Background().Done() would returns nil. [1][2]

@@ -81,7 +81,8 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
}
}

mc.buf = newBuffer(mc.netConn)
// Make the buffer get the context from mysqlConn.
mc.buf = newBufferContext(mc.netConn, &ContextWrapper{mc: mc})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context.Deadline() may be earlier than those two timeouts. So Context.Deadline() must be passed into these two functions.

How about calling .Close() instead of .SetDeadline() when <- ctx.Done()?
Cancelled connection is mostly unusable furthermore.

@julienschmidt julienschmidt modified the milestone: v1.4 May 10, 2017
@photon3108 photon3108 closed this May 12, 2017
@photon3108 photon3108 deleted the go1.8-Pinger branch May 12, 2017 05:29
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