Skip to content

Race condition leads to AttributeError from BaseEventLoop.__del__ #158903

Description

@bmerry

Bug report

The following program will rarely (maybe 0.1% of the time) print a stack trace:

#!/usr/bin/env python3

import asyncio
import os
import signal
import sys
import threading

async def main():
    await asyncio.sleep(1)

sys.setswitchinterval(1e-6)
try:
    t = threading.Thread(target=os.kill, args=(0, signal.SIGINT))
    t.start()
    asyncio.run(main())
except KeyboardInterrupt:
    pass
finally:
    t.join()

Output:

Exception ignored while calling deallocator <function BaseEventLoop.__del__ at 0x721e011f2140>:
Traceback (most recent call last):
  File "/usr/lib/python3.15/asyncio/base_events.py", line 765, in __del__
    self.close()
    ~~~~~~~~~~^^
  File "/usr/lib/python3.15/asyncio/unix_events.py", line 71, in close
    super().close()
    ~~~~~~~~~~~~~^^
  File "/usr/lib/python3.15/asyncio/selector_events.py", line 105, in close
    self._close_self_pipe()
    ~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib/python3.15/asyncio/selector_events.py", line 112, in _close_self_pipe
    self._remove_reader(self._ssock.fileno())
                        ^^^^^^^^^^^
AttributeError: '_UnixSelectorEventLoop' object has no attribute '_ssock'
/home/bmerry/work/presentations/2026-10-pycon-asyncio-patterns/tests/./pause.py:19: RuntimeWarning: coroutine 'main' was never awaited
  pass

Tested on Ubuntu 24.04 with CPython 3.15.0rc3 from deadsnakes. I've also observed this with 3.14.6.

CPython versions tested on:

3.15

Operating systems tested on:

Linux

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Oct 6, 2026
  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 6, 2026
  3. changed the title [-]Race condition leads to AttributeError from BaseEventLoop.__del__[/-] [+]Race condition leads to AttributeError from `BaseEventLoop.__del__`[/+] on Oct 6, 2026
  4. utam-1 commented on Oct 7, 2026

    @utam-1

    @bmerry, can I work on this issue? I am able to reproduce the same bug in Windows 11 Version: 23H2, with the following output:

    Exception ignored while calling deallocator <function BaseEventLoop.__del__ at 0x0000019DB6734EB0>:
    Traceback (most recent call last):
      File "C:\Users\user1\AppData\Local\Python\pythoncore-3.14-64\Lib\asyncio\base_events.py", line 760, in __del__
        self.close()
      File "C:\Users\user1\AppData\Local\Python\pythoncore-3.14-64\Lib\asyncio\proactor_events.py", line 696, in close
        self._close_self_pipe()
      File "C:\Users\user1\AppData\Local\Python\pythoncore-3.14-64\Lib\asyncio\proactor_events.py", line 778, in _close_self_pipe
        self._ssock.close()
    AttributeError: 'ProactorEventLoop' object has no attribute '_ssock'
    Running on Python 3.14.3 with default event loop policies...
    

    Based on my analysis of this bug the issue is that, in rare cases a KeyBoardInterrupt can block asyncio.run() loop's __init__ method before attributes ( such as self.sock in the above bug ), can be fully initialized. At the end during garbage collection the, these attributes aren't there so Python raises AttributeError for them.
    Making the cleanup process of the loop a bit more conservative would be able to resolve this I believe.

  5. added a commit that references this issue on Oct 7, 2026
  6. bmerry commented on Oct 7, 2026

    @bmerry
    ContributorAuthor

    @bmerry, can I work on this issue?

    I'm not a CPython committer, so it's not up to me. But note that @JohnnyWilson16 seems to have provided a fix already.

  7. added a commit that references this issue on Oct 7, 2026
  8. picnixz commented on Oct 7, 2026

    @picnixz
    Member

    I don't think it's a valid issue. This event is rare and suppressing the exception just for a clean traceback is honestly not worth it. If you interrupt the event loop during initialization, we can just leave it as is. And looking at the PR this adds un-necessary churn.

    cc @kumaraditya303

  9. added
    pendingThe issue will be closed if no feedback is provided
    stdlibStandard Library Python modules in the Lib/ directory
    and removed
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 7, 2026
  10. bmerry commented on Oct 7, 2026

    @bmerry
    ContributorAuthor

    I don't think it's a valid issue. This event is rare and suppressing the exception just for a clean traceback is honestly not worth it. If you interrupt the event loop during initialization, we can just leave it as is. And looking at the PR this adds un-necessary churn.

    cc @kumaraditya303

    I think you might be right. I was thinking in terms of https://docs.python.org/3/library/asyncio-runner.html#handling-keyboard-interruption, where Python 3.11 made changes specifically to ensure that SIGINT leads to a clean shutdown, but with the code I provided I'm guessing the SIGINT is being delivered before that signal handler is even installed.

    I still think it's best practice for __del__ not to assume that __init__ ran to completion (it could fail for a variety of reasons such as OSError or MemoryError, not just SIGINT), but maybe not worth the churn as you say. Feel free to close.

  11. picnixz commented on Oct 7, 2026

    @picnixz
    Member

    I'm leaving it open until Kumar comes back and then we can advise. But looking at the PR I really don't think having a bunch of ifs everywhere is really the best we can do. If there are better and cleaner alternatives I'm open to the change but otherwise I would advise against as it complicates the code.

  12. utam-1 commented on Oct 7, 2026

    @utam-1

    @picnixz and @bmerry, I think it is possible to handle this in the __init__ of EventLoop, however that'll take care of OSError and MemoryError. Getting rid of the issue associated with SIGINT completely isn't possible I believe.

  13. picnixz commented on Oct 7, 2026

    @picnixz
    Member

    I don't think we need anything to do here. The AttributeError is a benign exception and it's ignored anyway. And I don't think there is anything to do just in __init__.py. The problem is when close() is called by __del__ and where we are lacking attributes. What we can do is trying to free the resources in the reverse order they are declared so that if __init__ is interrupted somewhere, as soon as an AttributeError is rasied, it means the "rest" of the attributes don't need real cleanup. But we would still have AttributeErrors.

    Now, if Kumar wants to avoid any exception during cleanups like that, it's ok, but I'd like his input first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pendingThe issue will be closed if no feedback is providedstdlibStandard 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