Golang无法从惊慌的mgo.DialWithInfo中恢复

I tried to unit test connection to MongoDB using mgo.DialWithInfo function (in the failure case). mgo.DialWithInfo does not return an error but panics instead.

I tried to add recovery logic to recover from the panic without success.

My questions are:

  1. Why does mgo.DialWithInfo not return an error but panics?
  2. Why does my recovery not work?

Code:

Function

func Connect(mongoDBDialInfo *mgo.DialInfo) error {
    log.Infof("connect to MongoDB with %v", mongoDBDialInfo)
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()

    mongoSession, err := mgo.DialWithInfo(mongoDBDialInfo)
    if err != nil {
        log.Errorf("error, cannot connect to MongoDB, %v", err)
        msg := fmt.Sprintf("fail, cannot connect to MongoDB, %v", mongoDBDialInfo)
        sf := "main.main"
        errM := err.Error()
        util.LogError(msg, sf, errM)
        return err
    }

    DBSession = mongoSession
    return err
}

Unit test

func TestConnect_Fail(t *testing.T) {
    cf, err := config.GetConfigInstance()
    if err != nil {
        t.Errorf("expected, cannot connect to MongoDB but found, %v", err)
    }
    mongoDBDialInfo := &mgo.DialInfo{
        Addrs:    []string{cf.Config.MongoDB.Host + "ss"},
        Timeout:  time.Duration(cf.Config.MongoDB.Timeout) * time.Second,
        Database: cf.Config.MongoDB.DBName,
        Username: cf.Config.MongoDB.Username,
        Password: cf.Config.MongoDB.Password,
    }
    err = Connect(mongoDBDialInfo)
    if err == nil {
        t.Errorf("expected, cannot connect to MongoDB but can")
    }
}

Why does mgo.DialWithInfo not return error but panic?

I don't know. It could be the case that:

  • you are doing something unsupported, which causes the library call to panic
  • the library author chose to use panics to communicate non-recoverable error conditions
  • the library has an internal bug which causes the runtime to panic on encountering an unrecoverable situation (e.g. a nil pointer dereference)

or a combination thereof.

Your code doesn't look obviously broken, although I am not a user of the mgo library, and that doesn't mean to say there are no issues communicating with external resources, which cannot be diagnosed by looking at the code. Post the contents of the panic message, which may help us determine exactly where the problem lies.

Why recovery does not work?

It isn't clear from your question, but from your unit test and this comment, it appears recovery is working, but not as you intended because it is not returning an error with the panic's value.

The recover() call in the deferred statement will recover control of a panicking goroutine, return the value given when the panic was initiated, and resume the normal flow of execution. This is all recover will do. It won't do anything about errors unless you explicitly write code to do so. (And, for completeness, without a call to recover somewhere in a deferred function in your call stack, a panic will eventually bubble up and be fatal to your application.)

In this context, panic is working: the application will not exit fatally if a panic occurs during the execution of the Connect function. However, you eat the panic without doing anything about it or signalling to the caller that an error occurred during the Connect method.

To report this error, you should recover the panic and produce an error which can be returned from the Connect function. You can't return a value in the deferred function directly, but you can use a named return value to do so. Example code, skipping non-essential parts of the function body:

func Connect(mongoDBDialInfo *mgo.DialInfo) (err error) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)

            // Return error from Connect with panic's value
            err = fmt.Errorf("%v", r)
        }
    }()

    // rest of function body
}