对于测试执行引擎,我需要在临时容器/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