Skip to content

asyncio on Windows raises InvalidStateError after WinError 995 #149123

@maxliaops

Description

@maxliaops

Bug report

Bug description:

While running an asyncio-based service on Windows, I observed the following exception chain:

  1. ov.getresult() inside asyncio.windows_events.finish_socket_func() raises:

OSError: [WinError 995] The I/O operation has been aborted due to either a thread exit or an application request.

  1. Then inside _poll() in the same file, this is translated by finish_socket_func() into:

ConnectionResetError: [WinError 995] The I/O operation has been aborted ...

  1. Then _poll() executes:

f.set_exception(e)

which raises:

asyncio.exceptions.InvalidStateError: invalid state

  1. This exception then appears on the main event loop path, including frames in:
  • asyncio.base_events.BaseEventLoop._run_once()
  • asyncio.base_events.BaseEventLoop.run_forever()
  • asyncio.base_events.BaseEventLoop.run_until_complete()
  • asyncio.runners.Runner.run()
  • asyncio.run()
  1. After InvalidStateError is raised, the service process exits.

———

Environment

  • OS: Windows
  • Python: 3.13.12
  • Relevant stdlib files:
    • Lib/asyncio/windows_events.py
    • Lib/asyncio/base_events.py
    • Lib/asyncio/runners.py
    • Lib/asyncio/futures.py

———

Observed traceback

2026-04-24 10:35:08 - ERROR - uvicorn.error - Traceback (most recent call last):
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\windows_events.py", line 463, in finish_socket_func
    return ov.getresult()
           ~~~~~~~~~~~~^^
OSError: [WinError 995] 由于线程退出或应用程序请求,已中止 I/O 操作。

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\windows_events.py", line 804, in _poll
    value = callback(transferred, key, ov)
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\windows_events.py", line 467, in finish_socket_func
    raise ConnectionResetError(*exc.args)
ConnectionResetError: [WinError 995] 由于线程退出或应用程序请求,已中止 I/O 操作。

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\runners.py", line 195, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\base_events.py", line 712, in run_until_complete
    self.run_forever()
    ~~~~~~~~~~~~~~~~^^
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\base_events.py", line 683, in run_forever
    self._run_once()
    ~~~~~~~~~~~~~~^^
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\base_events.py", line 2012, in _run_once
    event_list = self._selector.select(timeout)
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\windows_events.py", line 446, in select
    self._poll(timeout)
    ~~~~~~~~~~^^^^^^^^^
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\windows_events.py", line 806, in _poll
    f.set_exception(e)
    ~~~~~~~~~~~~~~~^^^
  File "C:\Users\moza\AppData\Roaming\uv\python\cpython-3.13.12-windows-x86_64-none\Lib\asyncio\windows_events.py", line 89, in set_exception
    super().set_exception(exception)
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
asyncio.exceptions.InvalidStateError: invalid state

———

Relevant source code

  1. Key code in asyncio.windows_events.IocpProactor._poll():
  if obj in self._stopped_serving:
      f.cancel()
  elif not f.done():
      try:
          value = callback(transferred, key, ov)
      except OSError as e:
          f.set_exception(e)
          self._results.append(f)
      else:
          f.set_result(value)
          self._results.append(f)
  1. asyncio.futures.Future.done():
  def done(self):
      return self._state != _PENDING
  1. asyncio.futures.Future.set_exception():
  def set_exception(self, exception):
      if self._state != _PENDING:
          raise exceptions.InvalidStateError(...)

———

What I would like to confirm

In IocpProactor._poll(), entering:

elif not f.done():

means that, at the time of this check, f should still be PENDING, according to Future.done().

The next call is synchronous:

value = callback(transferred, key, ov)

There is no explicit await here and no obvious Python-level scheduling switch point.

However, the traceback shows that during callback(...), the lower-level ov.getresult() raises WinError 995, which is translated by finish_socket_func() into ConnectionResetError, and then the immediately
following:

f.set_exception(e)

raises:

asyncio.exceptions.InvalidStateError: invalid state

This means that by the time f.set_exception(e) is executed, f is no longer in the PENDING state.

———

Questions

  1. Is WinError 995 followed by InvalidStateError a known phenomenon?
  2. Is this exception chain expected behavior for the Windows Proactor event loop?
  3. In this scenario, is it expected that IocpProactor._poll() allows InvalidStateError to propagate out of the event loop core and ultimately terminate the process?

———

CPython versions tested on:

3.13

Operating systems tested on:

Windows

Metadata

Metadata

Assignees

No one assigned

    Labels

    OS-windowsstdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions