gh-103847: fix cancellation safety of asyncio.create_subprocess_exec (#140805)

This commit is contained in:
Kumar Aditya 2025-11-12 10:47:38 +05:30 committed by GitHub
parent fbebca289d
commit ef474cfafb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 51 additions and 1 deletions

View file

@ -26,6 +26,7 @@ def __init__(self, loop, protocol, args, shell,
self._pending_calls = collections.deque()
self._pipes = {}
self._finished = False
self._pipes_connected = False
if stdin == subprocess.PIPE:
self._pipes[0] = None
@ -213,6 +214,7 @@ async def _connect_pipes(self, waiter):
else:
if waiter is not None and not waiter.cancelled():
waiter.set_result(None)
self._pipes_connected = True
def _call(self, cb, *data):
if self._pending_calls is not None:
@ -256,6 +258,15 @@ def _try_finish(self):
assert not self._finished
if self._returncode is None:
return
if not self._pipes_connected:
# self._pipes_connected can be False if not all pipes were connected
# because either the process failed to start or the self._connect_pipes task
# got cancelled. In this broken state we consider all pipes disconnected and
# to avoid hanging forever in self._wait as otherwise _exit_waiters
# would never be woken up, we wake them up here.
for waiter in self._exit_waiters:
if not waiter.cancelled():
waiter.set_result(self._returncode)
if all(p is not None and p.disconnected
for p in self._pipes.values()):
self._finished = True