-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RuntimeWarning: coroutine method 'aclose' of '...' was never awaited when breaking out of async for #117536
Comments
With async generators, you have to take steps to clean them up properly if you don't iterate through them completely. In this case it's not entirely necessary, but it's good practice in general: async def customfilter(iterable):
items = []
agen = auto_aiter(iterable)
try:
async for item in agen:
items.append(item)
if len(items) == 3:
break
return "".join(items)
finally:
await agen.aclose() This ensures any pending With |
Thank you. This solves the particular case I provided as a reproducer, but I keep getting more and more such problems in Jinja. I've tried to add this pattern at multiple places, but I failed miserably. I still don't know whether what jinja does has always been "wrong" or whether the check is too strict :( |
asyncio.run is supposed to correctly shutdown async generator objects, I'm not sure what's happening here |
a smaller repro: async def agen():
yield
yield
async def main():
a = agen()
await anext(a)
del a
import asyncio
asyncio.run(main()) |
the problem is in _asyncgen_finalizer_hook, and exists in earlier versions of python - except generator.aclose() doesn't warn. cpython/Lib/asyncio/base_events.py Lines 557 to 561 in 9ceaee7
what's happening is |
I think it should be something like this? I'll try and get a PR and tests up def _asyncgen_finalizer_hook(self, agen):
def do_close():
if self._asyncgens_shutdown_called:
return
self._asyncgens.discard(agen)
self.create_task(agen.close())
if not self.is_closed():
self.call_soon_threadsafe(do_close) |
my original diagnosis was incorrect - I've eliminated asyncio here: import contextlib
async def agen():
while True:
yield
class CancelledError(BaseException):
pass
async def amain():
async with contextlib.aclosing(agen()) as a:
await anext(a)
try:
a.aclose().throw(CancelledError)
except CancelledError:
pass
try:
amain().send(None)
except StopIteration as e:
print(e.value) output:
it looks like throwing into aclose() doesn't mark the coroutine as awaited this is caused in asyncio because async generators garbage collected before |
@kumaraditya303 this looks like an issue in #104611 |
This comment was marked as outdated.
This comment was marked as outdated.
Simple reproducer that keeps the async def agen():
yield 0
yield 1
async def reproducer():
async for item in agen():
break
import asyncio
asyncio.run(reproducer()) This gives:
|
I'm marking this for consideration as a release blocker. |
This points to a workaround: adding |
Who's taking point here? It can't be me, I don't know the first thing about async generators (no need to educate me -- I'm trying to delegate here :-). |
Adding the |
@kumaraditya303 & @graingert Do you want to work on a fix, or should I look into it? |
Hold a strong reference to the asynchronous generator while it's being closed by the loop.
Hold a strong reference to the asynchronous generator while it's being closed by the loop.
Make the finalization of asynchronous generators more reliable. Store a strong reference to the asynchronous generator which is being closed to make sure that shutdown_asyncgens() can close it even if asyncio.run() cancels all tasks.
I proposed PR gh-117751 to fix this issue: make the finalization of asynchronous generators more reliable. I don't think that the warning is a regression. The problem is more that the finalization of asynchronous generators is not reliable, but Python now emits a warning if the generator is not finalized properly. It's just that the bug becomes more visible. Correct me if I'm wrong. |
@vstinner this is the wrong diagnosis I originally made, throwing into the aclose() object should mark it as awaited |
here's a realistic usage example of the problem of the incorrect warning: import asyncio
async def agenfn():
return
yield
class MyExc(Exception):
pass
async def amain():
try:
async with asyncio.TaskGroup() as tg:
agen = agenfn()
async for v in agen:
break
tg.create_task(agen.aclose())
raise MyExc()
except* MyExc:
pass
asyncio.run(amain()) outputs:
when clearly the |
Is it clear? You're running into unrelated issue #116048: the task was cancelled before it started running so asyncio doesn't run it at all (unlike Trio which would run it up to the first unshielded await and throw a cancellation exception as usual). [Edit: an earlier version of this comment was more equivocal but I just tried the code with a couple of extra print statements and I think it really is hitting that issue.] |
By the way, and sorry if this is a total red herring, but perhaps #116048 is not totally unrelated after all. The problem there is that |
@arthur-tacca yes this is true, but the aclose Coroutine should be considered awaited just like a regular async def |
Fixed in main; I'll close this. |
…nt' was never awaited See python/cpython#117536 (comment)
…nt' was never awaited See python/cpython#117536 (comment)
…nt' was never awaited See python/cpython#117536 (comment)
Bug report
Edit: See #117536 (comment) and #117536 (comment) for simpler reproducers --@encukou
Bug description:
The following code is extracted from jinja2 tests, as I was debugging pallets/jinja#1900 -- it uses no jinja:
This seems to be related to #89091
I am not sure if this is a regression in Python or a bug in Jinja tests, but considering Jinja folks closed my issue, I decided to report it here instead.
CPython versions tested on:
3.13
Operating systems tested on:
Linux
Linked PRs
The text was updated successfully, but these errors were encountered: