对asyncio子进程调用.terminate()会引发ProcessLookupError

2024-10-01 13:31:24 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一些使用子进程的非异步代码

import subprocess
import signal

p = subprocess.Popen(['/bin/true'], stdout=subprocess.PIPE)

# ... do something else here ...

# The process may or may not have finished yet.
# For the sake of this test, let us ensure a finish here
# by waiting for EOF on a pipe.
p.stdout.read()

p.terminate()

我尝试将其迁移到asyncio。但是,.terminate()调用会引发ProcessLookupError

import asyncio
import asyncio.subprocess
import signal

async def main():
    p = await asyncio.create_subprocess_exec('/bin/true',
        stdout=asyncio.subprocess.PIPE)
    # ... do something else here ...
    # for the sake of this test, ensure a finish here
    await p.stdout.read()
    p.terminate()

asyncio.run(main())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.8/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib64/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "<stdin>", line 6, in main
  File "/usr/lib64/python3.8/asyncio/subprocess.py", line 141, in terminate
    self._transport.terminate()
  File "/usr/lib64/python3.8/asyncio/base_subprocess.py", line 149, in terminate
    self._check_proc()
  File "/usr/lib64/python3.8/asyncio/base_subprocess.py", line 142, in _check_proc
    raise ProcessLookupError()
ProcessLookupError

这段代码中的错误是什么?我做错了什么

我在以下版本上进行了测试:

  • python39-3.9.0-1.fc32.x86_64
  • python3-3.8.5-5.fc32.x86_64

Tags: runinpyimportasyncioheremainusr
1条回答
网友
1楼 · 发布于 2024-10-01 13:31:24

解决方案:在调用.terminate()之前,使用p.returncode检查进程是否已返回。这同样适用于调用.kill().send_signal()

if p.returncode is None:
    p.terminate()

此代码是安全的。[*]无法在检查和.terminate()调用之间“收获”进程。只有在异步函数等待(await语句)时,才能获取进程

我撒谎了,这不安全。看看ThreadedChildWatcher,Unix进程可能会立即收获成果。这看起来像是一个非常恼人的比赛条件

讨论

在非异步subprocess模块中,调用.wait()是获取进程并设置.returncode的方法。如果尚未调用.wait(),将不会设置.returncode。如果UNIX进程退出但尚未收获,它将继续作为“僵尸”存在

asyncio中,事件循环获取进程并设置.returncode。这可能发生在函数中的任何await语句中。目前的文件没有提到这一点。获取Unix进程意味着它不再存在。没有什么可以发送信号的

理论上,可以更改asyncio以允许问题中的代码。但是,存在向后兼容性问题。到目前为止,我怀疑一些程序依赖于.returncode在没有.wait()的情况下/在.wait()之前进行设置,尽管它没有被记录在案。为了设置.returncode,必须收获Unix进程

最向后兼容的更改可能是asyncio自己进行检查。这对使用p.pid调用os.kill()的代码没有帮助。这样的代码不太可能得到支持。(首先,除非您删除或降级了FastChildWatcher,否则无法使用可移植Unix系统调用支持它)

相关问题 更多 >