Search code examples
pythonexceptionerror-handlingpython-asyncio

KeyboardInterrupt is not catched when blocking work is done


To stop the following code one will need to press Ctrl-C twice, however the second raised KeyboardInterrupt exception is not catched by the inner try-catch inside the start_consuming method.

import asyncio


async def start_consuming():
    try:
        await asyncio.Future()
    except:
        try:
            await asyncio.Future()
        except:
            pass


async def main():
    await asyncio.gather(start_consuming())


if __name__ == '__main__':
    asyncio.run(main())

Why?


Solution

  • It is not like it seems. Here is your program with some debug prints added.

    import asyncio
    
    async def start_consuming():
        try:
            await asyncio.Future()
        except BaseException as b:
            print(1, repr(b))
    
        try:
            await asyncio.Future()
        except BaseException as b:
            print(2, repr(b))
    
    async def main():
        try:
            await asyncio.gather(start_consuming())
        except BaseException as b:
            print(3, repr(b))
    
    if __name__ == '__main__':
        try:
            asyncio.run(main())
        except BaseException as b:
            print (4, repr(b))
    
    

    The output slightly edited:

    ^C 1 CancelledError()
    
    ^C 2 CancelledError()
    
    3 CancelledError()
    
    4 KeyboardInterrupt()
    

    I don't have a fully detailed explanation, but it is clear that when the ctrl-C interrupts arrive, the start_consuming coroutine is not executing, because it is not ready.

    The interrupt (exception) is then caught ("catched") by the asyncio itself. Everything else is in my opinion an asyncio shutdown & cleanup in action. The ctrl-C interrupt is then raised by asyncio.run() during the exit from asyncio.

    I'd like to add a note regarding the asyncio shutdown, "swallowing" the cancellation exception - which is also BaseException - is against the rules. Docs: "This exception can be caught to perform custom operations when asyncio Tasks are cancelled. In almost all situations the exception must be re-raised."