From 2bedaad9a80bfd452e7dc1de0895016601ebf03b Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 7 Oct 2026 17:00:11 +0200 Subject: [PATCH] Fix async generator aclose Fixes #1196 --- .../src/tests/test_generators.py | 27 +++++++++++++++++++ .../src/tests/unittest_tags/test_asyncgen.txt | 4 +++ .../asyncio/AsyncGenThrowBuiltins.java | 7 ++--- .../graal/python/nodes/ErrorMessages.java | 1 + 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_generators.py b/graalpython/com.oracle.graal.python.test/src/tests/test_generators.py index 643e8d2cde..3ef5a5b2eb 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_generators.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_generators.py @@ -9,6 +9,33 @@ from tests import util +class AsyncGeneratorTest(unittest.TestCase): + def test_aclose_await_in_finally(self): + resumed = [] + + class Suspend: + def __await__(self): + return (yield "suspended") + + async def gen(): + try: + yield 1 + finally: + resumed.append(await Suspend()) + + g = gen() + with self.assertRaises(StopIteration) as cm: + g.__anext__().send(None) + self.assertEqual(cm.exception.value, 1) + + close = g.aclose() + self.assertEqual(close.send(None), "suspended") + self.assertEqual(resumed, []) + with self.assertRaises(StopIteration): + close.send(42) + self.assertEqual(resumed, [42]) + + class ExceptionTest(unittest.TestCase): # Tests for the issue #23353: check that the currently handled exception # is correctly saved/restored in PyEval_EvalFrameEx(). diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_asyncgen.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_asyncgen.txt index e3259b3eec..5e63c3f0e5 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_asyncgen.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_asyncgen.txt @@ -14,8 +14,12 @@ test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_aiter_class @ darwin-arm64 test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_01 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_02 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_03 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_aclose_06 @ linux-x86_64 +test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_aclose_07 @ linux-x86_64 +test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_aclose_08 @ linux-x86_64 test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_aclose_10 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_aclose_11 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_aclose_12 @ linux-x86_64 test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_anext_04 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_anext_05 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_asyncgen.AsyncGenAsyncioTest.test_async_gen_asyncio_anext_06 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/asyncio/AsyncGenThrowBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/asyncio/AsyncGenThrowBuiltins.java index 749e361a53..c03e05b545 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/asyncio/AsyncGenThrowBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/asyncio/AsyncGenThrowBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -41,7 +41,7 @@ package com.oracle.graal.python.builtins.objects.asyncio; import static com.oracle.graal.python.builtins.objects.asyncio.PAsyncGenASend.AwaitableState; -import static com.oracle.graal.python.nodes.ErrorMessages.GENERATOR_IGNORED_EXIT; +import static com.oracle.graal.python.nodes.ErrorMessages.ASYNCGEN_IGNORED_EXIT; import static com.oracle.graal.python.nodes.ErrorMessages.SEND_NON_NONE_TO_UNSTARTED_GENERATOR; import java.util.List; @@ -174,6 +174,7 @@ public Object send(VirtualFrame frame, PAsyncGenAThrow self, Object sent, if (isAGWrappedValue.profileObject(inliningTarget, retval, PythonBuiltinClassType.PAsyncGenAWrappedValue)) { throw yieldClose(inliningTarget, self, gen); } + return retval; } else { // athrow mode try { @@ -212,7 +213,7 @@ public Object send(VirtualFrame frame, PAsyncGenAThrow self, Object sent, static PException yieldClose(Node inliningTarget, PAsyncGenAThrow athrow, PAsyncGen gen) { gen.setRunningAsync(false); athrow.setState(AwaitableState.CLOSED); - return PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.RuntimeError, GENERATOR_IGNORED_EXIT); + return PRaiseNode.raiseStatic(inliningTarget, PythonBuiltinClassType.RuntimeError, ASYNCGEN_IGNORED_EXIT); } static PException checkError(PAsyncGenAThrow athrow, PAsyncGen gen, PException exception, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index e8c7d2ad1d..effd6275cb 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1463,6 +1463,7 @@ public abstract class ErrorMessages { public static final TruffleString AGEN_ALREADY_RUNNING = tsLiteral("anext(): asynchronous generator is already running"); public static final TruffleString CORO_ALREADY_AWAITED = tsLiteral("coroutine is being awaited already"); public static final TruffleString ANEXT_INVALID_OBJECT = tsLiteral("'async for' received an invalid object from __anext__: %p"); + public static final TruffleString ASYNCGEN_IGNORED_EXIT = tsLiteral("async generator ignored GeneratorExit"); public static final TruffleString ASYNCGEN_RAISED_ASYNCSTOPITER = tsLiteral("async generator raised StopAsyncIteration"); public static final TruffleString MAXSIZE_SHOULD_BE_INTEGER_OR_NONE = tsLiteral("maxsize should be integer or None"); public static final TruffleString THE_FIRST_ARGUMENT_MUST_BE_CALLABLE = tsLiteral("the first argument must be callable");