Robert, here is code (actual working code this time) similar to what we have: https://play.golang.org/p/jUPqgnk6Ttk
Leonel, yep, I understand that, which is why we have this problem. The thing is, this pattern makes it very easy to forget to always re-use the same error variable, especially in code that's a bit more complex with a bunch of nested ifs, etc. So we're trying to come up with a pattern that's hard or impossible to misuse, instead of easy to misuse. -Ben On Mon, Dec 3, 2018 at 6:46 PM Leonel Quinteros <leonel.quinte...@gmail.com> wrote: > Hi Ben, > > I'm pretty sure that the *err* variable is getting shadowed on your > Update construct. > Instead of doing: > > if _, err := UpdateBar(tx); err != nil { > return err > } > > You should do something like: > > _, err = UpdateBar(tx) > if err != nil { > return err > } > > Just like you do with the insert and avoiding the *:=* operand > > Let me know if that works. > > > Best! > Leonel > > > El lunes, 3 de diciembre de 2018, 18:53:30 (UTC-3), Ben Hoyt escribió: >> >> 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 a topic in the > Google Groups "golang-nuts" group. > To unsubscribe from this topic, visit > https://groups.google.com/d/topic/golang-nuts/ge49ywYnjio/unsubscribe. > To unsubscribe from this group and all its topics, send an email to > golang-nuts+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. > -- 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.