Skip to content

database/sql: Tx.Commit and Tx.Rollback do not remove bad connections from pool #11264

Closed
@ChrisHines

Description

@ChrisHines

go version go1.4.2 windows/amd64

This issue was originally discovered while using github.com/alexbrainman/odbc, but it does not seem to be a problem specific to that driver. It is minimally reproducible with the below test program.

The program runs a query inside a transaction five times. The commit (or rollback) of the second attempt returns driver.ErrBadConn.

I expect the bad connection to get removed from the pool and subsequent transactions to occur on a fresh connection.

Instead database/sql keeps the bad connection in the pool and subsequent transactions reuse the bad connection and also fail.

package main

import (
    "database/sql"
    "database/sql/driver"
    "log"
    "reflect"
    "runtime"
)

var opened, closed int

type MockDriver struct{}

func (d *MockDriver) Open(name string) (driver.Conn, error) {
    opened++
    return &MockConn{}, NextError()
}

type MockConn struct{ err error }

// NOTE: that this always succeeds, even if c.err != nil this simulates a
// driver that doesn't need to communicate the beginning of a transaction and
// therefore may not be able to discover a bad connection at this point.
func (c *MockConn) Begin() (driver.Tx, error) { return &MockTx{c}, nil }

func (c *MockConn) Close() error                              { closed++; return c.Err() }
func (c *MockConn) Prepare(query string) (driver.Stmt, error) { return &MockStmt{c}, c.Err() }
func (c *MockConn) Err() error {
    if c.err == nil {
        c.err = NextError()
    }
    return c.err
}

type MockTx struct{ c *MockConn }

func (tx *MockTx) Commit() error   { return tx.c.Err() }
func (tx *MockTx) Rollback() error { return tx.c.Err() }

type MockStmt struct{ c *MockConn }

func (s *MockStmt) Close() error  { return s.c.Err() }
func (s *MockStmt) NumInput() int { return -1 }
func (s *MockStmt) Exec(args []driver.Value) (driver.Result, error) {
    return driver.ResultNoRows, s.c.Err()
}
func (s *MockStmt) Query(args []driver.Value) (driver.Rows, error) { return &MockRows{s}, s.c.Err() }

type MockRows struct{ s *MockStmt }

func (r *MockRows) Columns() []string              { return []string{} }
func (r *MockRows) Close() error                   { return r.s.c.Err() }
func (r *MockRows) Next(dest []driver.Value) error { return r.s.c.Err() }

var nextError error

func NextError() (err error) {
    err, nextError = nextError, nil
    return
}

func main() {
    drv := &MockDriver{}
    sql.Register("mock", drv)
    db, err := sql.Open("mock", "")
    checkErr(0, err, nil)
    for i := 0; i < 5; i++ {
        // this always succeeds because that's the way some drivers may work
        tx, err := db.Begin()
        checkErr(i, err, nil)
        if err != nil {
            continue
        }
        rows, err := tx.Query("query")
        checkErr(i, err, nil)
        if err != nil {
            err = tx.Rollback()
            checkErr(i, err, nil)
            continue
        }
        rows.Close()
        var want error
        if i == 1 {
            nextError, want = driver.ErrBadConn, driver.ErrBadConn
        }
        // fails when i == 1, but the connection is not removed from the pool
        err = tx.Commit()
        checkErr(i, err, want)
        log.Printf("i(%d), opened: %d, closed: %d", i, opened, closed)
    }
    log.Printf("final, opened: %d, closed: %d", opened, closed)
}

func checkErr(i int, got, want error) {
    if !reflect.DeepEqual(got, want) {
        _, _, line, _ := runtime.Caller(1)
        log.Printf("i(%d), line %d: got %v, want %v", i, line, got, want)
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions