I have a function that is designed to handle TSQL queries. Essentially, it attempts to create a connection to the server, run the query, commit and then close the connection. Currently, we're relying on the mssqldb driver as the backend is Azure SQL.
func (requester *Requester) doTransaction(ctx context.Context, isolation sql.IsolationLevel,
txFunc func(*sql.Tx) error) error {
// First, attempt to get a connection from the connection pool. If this fails return an error
conn, err := requester.conn.Conn(ctx)
if err != nil {
fmt.Printf("Conn failed, Error type: %s\n", reflect.TypeOf(err))
log.Printf("Conn failed, error: %v", err)
return err
}
// Before we continue on, ensure that the connection is clsoed and returned to the connection pool
defer func() {
if err := conn.Close(); err != nil {
log.Printf("Close failed, error: %v", err)
}
}()
// Next, start the transaction with the given context and the default isolation
tx, err := conn.BeginTx(ctx, &sql.TxOptions{Isolation: isolation, ReadOnly: false})
if err != nil {
fmt.Printf("BeginTx failed, Error type: %s\n", reflect.TypeOf(err))
log.Printf("BeginTx failed, error: %v", err)
return err
}
// Now, ensure that the transaction is either rolled back or committed before
// the function ends
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p)
} else if err != nil {
log.Printf("An error occurred: %v", err)
if err := tx.Rollback(); err != nil {
log.Printf("Rollback failed, error: %v", err)
}
} else {
if err := tx.Commit(); err != nil {
log.Printf("Commit failed, error: %v", err)
}
}
}()
// Finally, run the function and return the result
err = txFunc(tx)
return err
}
This works well for the most part. However, I've noticed a number of errors that occur due to things like timeouts, serverless pausing, IO limits being exceeded, etc; such as:
Login error: mssql: Database 'my-db' on server 'myserver.database.windows.net' is not currently available. Please retry the connection later. If the problem persists, contact customer support, and provide them the session tracing ID of '{SOME_GUID}'.
I'd like to handle these with a backoff of some kind rather than just failing. However, to do that I need to be able to interpret the error in some fashion. However, the errors returned all have the type of *errors.errorString
. I've tried doing using As(error, interface{})
to check if the error was a mssql.Error
and it's not so I'm not really sure how to handle it. How do I determine what the cause is for these errors?
You could try converting the error that you get to an Error as defined here as defined here using a type assertion
err:=//your operation that returns an error
//check and convert the error to a MSSQL error with more context
if msSQLErr,ok:=err.(mssql.Error);ok{
if msSQLErr.SQLErrorNumber() == someRetryableErrorCode{
//custom retry logic...
}
}
Alternatively, you can use the errors.As as follows
var msSQLErr mssql.Error
if errors.As(err,&msSQLErr){
if msSQLErr.SQLErrorNumber() == someRetryableErrorCode{
//custom retry logic...
}
}
Update: Unfortunately, it seems like the library doesn't wrap underlying errors, so you can not use errors.Is or errors.As for some errors e.g. network related errors, so you will have to match the error strings yourself using something like strings.Contains