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

Python 对asyncio子进程调用.terminate()会引发ProcessLookupError,python,subprocess,python-asyncio,Python,Subprocess,Python Asyncio,我有一些使用子进程的非异步代码 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 #

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

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())
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
文件“/usr/lib64/python3.8/asyncio/runners.py”,第43行,运行中
返回循环。运行直到完成(主)
文件“/usr/lib64/python3.8/asyncio/base_events.py”,第616行,运行直到完成
返回future.result()
文件“”,第6行,主
文件“/usr/lib64/python3.8/asyncio/subprocess.py”,第141行,终止
self.\u传输终止()
文件“/usr/lib64/python3.8/asyncio/base_subprocess.py”,第149行,终止
自我检查程序()
文件“/usr/lib64/python3.8/asyncio/base_subprocess.py”,第142行,在检查过程中
提升ProcessLookupError()
ProcessLookupError
这段代码中的错误是什么?我做错了什么

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

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

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

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

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

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

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

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

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

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
if p.returncode is None:
    p.terminate()