theine
theine copied to clipboard
Future exception was never retrieved problem when use with decorator in v2.0
how to reproduce the problem:
@Memoize(10000, timedelta(seconds=100))
async def foo_a(a:int) -> int:
raise Exception("test")
return a
@foo_a.key
def _(a:int) -> str:
return f"a:{a}"
async def main():
await foo_a(1)
if __name__ == "__main__":
asyncio.run(main())
result:
Future exception was never retrieved
future: <Future finished exception=Exception('test')>
Traceback (most recent call last):
File "\lib\site-packages\theine\theine.py", line 79, in __await__
result = yield from self.awaitable.__await__()
File "\test\theine_demo.py", line 20, in foo_a
raise Exception("test")
Exception: test
Traceback (most recent call last):
File "\test\theine_demo.py", line 38, in <module>
asyncio.run(main())
File "\lib\asyncio\runners.py", line 44, in run
return loop.run_until_complete(main)
File "\lib\asyncio\base_events.py", line 649, in run_until_complete
return future.result()
File "\test\theine_demo.py", line 29, in main
await foo_a(1)
File "\lib\site-packages\theine\theine.py", line 90, in __await__
raise e
File "\lib\site-packages\theine\theine.py", line 79, in __await__
result = yield from self.awaitable.__await__()
File "\testt\theine_demo.py", line 20, in foo_a
raise Exception("test")
Exception: test
It seem if you change the CachedAwaitable class to this could solve the problem. but I am no expert with python async I am not sure if this could cause any side effect.
if self.future:
self.future.set_exception(e)
# add this to consume the exception?
self.future.exception()
# https://github.com/python/cpython/issues/90780
# use event to protect from thundering herd
class CachedAwaitable:
def __init__(
self, awaitable: Awaitable[Any], on_error: Callable[[BaseException], None]
) -> None:
self.awaitable = awaitable
self.future: Optional[Awaitable[Any]] = None
self.result = sentinel
self.on_error = on_error
self.exception: Optional[BaseException] = None
def __await__(self) -> Any:
if self.result is not sentinel:
return self.result
elif self.exception:
raise self.exception
if self.future is None:
try:
self.future = asyncio.Future()
result = yield from self.awaitable.__await__()
self.result = result
self.future.set_result(self.result)
self.future = None
return result
except BaseException as e:
self.exception = e
if self.future:
self.future.set_exception(e)
# add this to consume the exception?
self.future.exception()
self.future = None
self.on_error(e)
raise e
else:
yield from self.future.__await__()
return self.result