The best way to access the return value of a function is to name the return 
variable:

func DoTwoThings(db *Database) (err error) {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Close(&err)

...
}

Then any accidental shadowing doesn't matter because the "defer" is 
referring to the actual return variable; it also means that you can support 
returning a new error directly (i.e. `return nil, fmt.Errorf(...)`) rather 
than needing to set the outer 'err' variable and then return it.

On Monday, December 3, 2018 at 3:55:03 PM UTC-8, Ben Hoyt wrote:
>
> 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.q...@gmail.com 
> <javascript:>> 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...@googlegroups.com <javascript:>.
>> 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