Python pytest中子进程写入文件数据丢失问题求助
首先,咱们来拆解你的问题核心:为什么sar子进程生成的cpu_usage.log在测试正常结束时是空的,但Ctrl-C中断时能正常写入?结合你的代码和补充信息,主要有这几个关键点:
1. 冗余的stdout=subprocess.PIPE与shell重定向冲突
你在启动sar的命令里已经用了shell重定向> cpu_usage.log,但同时给subprocess.Popen传了stdout=subprocess.PIPE。这其实是矛盾且多余的:
- shell会把
sar的输出直接重定向到文件,但stdout=PIPE会让父进程捕获shell本身的标准输出——而shell在执行这个命令时没有自己的输出,所以这个PIPE完全没用。 - 更关键的是,这种设置可能会改变shell或
sar的输出缓冲策略,从行缓冲切换成全缓冲,导致缓冲区内容在进程被SIGTERM终止时无法自动刷新到文件。
2. 缓冲未刷新导致内容丢失
当你用os.killpg发送SIGTERM终止进程组时,sar或shell进程会直接退出,而如果它们使用的是全缓冲(而非终端环境下的行缓冲),缓冲区里的内容不会被强制写入文件。但Ctrl-C发送的是SIGINT,这种信号会触发进程的异常退出逻辑,通常会自动刷新所有输出缓冲区,所以你能看到文件内容。
3. 新增的stdin=subprocess.PIPE的间接影响
你提到在setup()里新增了一个带stdin=PIPE的子进程,这可能改变了父进程的文件描述符状态,间接导致sar子进程的缓冲策略发生变化——比如父进程打开的管道数量变化,让子进程默认使用全缓冲而非行缓冲,这才是“之前正常,现在出问题”的触发点。
具体解决方案
方案一:移除多余的stdout=subprocess.PIPE
既然已经用shell重定向处理输出,就不需要父进程捕获stdout,修改后的代码:
cpu_process = subprocess.Popen("sar -u 1 > cpu_usage.log", shell=True, preexec_fn=os.setsid)
这样能让shell正常处理sar的输出缓冲,终止时更大概率自动刷新。
方案二:避免shell,直接让subprocess写入文件
更推荐的做法是不用shell=True,而是直接让sar的输出写入文件,这样完全避免shell的介入,缓冲行为更可控:
with open("cpu_usage.log", mode='w') as f: cpu_process = subprocess.Popen(["sar", "-u", "1"], stdout=f, preexec_fn=os.setsid)
这种方式不仅更安全(避免shell注入风险),还能确保输出直接写入文件,减少缓冲带来的问题。
方案三:先发送SIGINT触发缓冲刷新,再终止进程
如果必须保留现有shell重定向的写法,可以在终止进程前先发送SIGINT(模拟Ctrl-C的效果),让sar先刷新缓冲区,再发送SIGTERM确保进程终止:
import time import signal # 先发送SIGINT,让sar优雅退出并刷新缓冲区 os.killpg(os.getpgid(cpu_process.pid), signal.SIGINT) # 短暂等待,确保进程完成缓冲刷新 time.sleep(0.5) # 再发送SIGTERM彻底终止进程组 os.killpg(os.getpgid(cpu_process.pid), signal.SIGTERM)
额外小提示
- 你代码里的
f.close()是多余的,with语句会自动帮你关闭文件句柄,不用手动调用。 - 尽量避免
shell=True,除非你确实需要shell的特殊功能(比如管道、重定向),用列表形式的命令参数更安全可靠。
内容的提问来源于stack exchange,提问作者KansaiRobot

