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

database/sql: Conn.Raw() will not close the connection on error unless f() returns driver.ErrBadConn #47500

Closed
brackendawson opened this issue Aug 2, 2021 · 3 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.

Comments

@brackendawson
Copy link

What version of Go are you using (go version)?

$ go version
go version go1.16.6 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN="/Users/alandaws/bin"
GOCACHE="/Users/alandaws/Library/Caches/go-build"
GOENV="/Users/alandaws/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/alandaws/pkg/mod"
GONOPROXY="github.ibm.com"
GONOSUMDB="github.ibm.com"
GOOS="darwin"
GOPATH="/Users/alandaws"
GOPRIVATE="github.ibm.com"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.16.6"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/06/jjq25lws0cbcxv37ns0hb_mm0000gn/T/go-build3808160546=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

In the following handler, where *Server has db *sql.DB:

func (s *Server) health(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	conn, err := s.db.Conn(ctx)
	if err != nil {
		http.Error(w, "Cannot connect to database: "+err.Error(), http.StatusBadGateway)
		return
	}
	defer conn.Close()
	if err := conn.Raw(func(driverConn interface{}) error {
		dc, ok := driverConn.(pinger)
		if !ok {
			return errors.New("database is not pingable")
		}
		if err := dc.Ping(ctx); err != nil {
			return fmt.Errorf("failed to ping databse: %w", err)
		}
		return nil
	}); err != nil {
		http.Error(w, "Error pinging database: "+err.Error(), http.StatusBadGateway)
	}
	fmt.Fprintln(w, "imok")
}

(yes I am aware PingContext() exists)

What did you expect to see?

When f() used in the call to Raw() fails, I expected the connection to be closed, as happens with other sql.Conn methods.

What did you see instead?

When the ping fails, the bad connection is not closed. It is then ultimately returned to the connection pool after sql.Conn.Close() is called.

In my use case this leads to the handler repeatedly failing and then things like kubernetes liveness probe failures and service restarts.

Analysis

The Raw() function will only close the connection if the error returned by f() is (==) driver.ErrBadConn https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/database/sql/sql.go;drc=422dc83baa2816ca1d9a0aa3f1aaf4c47c8098ad;l=1979.

I think this behaviour should be documented. Currently "Once f returns and err is nil, the Conn will continue to be usable until Conn.Close is called." is inaccurate. Perhaps "Once f returns and err is not equal to driver.ErrBadConn, the Conn will continue to be usable until Conn.Close is called." would be more precise?

It might also be worth changing the behaviour so that either any non-nil error, or any error which wraps driver.ErrBadConn will close the connection, the current behaviour surprised me.

@gopherbot
Copy link

Change https://golang.org/cl/338970 mentions this issue: database/sql: improve the documentation of Conn.Raw

@dr2chase dr2chase added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Aug 2, 2021
@dr2chase
Copy link
Contributor

dr2chase commented Aug 2, 2021

Not sure if this wants a change in documentation or behavior.
@bradfitz , @kardianos, any opinions?

@seankhliao seankhliao changed the title sql.Conn.Raw() will not close the connection on error unless f() returns driver.ErrBadConn database/sql: Conn.Raw() will not close the connection on error unless f() returns driver.ErrBadConn Aug 2, 2021
@kardianos kardianos self-assigned this Aug 4, 2021
@brackendawson
Copy link
Author

I have an opinion regarding behavioural changes:

I think it should remain possible to return an error from f() which the caller can read from their call to Conn.Raw() (so as not to have to close over an err variable) without it closing the connection. Because people using Conn.Raw() may have genuine reasons to call a series of driver methods on an apparently broken connection without inadvertently closing it.

It should at least be documented that to close the connection the error returned by f() must be driver.ErrBadConn, but as this is Go 1.16 I think we should be more idiomatic and change this behaviour to any error with driver.ErrBadConn in its chain as defined by errors.Is().

The danger of this change is that it is a logic change for anyone using Conn.Raw() since Go 1.13 who has returned an error from f() which wraps driver.ErrBadConn and intended not to close the connection. I would wager most such users did actually intend the connection to close.

@golang golang locked and limited conversation to collaborators Jun 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants