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

Treat dial error as driver.ErrBadConn #867

Merged
merged 5 commits into from Oct 31, 2018
Merged

Treat dial error as driver.ErrBadConn #867

merged 5 commits into from Oct 31, 2018

Conversation

tjenkinson
Copy link
Contributor

@tjenkinson tjenkinson commented Oct 12, 2018

Description

Currently if an error happens from the Dial() call, there are no retries. The go sql driver will only retry on a driver.ErrBadConn error.

The following is the doc for this error:

// ErrBadConn should be returned by a driver to signal to the sql
// package that a driver.Conn is in a bad state (such as the server
// having earlier closed the connection) and the sql package should
// retry on a new connection.
//
// 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.

I noticed that other functions in MySQLDriver.Open() already return this error, so I wonder if this was an oversight?

One concern with this change is that users might be expecting and handling a net.Error from dial(), which with this PR would only be logged now.

It would also be possible to handle this without changes to the lib, by using RegisterDial(). If we are not concerned that users might be expecting net.Error, I feel that driver.ErrBadConn is a more appropriate error and users should't be required to use RegisterDial() if they want retries.

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

Tom Jenkinson added 3 commits October 12, 2018 10:37
because this error is retried in the driver and means the connection never succeeded

```
// ErrBadConn should be returned by a driver to signal to the sql
// package that a driver.Conn is in a bad state (such as the server
// having earlier closed the connection) and the sql package should
// retry on a new connection.
//
// 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.
```
@methane
Copy link
Member

methane commented Oct 16, 2018

In case of Open(), all error means "driver.Conn is in bad state".
And since no query is sent in Open(), it's safe to retry in new connection.

@methane
Copy link
Member

methane commented Oct 16, 2018

Then, we don't need to return ErrBadConn always in Open().

When we think database/sql should retry Open() again, we should return ErrBadConn.
Otherwise, returning ErrBadConn is not worth enough.

For example, if context is canceled, we should not return ErrBadConn.
On the other hand, if nerr, ok := err.(net.Error); ok && nerr.Temporary(), it may be worth to return ErrBadConn.

@tjenkinson
Copy link
Contributor Author

👍 @methane I added a commit to only return driver.ErrBadConn on a temporary error or timeout

driver.go Outdated
@@ -77,6 +77,10 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
}
if err != nil {
if nerr, ok := err.(net.Error); ok && (nerr.Temporary() || nerr.Timeout()) {
Copy link
Member

Choose a reason for hiding this comment

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

Don't || nerr.Timeout().

nerr.Temporary() returns True when retry is worthful.
For example, lookup timeout is temporary.

func (e *DNSError) Temporary() bool { return e.IsTimeout || e.IsTemporary }

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 docs say

IsTimeout   bool   // if true, timed out; not all timeouts set this
IsTemporary bool   // if true, error is temporary; not all errors set this; added in Go 1.6

so I thought it would be better to check both given IsTemporary might not be supported

@tjenkinson
Copy link
Contributor Author

@methane is this good to be merged?

@methane
Copy link
Member

methane commented Oct 31, 2018

I still dought this is worth enough to add more code to this project.
Is it really needed for your project?

@methane
Copy link
Member

methane commented Oct 31, 2018

After reading database/sql again, I think this change is reasonable even though
I don't have experience I want this. Thanks.

@methane methane merged commit fd197cd into go-sql-driver:master Oct 31, 2018
@mrdg mrdg deleted the treat-dial-error-as-ErrBadConn branch November 21, 2019 14:23
@julienschmidt julienschmidt added this to the v1.5.0 milestone Jan 3, 2020
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.

None yet

3 participants