Skip to content

Commit d2b941f

Browse files
committed
gh-149388: Make PipeHandle.close idempotent
Clear _handle before calling CloseHandle so a stale handle (closed by another code path) does not leak OSError into _ProactorBasePipeTransport._call_connection_lost.
1 parent f0daba1 commit d2b941f

3 files changed

Lines changed: 27 additions & 1 deletion

File tree

Lib/asyncio/windows_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,9 @@ def fileno(self):
111111

112112
def close(self, *, CloseHandle=_winapi.CloseHandle):
113113
if self._handle is not None:
114-
CloseHandle(self._handle)
114+
handle = self._handle
115115
self._handle = None
116+
CloseHandle(handle)
116117

117118
def __del__(self, _warn=warnings.warn):
118119
if self._handle is not None:

Lib/test/test_asyncio/test_windows_utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,30 @@ def test_pipe_handle(self):
7777
else:
7878
raise RuntimeError('expected ERROR_INVALID_HANDLE')
7979

80+
def test_pipe_handle_close_after_external_close(self):
81+
# gh-149388: PipeHandle.close() must clear ``_handle`` before calling
82+
# CloseHandle so that if CloseHandle raises on a stale handle the
83+
# PipeHandle is still marked closed and __del__ / subsequent close()
84+
# calls are silent no-ops.
85+
h1, h2 = windows_utils.pipe(overlapped=(False, False))
86+
try:
87+
p = windows_utils.PipeHandle(h1)
88+
# Simulate an external close of the underlying handle (e.g.
89+
# a finalizer race or a concurrent close on the same object).
90+
_winapi.CloseHandle(p.handle)
91+
# First close() still propagates the OSError from CloseHandle,
92+
# but must clear ``_handle`` first.
93+
with self.assertRaises(OSError):
94+
p.close()
95+
self.assertIsNone(p.handle)
96+
# Second close() is a no-op.
97+
p.close()
98+
# __del__ through GC is also a silent no-op — no unraisable.
99+
del p
100+
support.gc_collect()
101+
finally:
102+
_winapi.CloseHandle(h2)
103+
80104

81105
class PopenTests(unittest.TestCase):
82106

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make :class:`!asyncio.windows_utils.PipeHandle` closing idempotent.

0 commit comments

Comments
 (0)