-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Closed
Labels
Milestone
Description
What version of Go are you using (go version
)?
$ go version go version go1.11.5 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 GOARCH="amd64" GOBIN="" GOCACHE="/Users/georgeleslie-waksman/Library/Caches/go-build" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOOS="darwin" GOPATH="/Users/georgeleslie-waksman/co/backend/go" GOPROXY="" GORACE="" GOROOT="/Users/georgeleslie-waksman/co/backend/opt/go1.11.5" GOTMPDIR="" GOTOOLDIR="/Users/georgeleslie-waksman/co/backend/opt/go1.11.5/pkg/tool/darwin_amd64" GCCGO="gccgo" CC="clang" CXX="clang++" CGO_ENABLED="1" GOMOD="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/41/19kt662x72n9nfj8hzb8t1q00000gq/T/go-build118274996=/tmp/go-build -gno-record-gcc-switches -fno-common"
What did you do?
When connections are released, they may bypass the pool and be used to fulfill requests made with strategy=alwaysNewConn
even if they are "expired" according to SetConnMaxLifetime
, which allows the connections to fail on BeginTx
with ErrBadConn
despite passing through putConn
with err == nil
The following testcase (for database/sql_test.go
) exhibits the failure case:
func TestPutConnExpiredOrBadReset(t *testing.T) {
execCases := []struct{
expired bool
badReset bool
}{
{false, false},
{true, false},
{false, true},
}
t0 := time.Unix(1000000, 0)
offset := time.Duration(0)
nowFunc = func() time.Time { return t0.Add(offset) }
defer func() { nowFunc = time.Now}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db := newTestDB(t, "magicquery")
defer closeDB(t, db)
db.SetMaxOpenConns(1)
for _, ec := range execCases {
ec := ec
name := fmt.Sprintf("expired=%t,badReset=%t", ec.expired, ec.badReset)
t.Run(name, func(t *testing.T) {
db.clearAllConns(t)
db.SetMaxIdleConns(1)
db.SetConnMaxLifetime(10 * time.Second)
conn, err := db.conn(ctx, alwaysNewConn)
if err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
conn, err := db.conn(ctx, alwaysNewConn)
if err != nil {
t.Fatal(err)
}
db.putConn(conn, err, false)
}()
// Wait for pending request
for len(db.connRequests) < 1 {}
if ec.expired {
offset = 11 * time.Second
} else {
offset = time.Duration(0)
}
if ec.badReset {
conn.ci.(*fakeConn).stickyBad = true
}
db.putConn(conn, err, true)
wg.Wait()
})
}
}
with output:
--- FAIL: TestPutConnExpiredOrBadReset (0.00s)
--- FAIL: TestPutConnExpiredOrBadReset/expired=true,badReset=false (0.00s)
sql_test.go:2226: driver: bad connection
--- FAIL: TestPutConnExpiredOrBadReset/expired=false,badReset=true (0.00s)
sql_test.go:2226: driver: bad connection
FAIL
FAIL _/Users/georgeleslie-waksman/co/go/src/database/sql 1.337s
What did you expect to see?
BeginTx
's third attempt with strategy=alwaysNewConn
should not reuse an existing connection and should definitely not reuse an expired connection.
What did you see instead?
BeginTx
's third attempt fails with ErrBadConn
due to connection expiration.
mmailhos and busyfree