This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: exception lost when loop.stop() in finally
Type: behavior Stage:
Components: asyncio Versions: Python 3.10, Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Amos.Anderson, asvetlov, serhiy.storchaka, yselivanov
Priority: normal Keywords:

Created on 2021-11-24 20:35 by Amos.Anderson, last changed 2022-04-11 14:59 by admin.

Messages (4)
msg406957 - (view) Author: Amos Anderson (Amos.Anderson) Date: 2021-11-24 20:35
I found a case where an exception is lost if the loop is stopped in a `finally`.


```
import asyncio
import logging


logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger()


async def method_that_raises():
    loop = asyncio.get_event_loop()
    try:
        logger.info("raising exception")
        raise ValueError("my exception1")
    # except Exception as e:
    #     logger.info("in catcher")
    #     logger.exception(e)
    #     raise
    finally:
        logger.info("stopped")
        loop.stop()
        # await asyncio.sleep(0.5)

        return


async def another_level():
    try:
        await method_that_raises()
    except Exception as e:
        logger.info("trapping from another_level")
        logger.exception(e)


if __name__ == "__main__":
    logger.info("start")
    try:
        asyncio.run(another_level())
    except Exception as e:
        logger.exception(e)
    logger.info("done")
```

gives this output in python 3.10.0 and 3.8.10 (tested in Ubuntu Windows Subsystem Linux) and 3.8.11 in Windows:

```
INFO:root:start
DEBUG:asyncio:Using selector: EpollSelector
INFO:root:raising exception
INFO:root:stopped
INFO:root:done
```
i.e., no evidence an exception was raised (other than the log message included to prove one was raised)

If I remove the `return`, then the exception propagates as expected.

I believe the exception should be propagated regardless of whether there's a `return` in the `finally` block.
msg406961 - (view) Author: Amos Anderson (Amos.Anderson) Date: 2021-11-24 20:55
If I do this instead:
```
    try:
        logger.info("raising exception")
        raise ValueError("my exception1")
    finally:
        logger.info("stopped")
        loop.stop()
        await asyncio.sleep(0.5)
```

i.e., do an `await` instead of a `return`, then the original exception is also lost:

```
INFO:root:start
DEBUG:asyncio:Using selector: EpollSelector
INFO:root:raising exception
INFO:root:stopped
ERROR:root:Event loop stopped before Future completed.
Traceback (most recent call last):
  File "test.py", line 37, in <module>
    asyncio.run(another_level())
  File "/home/amos/miniconda3/lib/python3.8/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/home/amos/miniconda3/lib/python3.8/asyncio/base_events.py", line 614, in run_until_complete
    raise RuntimeError('Event loop stopped before Future completed.')
RuntimeError: Event loop stopped before Future completed.
INFO:root:done
```

it's also a bit surprising that my handler in `another_level` didn't see either exception, but I'm not really sure what I'd expect in that case.
msg406962 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2021-11-24 21:30
It is not related to loop.stop() and asyncio in general. It is the return statement which eats the exception. Simpler example:

>>> def f():
...     try:
...         1/0
...     finally:
...         return 42
... 
>>> f()
42

Return (and also break and continue) in the finally block cancel an exception if it was raised.
msg406970 - (view) Author: Amos Anderson (Amos.Anderson) Date: 2021-11-25 00:34
Ah, thank you, Serhiy. I didn't know that, but I see that in the documentation:
https://docs.python.org/3/reference/compound_stmts.html#the-try-statement

But what about the 2nd case I presented where a `RuntimeError` was raised? That's the actual case I'm working on. Based on this:

> If the finally clause raises another exception, the saved exception is set as the context of the new exception.


My expectation is that the two exceptions would be chained.
History
Date User Action Args
2022-04-11 14:59:52adminsetgithub: 90052
2021-11-25 00:34:04Amos.Andersonsetmessages: + msg406970
2021-11-24 21:30:36serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg406962
2021-11-24 20:55:56Amos.Andersonsetmessages: + msg406961
2021-11-24 20:35:15Amos.Andersoncreate