Ah, quite right. That's what comes of trying to modify the code snippet
from our actual code on the fly. It was more like:

t, err := InsertFoo(tx)

-Ben


On Mon, Dec 3, 2018, 5:50 PM Robert Engels <reng...@ix.netcom.com wrote:

> How can you write this
>
> err := InsertFoo(tx)
>
> Don’t you get no new variables defined error here?
>
> On Dec 3, 2018, at 3:53 PM, Ben Hoyt <benh...@gmail.com> wrote:
>
> 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.
>
>

-- 
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.

Reply via email to