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

Posted

技术标签:

【中文标题】Python 子进程返回错误的退出代码【英文标题】:Python subprocess returns wrong exit code 【发布时间】:2018-02-21 16:21:25 【问题描述】:

我编写了一个脚本来启动多个并行运行的进程(简单的单元测试)。它将一次执行Nnum_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")

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

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")

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

为什么会这样?

【问题讨论】:

考虑使用multiprocessing,而不是管理您自己的并行进程。 @ndmeiri 什么比赛条件?只有主线程读取/写入errors 变量。 不是竞争条件; os.wait() 破坏了 poll() 的返回码。 @NathanVērzemnieks 好眼光! 【参考方案1】:

问题在于os.wait()。它不仅等待子进程退出:它还返回该子进程的 pid 和“退出状态指示”,正如the documentation 所说。这需要等到子进程终止;但是一旦孩子终止,它的返回码就不再可用于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))

输出

njv@organon:~/tmp$ python false_runner.py
From os.wait: 256
From Popen object before poll: None
From Popen object after poll: 0

The source code for _internal_poll, called by Popen.poll,清楚地表明这里发生了什么:当Popen 尝试在其子进程的 pid 上调用 _waitpid 时,它会得到 ChildProcessError: [Errno 10] No child processes,并为自己分配一个 0 的 returncode,因为没有办法此时确定子进程的返回码。

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

至于修复:我可能只是将os.wait() 替换为睡眠。

【讨论】:

以上是关于Python 子进程返回错误的退出代码的主要内容,如果未能解决你的问题,请参考以下文章

来自 apache 的子进程返回退出代码 -6 而不是 stdout 或 stderr

子进程 check_output 返回非零退出状态 1

使用 Python 子进程通信方法时如何获取退出代码?

python多处理子进程未正常退出

确保子进程在退出 Python 程序时死亡

子进程 c 的返回值