【问题标题】:Too many file descriptors while capturing stdout捕获标准输出时文件描述符过多
【发布时间】:2021-12-18 05:04:30
【问题描述】:

我的 Go 项目中有数千个测试,为了确保 CLI 的正确输出,我编写了一个函数来捕获标准输出:

// captureStdout captures everything written to the terminal and returns it as a string.
func captureStdout(f func(w io.Writer)) string {
    originalStdout := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w

    f(w)

    _ = w.Close()
    out, _ := ioutil.ReadAll(r)
    os.Stdout = originalStdout
    _ = r.Close()

    return string(out)
}
// No error handling for smaller code snippet

这个函数很好用,但是当我运行它几千次时,它会随机失败。

我发现,这与 Linux 上的文件描述符限制有关,即 1024。

Linux 系统将任何一个进程可以打开的文件描述符的数量限制为每个进程 1024 个。 (这种情况在 Solaris 机器、x86、x64 或 SPARC 上不是问题)。在目录服务器超过每个进程 1024 的文件描述符限制后,任何新进程和工作线程都将被阻止。 - Source

在 Windows 上,测试完美无缺。

如何在 Go for Linux 机器中绕过这个限制?

【问题讨论】:

  • “我将如何绕过这个限制......?”,你设置了 ulimit,但这是一个 XY 问题,首先要弄清楚为什么你的 FD 用完了。您是否要同时运行数千次?你有阻塞读写,你确定你没有让整个函数在测试中被阻塞并且无法关闭管道吗?
  • f(w) (我假设它正在写一些东西)如果没有读者,则可能会阻塞,并且在该函数返回之前您不会阅读。一般来说,如果你用管道替换标准输出,你必须同时阅读。你确定这些电话能正常返回吗?
  • 鉴于f 采用io.Writer 参数,你能避免使用var buf bytes.Buffer; f(&buf); return buf.String() 的所有文件描述符吗?
  • 调试泄漏的 FD 可能很困难,一个好的起点是trace 你的程序,输出可以用go tool trace 检查,并告诉你到底发生了什么。此外,在 linux 上,您可以通过列出 /proc/{pid}/fd 目录来检查正在运行的进程的打开文件描述符,尽管您只能这样做,您可以获得正在运行的测试的 PID 并在测试完成之前列出目录。所以如果你走这条路,你可能需要在测试中添加一些延迟
  • 添加到穴居人的评论:从过程本身,您可以使用/proc/self;例如:打开/proc/self/fd 并列出该目录中的元素。

标签: linux macos go file-descriptor


【解决方案1】:

没有足够的文件描述符通常是由文件描述符泄漏引起的。例如,在不再需要打开的文件或网络套接字后忘记调用 .Close。

如果您想同时打开超过 1024 个文件/网络连接,请有意。您可以使用ulimit 命令(和适当的标志)来增加此限制。此命令将增加您当前 shell 会话的限制,如果涉及单元测试,这似乎是合适的。

【讨论】:

  • 嗨,遗憾的是 ulimit 解决方法对我不起作用。我将它设置为一百万,它仍然崩溃。运行 Go 测试时临时 ulimit 是否也有效?还是 Go 测试获取实际的 ulimit?
  • 更改限制时,您必须同时更改软限制和硬限制。 go test 进程应该继承该设置,因为它是 shell 的子进程。您可以使用 ulimit -a 检查当前限制,以确保它们确实发生了变化(ulimit -a -H 查看硬限制)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-05
  • 1970-01-01
  • 1970-01-01
  • 2019-02-26
  • 1970-01-01
  • 2012-02-23
  • 2019-10-16
相关资源
最近更新 更多