![父进程阻止尝试读取僵尸子进程的输出](https://linux22.com/image/147649/%E7%88%B6%E8%BF%9B%E7%A8%8B%E9%98%BB%E6%AD%A2%E5%B0%9D%E8%AF%95%E8%AF%BB%E5%8F%96%E5%83%B5%E5%B0%B8%E5%AD%90%E8%BF%9B%E7%A8%8B%E7%9A%84%E8%BE%93%E5%87%BA.png)
设置:我有一个 python (3.6) 脚本(称之为“操作员”)执行 bash 脚本(称之为“柱塞") 以标准子进程方式,从子进程收集和记录标准输出。柱塞脚本本身很简单,它调用其他脚本/程序来执行三件中等复杂的事情:a)关闭一堆守护进程,b)做一些内务处理,c)启动一些新的守护进程然后退出。系统本身并没有什么特别奇怪的地方:普通的旧 CentOS 以标准 rpm 运行。
问题:当。。。的时候柱塞脚本只运行 a 和 b 部分,一切都按预期运行 -柱塞(没有 c)运行至完成,并且操作员收集所有输出并继续完成其余工作。然而,当我包括步骤 c 时,柱塞运行正确,操作员收集所有输出(如果我一次读一点),但从未注意到柱塞已退出并且从未完成读取输出,因此控制永远不会传递回操作员脚本。
简单的例子:
return subprocess.check_output("plunger") # doesn't complete with the real plunger script
观察结果:
- 跑步柱塞在交互式 shell 中始终正常工作
- 这柱塞进程做了它应该做的一切并退出
- 运行 ps 显示柱塞bash 进程作为僵尸(“plunger”)
- 使用 Popen 并逐行读取表示所有预期的行均已输出并以换行符正确终止
- 使用 Popen 并使用 poll() 检查进程状态仅发出 None
- 它的行为就像子进程没有结束或者还有字节需要读取,即使它已经退出并且唯一的 PIPE 流是 stdout...并从 stdout 块读取。
推测: 我最好的猜测是,最后一步产生的新后台(守护进程)进程以某种方式继承并保持打开标准输出流,这样即使执行的柱塞脚本输出并退出,某些未知进程仍会继续保留它,因此不会不允许操作员脚本继续。
问题: 我的猜想有可能(或有可能)吗?如果没有,我还能寻找什么?如果是这样我该如何保护操作员和/或柱塞来自下游滥用我的流?
后记: 我可怕的 hacky fugly 解决方法是柱塞在完成其工作后呼应一条独特的线条,当操作员看见了就杀柱塞过程。光是打字我就觉得很肮脏。
编辑及结论: 我的猜想是正确的,问题与 python 或者实际上与 bash 无关,更多地与 fork 的工作方式有关。这是一个最小的例子:
$ (date; (sleep 5 &); date); date
Wed Feb 6 12:46:27 EST 2019
Wed Feb 6 12:46:27 EST 2019
Wed Feb 6 12:46:27 EST 2019
$ (date; (sleep 5 &); date) | cat; date
Wed Feb 6 12:46:51 EST 2019
Wed Feb 6 12:46:51 EST 2019
Wed Feb 6 12:46:56 EST 2019 # <- five second gap!
$ (date; ((sleep 5 &)>/dev/null); date) | cat; date
Wed Feb 6 12:47:13 EST 2019
Wed Feb 6 12:47:13 EST 2019
Wed Feb 6 12:47:13 EST 2019
# this works too
$ (date; (sleep 5 >/dev/null &); date) | cat; date
Wed Feb 6 13:11:24 EST 2019
Wed Feb 6 13:11:24 EST 2019
Wed Feb 6 13:11:24 EST 2019
我猜想没有办法真正防范这种情况。真正的罪魁祸首是 C 调用的脚本来启动守护进程,需要确保将输出重定向到其他内容,这样它们就不会保持管道打开。
答案1
我已经在问题(现已编辑)的最后部分回答了这个问题。
简而言之,任何启动后台进程的东西,无论嵌套多深,都可以捕获您的输出流并阻止您彻底关闭您的子进程。我提出的解决方案是:(a)将守护程序启动的输出重定向到 /dev/null 或(b)将守护程序启动的输出重定向到文件,并且(如果您关心的话)单独监视该文件,直到您的直系孩子退出。