Skip to content

KeyboardInterrupt in TaskGroup task propagates to event loop scheduler #102572

Description

@mandolaerik

Bug report

If raising KeyboardInterrupt in a task created within a TaskGroup, the exception seems to propagate to the asyncio scheduler:

import asyncio
async def runner():
    loop = asyncio.get_running_loop()
    fut = loop.create_future()
    async def coro():
        try:
            fut.set_exception(TypeError())
            await loop.create_future()
        except asyncio.CancelledError:
            raise KeyboardInterrupt()
    try:
        async with asyncio.TaskGroup() as tg:
            tg.create_task(coro())
            await fut
    except* KeyboardInterrupt as e:
        print('Got KeyboardInterrupt', repr(e))
    except* TypeError as e:
        print('Got TypeError', repr(e))
asyncio.run(runner())

Gives me:

Got KeyboardInterrupt BaseExceptionGroup('', (KeyboardInterrupt(),))
Traceback (most recent call last):
  File "/tmp/foo.py", line 14, in runner
    await fut
TypeError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/foo.py", line 19, in <module>
    asyncio.run(runner())
  File "/tmp/foo.py", line 12, in runner
    async with asyncio.TaskGroup() as tg:
  File "/usr/lib64/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/asyncio/base_events.py", line 640, in run_until_complete
    self.run_forever()
  File "/usr/lib64/python3.11/asyncio/base_events.py", line 607, in run_forever
    self._run_once()
  File "/usr/lib64/python3.11/asyncio/base_events.py", line 1919, in _run_once
    handle._run()
  File "/usr/lib64/python3.11/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/tmp/foo.py", line 10, in coro
    raise KeyboardInterrupt()
KeyboardInterrupt

One may argue that it makes a bit of sense that a KeyboardInterrupt propagates to the user, but if I swap the raising of KeyboardInterrupt and TypeError in my code so the KeyboardInterrupt comes from the main task, then the KeyboardInterrupt is captured cleanly. I find this behaviour inconsistent with the documentation that says "The same special case is made for KeyboardInterrupt and SystemExit as in the previous paragraph".

Your environment

CPython 3.11.1 on fedora 37 x86-64:

$ python --version
Python 3.11.1
$ uname -a
Linux ecarsten-mobl1 6.1.9-200.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Feb  2 00:21:48 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.16new features, bugs and security fixestopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions