我犯了一个新手错误,在代码库中引入了内存泄漏,因为我没有意识到即使不需要响应内容,也必须关闭 http.Response 返回的 body。我怀疑我不是唯一犯这个错误的人。如果提案被接受,我有一个差异。
http.Response
1cklez4t1#
尽管忽略响应对于Post请求来说是常见的,但对于Get请求来说应该是罕见的。因此,如果我们想要一个通用的解决方案,仅仅检测到响应被忽略(""正如你的标题所暗示的)对于Post请求是有帮助的。换句话说,Get请求的主要用例需要查看响应,因此检测""对绝大多数Get请求案例没有帮助。
如果一般情况(*响应是一个函数调用的返回值,且该响应的主体没有关闭)难以检测,那么将这个问题的范围缩小到Post(和Do)可能会更好。
这里有一个特别糟糕的不关闭主体的例子:Azure/azure-sdk-for-go@475dde0#diff-08eb04d438c2ee00895ac528dc7790b5L112。*http.Response是从函数中返回的。重试循环正在覆盖resp,导致它泄漏而没有关闭。但是解决这个特定的例子是有一定难度的——仍然可以从检测常规情况中获得巨大的价值。
*http.Response
resp
我很惊讶这个问题之前没有被讨论过;显然是有用的。尽管在文档中很清楚,但我看到新手和老手都遭受了这个问题。我在github上没有看到讨论——也许其他地方有讨论。与此相关。cc @bradfitz
rkue9o1l2#
还有一个更一般的问题,那就是一旦构造了 io.Closer ,就不应该再使用它。也就是说,永远不要在上面调用 Close() 。如果我们只检查 _, err := http.Post(...) 模式,在我看来,这个检查可能不会非常强大。如果我们首先构造 http.Request ,然后在其上调用 Do 呢?如果我们不忽略 http.Response ,但在某些情况下不关闭它呢?上面链接的 Azure bug 具有这两个属性。另一方面,如果我们实现更通用的检查,我猜想避免误报会非常棘手。例如,一些 io.Closer 的实现可能是无操作的,可以忽略。
io.Closer
Close()
_, err := http.Post(...)
http.Request
Do
46scxncf3#
@alandonovan
ijxebb2r4#
例如,io.Closer的一些实现可能是无操作的,可以忽略。在标准库中甚至有一个这样的辅助函数(https://golang.org/pkg/io/ioutil/#NopCloser),用于将简单的io.Reader转换为具有无操作Close()方法的ReadCloser。
gzjq41n45#
我们已经在vet中有一个函数结果不容忽视的列表;听起来像是http.Post应该在那个列表上。解决这类问题的一般分析被称为类型-状态验证。例如,它会检查由Open返回的文件上的所有Read和Write调用是否在Close之前发生,以及所有控制流路径上是否都发生了Close调用(较宽松的检查版本将确保至少某些路径上调用了Close)。这种分析的质量取决于它如何精确地模拟数据、控制和调用图。通常,在更复杂的情况中,需要大量的分析能力(以及CPU/RAM)才能做好工作,但我们可以用更简单的启发式方法来捕捉愚蠢的错误。一个可以遵循的例子可能是vet中的lostcancel检查,它确保在创建上下文的取消函数后,该函数在所有控制流路径上都被“使用”。这个检查或许可以推广以解决其他此类问题。
lawou6xi6#
如果我们将 -closer 作为 vet 标志,当忽略本地的 io.Closer 时会报错,那么我们就可以从 stdlib 中白名单 ioutil.nopCloser。这是默认检查的一个开始。
-closer
ioutil.nopCloser
在 vet 中,我们已经有了一个不应被忽略结果的函数列表;听起来 http.Post 应该在这个列表里。
你能分享一下这个位置吗?
6条答案
按热度按时间1cklez4t1#
尽管忽略响应对于Post请求来说是常见的,但对于Get请求来说应该是罕见的。因此,如果我们想要一个通用的解决方案,仅仅检测到响应被忽略(""正如你的标题所暗示的)对于Post请求是有帮助的。换句话说,Get请求的主要用例需要查看响应,因此检测""对绝大多数Get请求案例没有帮助。
如果一般情况(*响应是一个函数调用的返回值,且该响应的主体没有关闭)难以检测,那么将这个问题的范围缩小到Post(和Do)可能会更好。
这里有一个特别糟糕的不关闭主体的例子:Azure/azure-sdk-for-go@475dde0#diff-08eb04d438c2ee00895ac528dc7790b5L112。
*http.Response
是从函数中返回的。重试循环正在覆盖resp
,导致它泄漏而没有关闭。但是解决这个特定的例子是有一定难度的——仍然可以从检测常规情况中获得巨大的价值。我很惊讶这个问题之前没有被讨论过;显然是有用的。尽管在文档中很清楚,但我看到新手和老手都遭受了这个问题。我在github上没有看到讨论——也许其他地方有讨论。与此相关。
cc @bradfitz
rkue9o1l2#
还有一个更一般的问题,那就是一旦构造了
io.Closer
,就不应该再使用它。也就是说,永远不要在上面调用Close()
。如果我们只检查
_, err := http.Post(...)
模式,在我看来,这个检查可能不会非常强大。如果我们首先构造http.Request
,然后在其上调用Do
呢?如果我们不忽略http.Response
,但在某些情况下不关闭它呢?上面链接的 Azure bug 具有这两个属性。另一方面,如果我们实现更通用的检查,我猜想避免误报会非常棘手。例如,一些
io.Closer
的实现可能是无操作的,可以忽略。46scxncf3#
@alandonovan
ijxebb2r4#
例如,io.Closer的一些实现可能是无操作的,可以忽略。
在标准库中甚至有一个这样的辅助函数(https://golang.org/pkg/io/ioutil/#NopCloser),用于将简单的io.Reader转换为具有无操作Close()方法的ReadCloser。
gzjq41n45#
我们已经在vet中有一个函数结果不容忽视的列表;听起来像是http.Post应该在那个列表上。
解决这类问题的一般分析被称为类型-状态验证。例如,它会检查由Open返回的文件上的所有Read和Write调用是否在Close之前发生,以及所有控制流路径上是否都发生了Close调用(较宽松的检查版本将确保至少某些路径上调用了Close)。这种分析的质量取决于它如何精确地模拟数据、控制和调用图。通常,在更复杂的情况中,需要大量的分析能力(以及CPU/RAM)才能做好工作,但我们可以用更简单的启发式方法来捕捉愚蠢的错误。一个可以遵循的例子可能是vet中的lostcancel检查,它确保在创建上下文的取消函数后,该函数在所有控制流路径上都被“使用”。这个检查或许可以推广以解决其他此类问题。
lawou6xi6#
如果我们将
-closer
作为 vet 标志,当忽略本地的io.Closer
时会报错,那么我们就可以从 stdlib 中白名单ioutil.nopCloser
。这是默认检查的一个开始。在 vet 中,我们已经有了一个不应被忽略结果的函数列表;听起来 http.Post 应该在这个列表里。
你能分享一下这个位置吗?