Hi folks, We found some subtle bugs in our db transaction code for handling commits/rollbacks. Here's the pattern we were using (not real, but shows the issue):
func DoTwoThings() error { tx, err := db.Begin() if err != nil { return err } // commit or rollback the transaction before we return defer tx.Close(&err) err := InsertFoo(tx) if err != nil { return err } if _, err := UpdateBar(tx); err != nil { return err } return nil } The problem is there's a subtle but potentially quite bad bug with this usage pattern -- if the InsertFoo succeeds but UpdateBar fails, the first "err" variable will be nil, so the deferred tx.Close() will COMMIT the transaction rather than ROLLBACK, and the database will be in an inconsistent state. The code above is a bit contrived, and you can easily fix it by moving the "_, err := UpdateBar()" outside of the if so it's referring to the same "err" variable, but it's very easy to miss and get it wrong. So we decided it was a bad pattern and started thinking about the best way to fix. One idea is a RollbackUnlessCommitted() function which you can defer, and then you call Commit() once manually (stolen from gocraft/dbr): func DoTwoThings() error { tx, err := db.Begin() if err != nil { return err } defer tx.RollbackUnlessCommitted() err := InsertFoo(tx) if err != nil { return err } if _, err := UpdateBar(tx); err != nil { return err } tx.Commit() return nil } Another idea is to create a "Transact" function which takes an anonymous function and does all the transaction handling: func (db *DatabaseImpl) Transact(txFunc func() error) (err error) { tx, err := db.Begin() if err != nil { return } defer func() { if p := recover(); p != nil { tx.Rollback() panic(p) // re-throw panic after Rollback } else if err != nil { tx.Rollback() // err is non-nil; don't change it } else { err = tx.Commit() // err is nil; if Commit returns error update err } }() err = txFunc(tx) return err } And then the DoTwoThings function becomes: func DoTwoThings() error { return db.Transact(func() error) { err := InsertFoo(tx) if err != nil { return err } if _, err := UpdateBar(tx); err != nil { return err } }) } I think the second is probably safer and nicer, but it's slightly awkward in that it requires an extra level of indentation. Still, awkward is better than buggy. Does anyone else have a better pattern for this kind of thing, or feedback on the above? -Ben -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.