Repository navigation
asyncio's Process.communicate() is unsafe to cancel #139373
Description
Bug report
Bug description:
Documentation on asyncio.subprocess.Process (link) says:
the communicate() and wait() methods don’t have a timeout parameter: use the wait_for() function;
but in the meantime, cancelling this Future may result in stdout/stderr loss, which might be unclear from the documentation and not very user-friendly if the user ever retries communicating with the process again to collect the whole output.
So in general, using the synchronous API I would do it like this:
import sys
import subprocess
process = subprocess.Popen(
args=[
sys.executable,
'-c',
'import time; print("first", flush=True); time.sleep(3); print("second")',
],
stdout=subprocess.PIPE)
try:
[stdout, _] = process.communicate(timeout=1)
except subprocess.TimeoutExpired:
process.kill()
[stdout, _] = process.communicate()
print(f"Timed out, {stdout=}")output:
Timed out, stdout=b'first\r\n'
which gives me the full process output. I think this is a common approach to using the subprocess API.
Doing the same in async will mostly lead to output loss:
import asyncio
import sys
async def main():
process = await asyncio.create_subprocess_exec(
sys.executable,
'-c',
'import time; print("first", flush=True); time.sleep(3); print("second")',
stdout=asyncio.subprocess.PIPE,
)
try:
[stdout, _] = await asyncio.wait_for(process.communicate(), timeout=1)
except asyncio.TimeoutError:
process.kill()
[stdout, _] = await process.communicate()
print(f"Timed out, {stdout=}")
if __name__ == '__main__':
asyncio.run(main())output:
Timed out, stdout=b''
I researched the source code a bit and don't see a clear solution for this other than implementing a timeout parameter for Process.communicate(). Cancellation may occur right between those two calls:
cpython/Lib/asyncio/subprocess.py
Lines 201 to 202 in 93ac352
| stdin, stdout, stderr = await tasks.gather(stdin, stdout, stderr) | |
| await self.wait() |
so the transport buffer will already be drained, but the process is not yet .wait()ed for.
PS I don't know if I chose the issue type correctly, since it might be a bug, a documentation issue, or an enhancement proposal.
CPython versions tested on:
3.11, 3.13
Operating systems tested on:
No response
Linked PRs
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
Zheaoli commentedon Sep 27, 2025
confirmed on main branch
Zheaoli commentedon Sep 27, 2025
The bug has been confirmed, I write a script about the cancel operation
Zheaoli commentedon Sep 27, 2025
I think maybe we need add a timeout argument just like what we do in subporcess lib.
cc @1st1 @asvetlov
kumaraditya303 commentedon Oct 9, 2025
This is more or less a duplicate of #103847
asyncio's subprocess implementation is not cancellation safe, I plan to rewrite it in 3.15 to fix that, closing in favor of other issue.
uhx commentedon Feb 3, 2026
@kumaraditya303 I read #103847 and I'm not sure if these are the same issues. I'm highlighting the
.communicate()cancellation issue, notasyncio.create_subprocess_exec()cancellation. There is no hangs in my issue, only loss of stdout/stderr.TLDR: we lose all stdout/stderr when we cancel
.communicate(), and there is no way to collect it to investigate the child process issue.Please look once again and confirm that it is the same or different problem.
uhx commentedon Feb 5, 2026
OK, maybe I'm too unfamiliar with asyncio. Instead of cancelling
Process.communicate()we could just cancelProcess.wait(), and use separate task for reading IO. I'll save this here in case someone else faces the same problem:This way, we avoid stdout/stderr loss.
pythongh-139373: Fix asyncio Process.communicate() losing output when…
kumaraditya303 commentedon Jul 20, 2026
Created #154223 to fix this.
gh-139373: Fix asyncio Process.communicate() losing output when cance…