diff --git a/Lib/asyncio/windows_events.py b/Lib/asyncio/windows_events.py index 8241c10b32d105..541df43f63c074 100644 --- a/Lib/asyncio/windows_events.py +++ b/Lib/asyncio/windows_events.py @@ -804,11 +804,13 @@ def _poll(self, timeout=None): try: value = callback(transferred, key, ov) except OSError as e: - f.set_exception(e) - self._results.append(f) + if not f.done(): + f.set_exception(e) + self._results.append(f) else: - f.set_result(value) - self._results.append(f) + if not f.done(): + f.set_result(value) + self._results.append(f) finally: f = None diff --git a/Lib/test/test_asyncio/test_windows_events.py b/Lib/test/test_asyncio/test_windows_events.py index c40391b237d57e..13ff025f3094b7 100644 --- a/Lib/test/test_asyncio/test_windows_events.py +++ b/Lib/test/test_asyncio/test_windows_events.py @@ -220,6 +220,38 @@ def test_wait_for_handle_cancel(self): fut.cancel() fut.cancel() + def _poll_with_future_done_during_callback(self, callback_exception=None): + proactor = self.loop._proactor + ov = _overlapped.Overlapped(_winapi.NULL) + fut = windows_events._OverlappedFuture(ov, loop=self.loop) + + class Obj: + pass + + def callback(transferred, key, ov): + self.assertFalse(fut.done()) + fut.cancel() + if callback_exception is not None: + raise callback_exception + return object() + + proactor._cache[ov.address] = (fut, ov, Obj(), callback) + _overlapped.PostQueuedCompletionStatus(proactor._iocp, 0, 0, + ov.address) + + proactor._poll(0) + + self.assertTrue(fut.cancelled()) + self.assertEqual(proactor._results, []) + + def test_poll_skips_exception_if_future_done_during_callback(self): + exc = ConnectionResetError(_overlapped.ERROR_OPERATION_ABORTED, + "operation aborted") + self._poll_with_future_done_during_callback(exc) + + def test_poll_skips_result_if_future_done_during_callback(self): + self._poll_with_future_done_during_callback() + def test_read_self_pipe_restart(self): # Regression test for https://bugs.python.org/issue39010 # Previously, restarting a proactor event loop in certain states diff --git a/Misc/NEWS.d/next/Library/2026-07-02-01-55-00.gh-issue-149123.GN8o4M.rst b/Misc/NEWS.d/next/Library/2026-07-02-01-55-00.gh-issue-149123.GN8o4M.rst new file mode 100644 index 00000000000000..91a2ae8efc853e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-02-01-55-00.gh-issue-149123.GN8o4M.rst @@ -0,0 +1,2 @@ +Avoid raising :exc:`asyncio.InvalidStateError` from the Windows proactor event +loop when an overlapped future becomes done while handling an I/O completion.