Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Lib/asyncio/windows_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ def fileno(self):

def close(self, *, CloseHandle=_winapi.CloseHandle):
if self._handle is not None:
CloseHandle(self._handle)
self._handle = None
handle, self._handle = self._handle, None
CloseHandle(handle)

def __del__(self, _warn=warnings.warn):
if self._handle is not None:
Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_asyncio/test_windows_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,34 @@ 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 a handle already closed by another code path
# does not leak an OSError into the caller (and into
# _ProactorBasePipeTransport._call_connection_lost on the proactor
# loop, where the error is not caught).
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)
# The OSError from CloseHandle may still surface to the caller,
# but ``_handle`` must be cleared first so that __del__ and any
# subsequent close() are silent no-ops.
try:
p.close()
except OSError:
pass
self.assertIsNone(p.handle)
# Second close is a no-op.
p.close()
# __del__ through GC is a no-op too — no unraisable warning.
del p
support.gc_collect()
finally:
_winapi.CloseHandle(h2)


class PopenTests(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Make :meth:`!asyncio.windows_utils.PipeHandle.close` idempotent by clearing
``_handle`` before calling :c:func:`!CloseHandle`. Previously, if the
underlying Win32 handle had already been closed by another code path, the
``OSError`` raised by :c:func:`!CloseHandle` would escape through
:meth:`!_ProactorBasePipeTransport._call_connection_lost` on Windows
proactor loops and be reported as an unhandled exception to the event
loop's exception handler.
Loading