2

So I'm having some trouble figuring out best practices for using concurrency with a MongoDB in go. My first implementation of getting a session looked like this:

var globalSession *mgo.Session

func getSession() (*mgo.Session, error) {
    //Establish our database connection
    if globalSession == nil {
        var err error
        globalSession, err = mgo.Dial(":27017")
        if err != nil {
            return nil, err
        }

        //Optional. Switch the session to a monotonic behavior.
        globalSession.SetMode(mgo.Monotonic, true)
    }

    return globalSession.Copy(), nil
}

This works great the trouble I'm running into is that mongo has a limit of 204 connections then it starts refusing connections connection refused because too many open connections: 204;however, the issue is since I'm calling session.Copy() it only returns a session and not an error. So event though the connection refused my program never thrown an error.

Now what I though about doing is just having one session and using that instead of copy so I can have access to a connection error like so:

var session *mgo.Session = nil

func NewSession() (*mgo.Session, error) {
    if session == nil {
        session, err = mgo.Dial(url)
        if err != nil {
            return nil, err
        }
    }

    return session, nil
}

Now the problem I have with this is that I don't know what would happen if I try to make concurrent usage of that same session.

ThreeAccents
  • 1,822
  • 2
  • 13
  • 24
  • When using the code in the first example, do you close the returned session when you are done with it? – Charlie Tumahai Sep 25 '15 at 18:13
  • @BravadaZadada yeah I'm sending the session over a channel and then closing it – ThreeAccents Sep 25 '15 at 19:38
  • 1
    You have a [race](https://blog.golang.org/race-detector) on your global pointer (assuming you ever call your `getSession` concurrently). You can't have `var x *T` and then safely do `if x == nil { x = … }` concurrently. One solution to that is to use [`sync.Once`](https://golang.org/pkg/sync/#Once). Another is to just initialize the global once at program start (in `init`, via `var x = getSession()`, in `main`, etc). – Dave C Sep 25 '15 at 19:38
  • What I usually do is to hand over a session, copy it and defer a close of the copied session. – Markus W Mahlberg Sep 08 '17 at 08:49

1 Answers1

1

The key is to duplicate the session and then close it when you've finished with it.

func GetMyData() []myMongoDoc {

    sessionCopy, _ := getSession() // from the question above
    defer sessionCopy.Close() // this is the important bit

    results := make([]myMongoDoc, 0)
    sessionCopy.DB("myDB").C("myCollection").Find(nil).All(&results)
    return results
}

Having said that it looks like mgo doesn't actually expose control over the underlying connections (see the comment from Gustavo Niemeyer who maintains the library). A session pretty much equates to a connection, but even if you call Close() on a session mgo keeps the connection alive. From reading around it seems that Clone() might be the way to go, as it reuses the underlying socket, this will avoid the 3 way handshake of creating a new socket (see here for more discussion on the difference).

Also see this SO answer describing a standard pattern to handle sessions.

Aidan Ewen
  • 13,049
  • 8
  • 63
  • 88