https://github.com/python/cpython/commit/7241f2739c4bbdf4519238689e5e4ea9268b411e
commit: 7241f2739c4bbdf4519238689e5e4ea9268b411e
branch: main
author: Max Schmitt <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-05-09T19:44:29+05:30
summary:
gh-149388: Make asyncio `PipeHandle.close` idempotent (#149518)
files:
A Misc/NEWS.d/next/Library/2026-05-07-21-58-17.gh-issue-149388.DDBPeA.rst
M Lib/asyncio/windows_utils.py
M Lib/test/test_asyncio/test_windows_utils.py
diff --git a/Lib/asyncio/windows_utils.py b/Lib/asyncio/windows_utils.py
index acd49441131b04..d6393f0b1ffee5 100644
--- a/Lib/asyncio/windows_utils.py
+++ b/Lib/asyncio/windows_utils.py
@@ -111,8 +111,9 @@ def fileno(self):
def close(self, *, CloseHandle=_winapi.CloseHandle):
if self._handle is not None:
- CloseHandle(self._handle)
+ handle = self._handle
self._handle = None
+ CloseHandle(handle)
def __del__(self, _warn=warnings.warn):
if self._handle is not None:
diff --git a/Lib/test/test_asyncio/test_windows_utils.py
b/Lib/test/test_asyncio/test_windows_utils.py
index f9ee2f4f68150a..50969761347595 100644
--- a/Lib/test/test_asyncio/test_windows_utils.py
+++ b/Lib/test/test_asyncio/test_windows_utils.py
@@ -77,6 +77,30 @@ def test_pipe_handle(self):
else:
raise RuntimeError('expected ERROR_INVALID_HANDLE')
+ def test_pipe_handle_close_after_external_close(self):
+ # gh-149388: PipeHandle.close() must clear ``_handle`` before calling
+ # CloseHandle so that if CloseHandle raises on a stale handle the
+ # PipeHandle is still marked closed and __del__ / subsequent close()
+ # calls are silent no-ops.
+ h1, h2 = windows_utils.pipe(overlapped=(False, False))
+ try:
+ p = windows_utils.PipeHandle(h1)
+ # Simulate an external close of the underlying handle (e.g.
+ # a finalizer race or a concurrent close on the same object).
+ _winapi.CloseHandle(p.handle)
+ # First close() still propagates the OSError from CloseHandle,
+ # but must clear ``_handle`` first.
+ with self.assertRaises(OSError):
+ p.close()
+ self.assertIsNone(p.handle)
+ # Second close() is a no-op.
+ p.close()
+ # __del__ through GC is also a silent no-op — no unraisable.
+ del p
+ support.gc_collect()
+ finally:
+ _winapi.CloseHandle(h2)
+
class PopenTests(unittest.TestCase):
diff --git
a/Misc/NEWS.d/next/Library/2026-05-07-21-58-17.gh-issue-149388.DDBPeA.rst
b/Misc/NEWS.d/next/Library/2026-05-07-21-58-17.gh-issue-149388.DDBPeA.rst
new file mode 100644
index 00000000000000..4a1c6f3f5b4e57
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-05-07-21-58-17.gh-issue-149388.DDBPeA.rst
@@ -0,0 +1 @@
+Make :class:`!asyncio.windows_utils.PipeHandle` closing idempotent.
_______________________________________________
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]