Shell:清理由于共享stdout/stderr而挂起的泄漏后台进程



对于测试执行引擎,我需要在临时容器/vm中的(远程)shell上运行基本上任意的命令。有时,这些泄漏后台进程,然后导致整个命令挂起。这可以归结为这个简单的命令:

$ sh -c 'sleep 30 & echo payload'
payload
$

这里,后台的sleep 30扮演了泄漏进程的角色(实际上类似于dbus-daemon),而echo是我想要运行的实际内容。在这里,sleep 30 & echo payload应该被认为是一个原子的不透明示例命令。

上面的命令很好,并立即返回,因为shell和sleep的stdout/stderr都是PTY。但是,当将命令的输出捕获到管道/文件时(毕竟,测试运行器希望将所有内容保存到日志中),整个命令将挂起:

$ sh -c 'sleep 30 & echo payload' | cat
payload
# ... does not return to the shell (until the sleep finishes)

现在,这可以通过一些相当复杂的shell魔法来修复,它确定/proc/$$/fd/{1,2}的stdout/err的fd,迭代ls /proc/[0-9]*/fd/*并杀死所有具有相同stdout/stderr的进程。但是这涉及到很多脆弱的shell代码和昂贵的shell字符串比较。

是否有一种更优雅、更简单的方式来清理这些泄露的后台进程?setsid没有帮助:

$ sh -c 'setsid -w sh -c "sleep 30 & echo payload"' | cat
payload
# hangs...

请注意,进程组/会话并批量杀死它们是不够的,因为泄漏的进程(如dbus-daemon)经常会自己关闭。

注:我只能假设在这些环境中使用POSIX shell或bash;没有Python, Perl等

提前感谢!

我们在Launchpad中进行并行测试时遇到了这个问题。我们当时拥有的最简单的解决方案——它工作得很好——只是确保没有进程共享stdout/stdin/stderr(除了那些你实际上想要挂起的进程,如果它们还没有完成——例如测试工作者本身)。

嗯,重读了这篇文章后,我无法给你你想要的解决方案(使用systemd杀死它们)。我们想到的是简单地忽略进程,但在我们等待的单个进程完成时可靠地不挂起。请注意,这与关闭管道明显不同。

另一个不完美但有用的选择是使用prctl(2)和PR_SET_CHILD_SUBREAPER成为本地死神。这将允许您成为所有进程的父进程,否则这些进程将归属于init。通过这种安排,您可以尝试杀死所有将您设置为ppid的进程。这很糟糕,但这是最接近使用cgroups的方法了。

但是请注意,除非您以root用户身份运行这个helper,否则您会发现实际测试可能会产生一些隐藏的东西,并且不会被杀死。

script -qfc代替sh -c

相关内容

  • 没有找到相关文章

最新更新