OK. I’ve made some progress, My client code is now:
package main
import (
q "github.com/carlca/bigdata/sqlserver"
_ "github.com/denisenkom/go-mssqldb"
)
func main() {
db := q.Connect()
defer db.Close()
tx := db.NewTx()
tx.Exec("DROP TABLE IF EXISTS Company")
tx.Exec(`CREATE TABLE Company
(ProductID int PRIMARY KEY NOT NULL,
ProductName varchar(25) NOT NULL,
Price money NULL,
Price2 money NULL,
ProductDescription text NULL)`)
tx.CommitTx()
}
I also have this library code:
package sqlserver
import (
"database/sql"
"flag"
"fmt"
e "github.com/carlca/utils/essentials"
)
var (
debug = flag.Bool("debug", false, "enable debugging")
password = flag.String("password", "", "the database password")
port = flag.Int("port", 1433, "the database port")
server = flag.String("server", "", "the database server")
user = flag.String("user", "", "the database user")
)
// DB inherits from sql.DB
type DB struct {
*sql.DB
}
// Exec combines Prepare and Exec methods
func (db *DB) Exec(cmd string) {
stmt, err := db.Prepare(cmd)
e.CheckError("prepare: "+cmd+" failed", err)
_, err = stmt.Exec()
e.CheckError("exec: "+cmd+" failed", err)
if *debug {
fmt.Printf("exec: %v succeeded\n", cmd)
}
}
// NewTx wraps the *DB.Begin func
func (db *DB) NewTx() *Tx {
tnx, err := db.Begin()
e.CheckError("BeginTx failed: ", err)
if *debug {
fmt.Printf("NewTx: succeeded\n")
}
return &Tx{*tnx}
}
// Tx inherits from sql.Tx
type Tx struct {
sql.Tx
}
// Exec wraps *Tx.Exec
func (tx *Tx) Exec(cmd string) {
stmt, err := tx.Prepare(cmd)
e.CheckError("prepare: "+cmd+" failed", err)
_, err = stmt.Exec()
e.CheckError("exec: "+cmd+" failed", err)
if *debug {
fmt.Printf("exec: %v succeeded\n", cmd)
}
}
// CommitTx wraps the *Tx.Commit func
func (tx *Tx) CommitTx() {
err := tx.Commit()
e.CheckError("tx.CommitTx failed: ", err)
if *debug {
fmt.Printf("tx.CommitTx succeeded\n")
}
}
// Connect establishes contact with an SQL Server
func Connect() *DB {
// parse command line flags
flag.Parse()
// dump flags if debug
if *debug {
fmt.Printf("password: %s\n", *password)
fmt.Printf("port: %d\n", *port)
fmt.Printf("server: %s\n", *server)
fmt.Printf("user: %s\n", *user)
}
// build connection string
connString := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d", *server, *user, *password, *port)
// if debug dump connection string
if *debug {
fmt.Printf("connString: %s\n", connString)
}
// create an SQL Server connection
dbx, err := sql.Open("mssql", connString)
e.CheckError("Open DB failed: ", err)
if *debug {
fmt.Printf("open mssql: succeeded\n")
}
err = dbx.Ping()
e.CheckError("db.Ping failed", err)
return &DB{dbx}
}
The problem is that I’m getting intermittant failure at the end of the client code. It says “panic: sql: connection returned that was never out”. When this occurs, however, if I wait a couple of seconds and run the client program again, it runs without failure.
The error message comes from a func *DB.putConn in sql.go, and seems to be a state dependent issue that occurs depending on timing.
I’ve had a search on Google but, apart from some mentions from github.com/go from 2012 or thereabouts, I cannot find any relevant references.
Can anyone help? Thanks.
func (db *DB) putConn(dc *driverConn, err error) {
db.mu.Lock()
if !dc.inUse {
if debugGetPut {
fmt.Printf("putConn(%v) DUPLICATE was: %s\n\nPREVIOUS was: %s", dc, stack(), db.lastPut[dc])
}
panic("sql: connection returned that was never out")
}
if debugGetPut {
db.lastPut[dc] = stack()
}
dc.inUse = false
for _, fn := range dc.onPut {
fn()
}
dc.onPut = nil
if err == driver.ErrBadConn {
// Don't reuse bad connections.
// Since the conn is considered bad and is being discarded, treat it
// as closed. Don't decrement the open count here, finalClose will
// take care of that.
db.maybeOpenNewConnections()
db.mu.Unlock()
dc.Close()
return
}
if putConnHook != nil {
putConnHook(db, dc)
}
added := db.putConnDBLocked(dc, nil)
db.mu.Unlock()
if !added {
dc.Close()
}
}