Search code examples
recursiongodereferencepanic

With recursive functions in Go, if the inner function returns, does the outer function continue execution normally?


Okay, so I have this piece of code

func registerDomain(domainName string, n int) bool {
    //building the request here 
    resp, errr := client.Do(r)
    if errr != nil { 
        if n == 1 {
            return false
        }
        registerDomain(domainName, n-1)
    }

    bodyBytes, err2 := ioutil.ReadAll(resp.Body)
    if err2 == nil {
        resp.Body.Close()
        //handle bodyBytes
        //if the response is how it should be return true, if it's not call the function again with n-1
    } else { //if there is an error reading the response
        resp.Body.Close()
        if n == 1 {
            return false
        }
        registerDomain(domainName, n-1)
    }
    return false //it should never reach this line
}

Explanation:

I call the function with an argument n (let's say 5) that represents the number of times the function will retry if something is not right. Every time something is wrong I make a recursive call with n-1 so when it reaches n=1 it gives up and returns false. This code works fine in practice, it does what it's supposed to do, sometimes when the response is not correct it calls itself recursively and it works the second (or 3rd, 4th..) time around. When the problem isn't fixed before n=1 it returns false.

The problem:

This is a part of a big code that is supposed to run for about 17 hours and it panics when it tries to read from the body sometimes on this line:

bodyBytes, err2 := ioutil.ReadAll(resp.Body)

it says panic: runtime error: invalid memory address or nil pointer dereference. Now I know that probably means that it's trying to read from a nonexistent resp.Body but the Go documentation clearly states that When err is nil, resp always contains a non-nil resp.Body.

So, in my head it's probably something with the recursive calls. The only thing that makes sense to me is this scenario: lets say that the errr is not nil (this means that the resp.Body doesn't exist), so it goes inside of the if errr != nil and because n!=1, it will call itself again with n=4. Let's say this time everything is as it should be and the second function returns true to the first one BUT the first one continues with execution and tries to read from the resp.Body which doesn't exist. That causes panic and here we are...

So, what I need is someone that knows exactly how recursive functions work and if it's not that, can I somehow check the existence of resp.Body before I read form it, or something that will help.

Thanks anyways! :)

UPDATE: You were all right, my code doesn't panic anymore and neither do I. Thank you very much! (I'm not sure if this is where the update goes)


Solution

  • Inside the registerDomain() function there are 2 places where you call itself.

    If the client.Do() failes (errr != nil), you will call registerDomain() again. Sometime it will return, and when it does, your code execution will continue, trying to read from resp.Body but that most likely is nil because errr != nil.

    Here is how you should handle it:

    Whenever you make a recursive call, you should not let code continue when that call returns, but return the value it returned, something like this:

    return registerDomain(domainName, n-1)
    

    Alternative

    The problem you're trying to solve can be solved without recursion using a for statement. The for variant will even be clearer and more simple.

    Modify your registerDomain() function to return false at each point it calls itself, and use this loop to retry 5 times:

    var result bool
    for i := 0; i < 5; i++ {
        result = registerDomain(domainName) // You don't need to pass n anymore
        if result {
            break
        }
    }
    
    if result {
        fmt.Println("Domain registered successfully!")
    } else {
        fmt.Println("Failed to register domain!")
    }