Skip to content

The asyncio REPL interactive daemon thread doesn't handle PYTHONSTARTUP exceptions #140287

Description

@johnslavik

Bug report

Bug description:

That leads to flaky behaviors.

This is my PYTHONSTARTUP file:

raise ModuleNotFoundError("oops!")

ModuleNotFoundError is just an example exception that can occur in PYTHONSTARTUP scripts.

This is how the regular REPLs handle it (both the basic REPL and the new REPL):

❯ PYTHONSTARTUP=t.py ./python
Python 3.15.0a1+ (heads/main:fbf0843e39e, Oct 18 2025, 11:24:43) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
Traceback (most recent call last):
  File "t.py", line 1, in <module>
    raise ModuleNotFoundError("oops!")
ModuleNotFoundError: oops!
>>> 

The traceback is shown and we proceed to the loop.

For the asyncio REPL, startup file exception always causes it to exit immediately -- we'd never want the REPL to exit because of a startup file error. On top of that, it doesn't report the exception properly, because it's racing with the interpreter shutdown at that point.

I've found 3 variants of the incorrect behavior:

  1. The interactive daemon thread doesn't write the full traceback:

    ❯ PYTHONSTARTUP=t.py ./python -m asyncio
    asyncio REPL 3.15.0a1+ (heads/main:fbf0843e39e, Oct 18 2025, 11:24:43) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
    Use "await" directly instead of "asyncio.run()".
    Type "help", "copyright", "credits" or "license" for more information.
    Exception in thread Interactive thread:
    exiting asyncio REPL...
    Traceback (most recent call last):
    
  2. The interactive daemon thread does flush, but after exiting is reported:

    ❯ PYTHONSTARTUP=t.py ./python -m asyncio
    asyncio REPL 3.15.0a1+ (heads/main:fbf0843e39e, Oct 18 2025, 11:24:43) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
    Use "await" directly instead of "asyncio.run()".
    Type "help", "copyright", "credits" or "license" for more information.
    Exception in thread Interactive thread:
    exiting asyncio REPL...
    Traceback (most recent call last):
      File "/home/bswck/Python/cpython/Lib/threading.py", line 1075, in _bootstrap_inner
        self._context.run(self.run)
        ~~~~~~~~~~~~~~~~~^^^^^^^^^^
      File "/home/bswck/Python/cpython/Lib/asyncio/__main__.py", line 104, in run
        exec(startup_code, console.locals)
        ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "t.py", line 1, in <module>
        raise ModuleNotFoundError("oops!")
    ModuleNotFoundError: oops!
    
  3. The interactive daemon tries to acquire stderr at interpreter shutdown, and fails:

    ❯ PYTHONSTARTUP=t.py ./python -m asyncio
    asyncio REPL 3.15.0a1+ (heads/main:fbf0843e39e, Oct 18 2025, 11:24:43) [GCC 15.2.1 20250808 (Red Hat 15.2.1-1)] on linux
    Use "await" directly instead of "asyncio.run()".
    Type "help", "copyright", "credits" or "license" for more information.
    Exception in thread Interactive thread:
    exiting asyncio REPL...
    Traceback (most recent call last):
      File "/home/bswck/Python/cpython/Lib/threading.py", line 1075, in _bootstrap_inner
        self._context.run(self.run)
        ~~~~~~~~~~~~~~~~~^^^^^^^^^^
    Fatal Python error: _enter_buffered_busy: could not acquire lock for <_io.BufferedWriter name='<stderr>'> at interpreter shutdown, possibly due to daemon threads
    Python runtime state: finalizing (tstate=0x00000000009c3818)
    
    Current thread 0x00007f6c4781cbc0 [python] (most recent call first):
      <no Python frame>
    Aborted (core dumped)
    

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Activity

  1. johnslavik commented on Oct 18, 2025

    @johnslavik
    MemberAuthor

    CC @ZeroIntensity (mentorship) I'd add these labels: stdlib, type-repl, type-asyncio. Perhaps also version labels once I reproduce for all versions.

  2. johnslavik commented on Oct 18, 2025

    @johnslavik
    MemberAuthor

    The simplest fix is to just handle these exceptions with sys.excepthook (via console.showtraceback).

  3. johnslavik commented on Oct 18, 2025

    @johnslavik
    MemberAuthor

    Interestingly, _pyrepl doesn't seem to properly handle this as well when pythonstartup=True is passed to _pyrepl.main.interactive_console. PYTHONSTARTUP=t.py ./python -c 'import _pyrepl.main; _pyrepl.main.interactive_console(pythonstartup=True)' exits immediately. However, it doesn't suffer from problems that daemonic threads do. I need to check that later.

  4. johnslavik commented on Nov 8, 2025

    @johnslavik
    MemberAuthor

    This is a bugfix -- let's add 3.13, 3.14 and 3.15 here cc @ZeroIntensity mentorship

  5. added
    3.13only security fixes
    3.14bugs and security fixes
    3.15bugs and security fixes
    on Nov 8, 2025
  6. added a commit that references this issue on Apr 25, 2026
  7. added a commit that references this issue on Apr 25, 2026
  8. added 2 commits that reference this issue on Apr 29, 2026
  9. moved this from Todo to Done in asyncioon Apr 29, 2026
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

    3.13only security fixes3.14bugs and security fixes3.15bugs and security fixesstdlibStandard Library Python modules in the Lib/ directorytopic-asynciotopic-replRelated to the interactive shelltype-bugAn unexpected behavior, bug, or error

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions