英文:
With recursive functions in Go, if the inner function returns, does the outer function continue execution normally?
问题
好的,我来为你翻译这段代码。
func registerDomain(domainName string, n int) bool {
// 在这里构建请求
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()
// 处理 bodyBytes
// 如果响应是正确的,返回 true;如果不正确,使用 n-1 再次调用该函数
} else { // 如果读取响应时出错
resp.Body.Close()
if n == 1 {
return false
}
registerDomain(domainName, n-1)
}
return false // 不应该执行到这一行
}
解释:
我使用一个参数 n(假设为 5)调用该函数,该参数表示函数在出现问题时重试的次数。每当出现问题时,我使用 n-1 进行递归调用,当 n=1 时放弃并返回 false。这段代码在实践中运行良好,它能够完成预期的工作,有时当响应不正确时,它会递归调用自身,第二次(或第三次、第四次...)运行时会成功。当在 n=1 之前问题没有解决时,它返回 false。
问题:
这是一个大型代码的一部分,预计运行时间约为17小时,但有时在以下代码行尝试从 body 中读取时会发生 panic:
bodyBytes, err2 := ioutil.ReadAll(resp.Body)
它显示 panic: runtime error: invalid memory address or nil pointer dereference。现在我知道这可能意味着它试图从不存在的 resp.Body 中读取,但是 Go 文档 明确指出当 err 为 nil 时,resp 始终包含一个非 nil 的 resp.Body。
所以,在我看来,可能是递归调用的问题。唯一有意义的情况是:假设 errr 不为 nil(这意味着 resp.Body 不存在),所以它进入 if errr != nil,并且因为 n!=1,它将使用 n=4 再次调用自身。假设这次一切正常,第二个函数向第一个函数返回 true,但是第一个函数继续执行并尝试从 resp.Body 中读取,而它不存在。这导致 panic,就是现在的情况...
所以,我需要知道确切了解递归函数如何工作的人,如果不是这个问题,我是否可以在读取之前检查 resp.Body 的存在性,或者有其他有用的方法。
不管怎样,谢谢你们!
更新: 你们都是对的,我的代码不再发生 panic,我也不再发生 panic。非常感谢你们!(我不确定更新应该放在哪里)
英文:
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)
答案1
得分: 4
将
registerDomain(domainName, n-1)
改为
return registerDomain(domainName, n-1)
这样,外部函数将不会继续执行,并且不会从空的主体中读取。
英文:
Change
registerDomain(domainName, n-1)
to
return registerDomain(domainName, n-1)
This way outer function will not continue executing and will not read from nil body.
答案2
得分: 2
在registerDomain()
函数内部,有两个地方调用了它自身。
如果client.Do()
失败(errr != nil
),你将再次调用registerDomain()
。有时它会返回,当它返回时,你的代码执行将继续,尝试从resp.Body
读取,但很可能是nil
,因为errr != nil
。
以下是你应该处理它的方式:
每当你进行递归调用时,你不应该让代码继续执行,而是返回它返回的值,类似这样:
return registerDomain(domainName, n-1)
另一种选择
你试图解决的问题可以使用for
语句而不是递归来解决。使用for
循环的方式甚至更清晰、更简单。
修改你的registerDomain()
函数,在每个调用自身的点上返回false,并使用以下循环重试5次:
var result bool
for i := 0; i < 5; i++ {
result = registerDomain(domainName) // 你不再需要传递n了
if result {
break
}
}
if result {
fmt.Println("域名注册成功!")
} else {
fmt.Println("域名注册失败!")
}
英文:
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!")
}
答案3
得分: 1
你需要在第一次调用registerDomain后返回,否则当你调用registerDomain完成时,调用ioutil.ReadAll时会出现错误。
// 在这里构建请求
resp, errr := client.Do(r)
if errr != nil {
if n == 1 {
return false
}
return registerDomain(domainName, n-1)
}
英文:
You need to return after your first registerDomain call or you will panic on ioutil.ReadAll when your call to registerDomain finishes
//building the request here
resp, errr := client.Do(r)
if errr != nil {
if n == 1 {
return false
}
return registerDomain(domainName, n-1)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论