https://github.com/python/cpython/commit/5b25aab02db9a961b69c6ec93be09bb23d40c017
commit: 5b25aab02db9a961b69c6ec93be09bb23d40c017
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-03-10T10:11:31+05:30
summary:

[3.14] gh-145541: Fix `InvalidStateError` in 
`BaseSubprocessTransport._call_connection_lost()` (GH-145554) (#145676)

gh-145541: Fix `InvalidStateError` in 
`BaseSubprocessTransport._call_connection_lost()` (GH-145554)
(cherry picked from commit 1564e231aae7afad5b9b19a277d1efff2b840ad2)

Co-authored-by: Daan De Meyer <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-03-05-19-01-28.gh-issue-145551.gItPRl.rst
M Lib/asyncio/base_subprocess.py
M Lib/test/test_asyncio/test_subprocess.py

diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py
index 321a4e5d5d18fb..224b1883808a41 100644
--- a/Lib/asyncio/base_subprocess.py
+++ b/Lib/asyncio/base_subprocess.py
@@ -265,7 +265,7 @@ def _try_finish(self):
             # 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():
+                if not waiter.done():
                     waiter.set_result(self._returncode)
         if all(p is not None and p.disconnected
                for p in self._pipes.values()):
@@ -278,7 +278,7 @@ def _call_connection_lost(self, exc):
         finally:
             # wake up futures waiting for wait()
             for waiter in self._exit_waiters:
-                if not waiter.cancelled():
+                if not waiter.done():
                     waiter.set_result(self._returncode)
             self._exit_waiters = None
             self._loop = None
diff --git a/Lib/test/test_asyncio/test_subprocess.py 
b/Lib/test/test_asyncio/test_subprocess.py
index bf301740741ae7..c08eb7cf261568 100644
--- a/Lib/test/test_asyncio/test_subprocess.py
+++ b/Lib/test/test_asyncio/test_subprocess.py
@@ -111,6 +111,37 @@ def test_subprocess_repr(self):
         )
         transport.close()
 
+    def test_proc_exited_no_invalid_state_error_on_exit_waiters(self):
+        # gh-145541: when _connect_pipes hasn't completed (so
+        # _pipes_connected is False) and the process exits, _try_finish()
+        # sets the result on exit waiters. Then _call_connection_lost() must
+        # not call set_result() again on the same waiters.
+        self.loop.set_exception_handler(
+            lambda loop, context: self.fail(
+                f"unexpected exception: {context}")
+        )
+        waiter = self.loop.create_future()
+        transport, protocol = self.create_transport(waiter)
+
+        # Simulate a waiter registered via _wait() before the process exits.
+        exit_waiter = self.loop.create_future()
+        transport._exit_waiters.append(exit_waiter)
+
+        # _connect_pipes hasn't completed, so _pipes_connected is False.
+        self.assertFalse(transport._pipes_connected)
+
+        # Simulate process exit. _try_finish() will set the result on
+        # exit_waiter because _pipes_connected is False, and then schedule
+        # _call_connection_lost() because _pipes is empty (vacuously all
+        # disconnected). _call_connection_lost() must skip exit_waiter
+        # because it's already done.
+        transport._process_exited(6)
+        self.loop.run_until_complete(waiter)
+
+        self.assertEqual(exit_waiter.result(), 6)
+
+        transport.close()
+
 
 class SubprocessMixin:
 
diff --git 
a/Misc/NEWS.d/next/Library/2026-03-05-19-01-28.gh-issue-145551.gItPRl.rst 
b/Misc/NEWS.d/next/Library/2026-03-05-19-01-28.gh-issue-145551.gItPRl.rst
new file mode 100644
index 00000000000000..15b70d734ca3b9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-03-05-19-01-28.gh-issue-145551.gItPRl.rst
@@ -0,0 +1 @@
+Fix InvalidStateError when cancelling process created by 
:func:`asyncio.create_subprocess_exec` or 
:func:`asyncio.create_subprocess_shell`. Patch by Daan De Meyer.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to