5

I'm trying to handle reconnections to MongoDB. To do this I try to perform every operation three times (in case it fails with io.EOF)

type MongoDB struct {
    session *mgo.Session
    DB      *mgo.Database
}

func (d MongoDB) performWithReconnect(collection string, 
operation func(*mgo.Collection) error) error {
    var err error
    for i := 0; i < 3; i++ {
        session := d.session.Copy()
        defer session.Close()
        err = operation(session.DB(Config.MongoDb).C(collection))
        if err == io.EOF{
            continue
        }
        if err == nil{
            return err
        }
    }
    return err
}

So the question is about defer. Will it close all sessions as I suppose or it is going to behave some other way? If you know some good practices to handle this different way I will be happy to read them.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
StTymur
  • 69
  • 1
  • 3

2 Answers2

6

Consider the following program

package main

import (
    "fmt"
)

func print(s string, i int) {
    fmt.Println(s, i)
}

func main() {

    for i := 0; i < 3; i++ {
        defer print("loop", i)
    }

    fmt.Println("after loop 1")

    for i := 0; i < 3; i++ {
        func(i int) {
            defer print("func", i)
        }(i)
    }

    fmt.Println("after loop 2")

}

It will print

after loop 1
func 0
func 1
func 2
after loop 2
loop 2
loop 1
loop 0

The deferred function calls will be put on stack and then executed in a reverse order at the end of surrounding function. In your case it will be quite bad as you will have connections waiting to be closed.

I recommend wrapping the contents of loop into an inline function. It will call deferred function just as you want it.

Grzegorz Żur
  • 47,257
  • 14
  • 109
  • 105
2

From A Tour of Go:

A defer statement defers the execution of a function until the surrounding function returns.

So in your code, you're creating three (identical) defer functions, which will all run when the function exits.

If you need a defer to run inside of a loop, you have to put it inside of a function. This can be done in an anonymous function thusly:

for i := 0; i < 3; i++ {
    err := func() error {
        session := d.session.Copy()
        defer session.Close()
        return operation(session.DB(Config.MongoDb).C(collection))
    }()
    if err == io.EOF {
        continue
    }
    if err != nil {
        return err
    }
}
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189