【发布时间】: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