Python子进程返回错误的退出代码

Jos*_*tiz 5 python subprocess exit-code

我写了一个脚本来启动一些并行运行的进程(简单单元测试).它会做N与工作num_workers同时并行处理.

我的第一个实现分批运行流程,num_workers似乎工作正常(我在false这里使用命令来测试行为)

import subprocess

errors = 0
num_workers = 10
N = 100
i = 0

while i < N:
    processes = []
    for j in range(i, min(i+num_workers, N)):
        p = subprocess.Popen(['false'])
        processes.append(p)

    [p.wait() for p in processes]
    exit_codes = [p.returncode for p in processes]

    errors += sum(int(e != 0) for e in exit_codes)
    i += num_workers

print(f"There were {errors}/{N} errors")
Run Code Online (Sandbox Code Playgroud)

但是,测试不会花费相同的时间,所以我有时候会等待慢速测试才能完成.因此,我重写了它,以便在完成任务时继续分配任务

import subprocess
import os


errors = 0
num_workers = 40
N = 100
assigned = 0
completed = 0
processes = set()

while completed < N:
    if assigned < N:
        p = subprocess.Popen(['false'])
        processes.add((assigned, p))
        assigned += 1
    if len(processes) >= num_workers or assigned == N:
        os.wait()

    for i, p in frozenset(processes):
        if p.poll() is not None:
            completed += 1
            processes.remove((i, p))
            err = p.returncode
            print(i, err)
            if err != 0:
                errors += 1

print(f"There were {errors}/{N} errors")
Run Code Online (Sandbox Code Playgroud)

但是,这会在最后几个过程中产生错误的结果.例如,在上面的示例中,它产生98/100错误而不是100.我检查了这与并发性无关; 出于某种原因,2个最新作业返回,退出代码为0.

为什么会这样?

Nat*_*eks 1

问题在于os.wait(). 它不仅等待子进程退出:它还返回该子进程的 pid 和“退出状态指示”,如文档所述。这需要等待直到子进程终止;但是一旦子进程终止,它的返回代码就不再适用于poll. 这是重现该问题的简单测试:

false_runner.py

import os
import subprocess
p = subprocess.Popen(['false'], stderr=subprocess.DEVNULL)
pid, retcode = os.wait()
print("From os.wait: {}".format(retcode))
print("From popen object before poll: {}".format(p.returncode))
p.poll()
print("From popen object after poll: {}".format(p.returncode))
Run Code Online (Sandbox Code Playgroud)

输出

njv@organon:~/tmp$ python false_runner.py
From os.wait: 256
From Popen object before poll: None
From Popen object after poll: 0
Run Code Online (Sandbox Code Playgroud)

_internal_poll由 调用的 的源代码Popen.poll清楚地表明了这里发生的情况:当Popen尝试调用_waitpid其子进程的 pid 时,它获取ChildProcessError: [Errno 10] No child processes,并为自己分配returncode0,因为此时无法确定子进程的返回码。

这种情况仅在示例中的最后几个子流程中发生的原因是因为os.wait仅被调用该or assigned == N案例,并且仅调用一次或两次,因为您的子流程非常快。如果你放慢一点速度,你会得到更多的随机行为。

至于解决办法:我可能会os.wait()用睡眠来代替。