Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d61ad17

Browse files
authored
[mypyc] Fix exception reraising when awaiting a future (#20547)
Fixes mypyc/mypyc#1177. Fixed incorrect behavior seen when a function compiled with mypyc awaits an expression in which a future raises an exception, eg. ```python async def inner(): try: return await future_that_raises except Exception: # do something async def outer(): return await inner() ``` It seems like this case is different from a regular async function raising an exception in that the exception raised by the future is available in the handled exception state seen by the delegating generator (meaning the outer function compiled with mypyc) even when the exception is caught in the inner function. The delegating generator reraises the exception from the handled exception state in [`CPy_YieldFromErrorHandle`](https://github.com/python/mypy/blob/8eb14fb2a7f3b4683856b74babc637a845f0f8a5/mypyc/lib-rt/misc_ops.c#L93) which ends up reraising an already caught exception. The fix is to also return the function early when the delegated generator throws an exception other than `StopIteration` (for `StopIteration` it already returns), meaning `res` is `NULL` in line 71 and also in line 80. This is now consistent with the implementation in [cpython](https://github.com/python/cpython/blob/f3e069a7ab8b0594508c998da88937e3aab46451/Objects/genobject.c#L666) which does not reraise on exception in the delegated generator (the reraising happens under label `throw_here` which is reached under different conditions).
1 parent b5d3159 commit d61ad17

2 files changed

Lines changed: 150 additions & 0 deletions

File tree

mypyc/lib-rt/misc_ops.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ int CPy_YieldFromErrorHandle(PyObject *iter, PyObject **outp)
8181
if (res) {
8282
*outp = res;
8383
return 1;
84+
} else {
85+
return 2;
8486
}
8587
}
8688
} else if (PyErr_ExceptionMatches(PyExc_AttributeError)) {

mypyc/test-data/run-async.test

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,3 +1573,151 @@ def test_nested() -> None:
15731573

15741574
[file asyncio/__init__.pyi]
15751575
def run(x: object) -> object: ...
1576+
1577+
[case testFuture]
1578+
import asyncio
1579+
from typing import Any, Awaitable
1580+
1581+
from testutil import assertRaises
1582+
1583+
from m import (
1584+
m_future_with_result,
1585+
m_future_with_exception,
1586+
m_future_with_caught_exception,
1587+
m_future_with_reraised_exception
1588+
)
1589+
1590+
async def set_result(fut: asyncio.Future, result: Any) -> None:
1591+
if isinstance(result, Exception):
1592+
fut.set_exception(result)
1593+
else:
1594+
fut.set_result(result)
1595+
1596+
def make_future(result: Any) -> asyncio.Future:
1597+
loop = asyncio.get_running_loop()
1598+
fut = loop.create_future()
1599+
loop.create_task(set_result(fut, result))
1600+
return fut
1601+
1602+
async def future_with_result(result: int) -> int:
1603+
return await make_future(result)
1604+
1605+
async def future_with_exception(exception: Exception) -> Any:
1606+
return await make_future(exception)
1607+
1608+
async def future_with_caught_exception(exception: Exception) -> None:
1609+
try:
1610+
return await make_future(exception)
1611+
except type(exception):
1612+
return None
1613+
1614+
async def future_with_reraised_exception(first_exc: Exception, second_exc: Exception) -> Any:
1615+
try:
1616+
return await make_future(first_exc)
1617+
except type(first_exc):
1618+
raise second_exc
1619+
1620+
def test_future() -> None:
1621+
assert asyncio.run(future_with_result(42)) == 42
1622+
1623+
with assertRaises(ValueError, "error"):
1624+
asyncio.run(future_with_exception(ValueError("error")))
1625+
1626+
assert asyncio.run(future_with_caught_exception(ValueError("error"))) == None
1627+
1628+
with assertRaises(RuntimeError, "reraised"):
1629+
asyncio.run(future_with_reraised_exception(ValueError("error"), RuntimeError("reraised")))
1630+
1631+
class ctx_man:
1632+
def __enter__(self) -> None:
1633+
pass
1634+
1635+
def __exit__(self, *args: Any) -> None:
1636+
pass
1637+
1638+
async def wrap(f: Awaitable) -> Any:
1639+
with ctx_man():
1640+
return await f
1641+
1642+
def test_future_with_context_manager() -> None:
1643+
assert asyncio.run(wrap(future_with_result(42))) == 42
1644+
1645+
with assertRaises(ValueError, "error"):
1646+
asyncio.run(wrap(future_with_exception(ValueError("error"))))
1647+
1648+
assert asyncio.run(wrap(future_with_caught_exception(ValueError("error")))) == None
1649+
1650+
with assertRaises(RuntimeError, "reraised"):
1651+
asyncio.run(wrap(future_with_reraised_exception(ValueError("error"), RuntimeError("reraised"))))
1652+
1653+
def test_interpreted_future() -> None:
1654+
assert asyncio.run(m_future_with_result(42)) == 42
1655+
1656+
with assertRaises(ValueError, "error"):
1657+
asyncio.run(m_future_with_exception(ValueError("error")))
1658+
1659+
assert asyncio.run(m_future_with_caught_exception(ValueError("error"))) == None
1660+
1661+
with assertRaises(RuntimeError, "reraised"):
1662+
asyncio.run(m_future_with_reraised_exception(ValueError("error"), RuntimeError("reraised")))
1663+
1664+
def test_interpreted_future_with_context_manager() -> None:
1665+
assert asyncio.run(wrap(m_future_with_result(42))) == 42
1666+
1667+
with assertRaises(ValueError, "error"):
1668+
asyncio.run(wrap(m_future_with_exception(ValueError("error"))))
1669+
1670+
assert asyncio.run(wrap(m_future_with_caught_exception(ValueError("error")))) == None
1671+
1672+
with assertRaises(RuntimeError, "reraised"):
1673+
asyncio.run(wrap(m_future_with_reraised_exception(ValueError("error"), RuntimeError("reraised"))))
1674+
1675+
[file asyncio/__init__.pyi]
1676+
from typing import Any, Generator
1677+
1678+
def run(x: object) -> object: ...
1679+
1680+
class Future:
1681+
def set_result(self, result: object) -> None: ...
1682+
def set_exception(self, exception: object) -> None: ...
1683+
def __await__(self) -> Generator[Any, Any, Any]: ...
1684+
1685+
class Loop:
1686+
def create_future(self) -> Future: ...
1687+
def create_task(self, x: object) -> None: ...
1688+
1689+
def get_running_loop() -> Loop: ...
1690+
1691+
[file m.py]
1692+
import asyncio
1693+
from typing import Any
1694+
1695+
async def set_result(fut: asyncio.Future, result: Any) -> None:
1696+
if isinstance(result, Exception):
1697+
fut.set_exception(result)
1698+
else:
1699+
fut.set_result(result)
1700+
1701+
def make_future(result: Any) -> asyncio.Future:
1702+
loop = asyncio.get_running_loop()
1703+
fut = loop.create_future()
1704+
loop.create_task(set_result(fut, result))
1705+
return fut
1706+
1707+
async def m_future_with_result(result: int) -> int:
1708+
return await make_future(result)
1709+
1710+
async def m_future_with_exception(exception: Exception) -> Any:
1711+
return await make_future(exception)
1712+
1713+
async def m_future_with_caught_exception(exception: Exception) -> None:
1714+
try:
1715+
return await make_future(exception)
1716+
except type(exception):
1717+
return None
1718+
1719+
async def m_future_with_reraised_exception(first_exc: Exception, second_exc: Exception) -> Any:
1720+
try:
1721+
return await make_future(first_exc)
1722+
except type(first_exc):
1723+
raise second_exc

0 commit comments

Comments
 (0)