From: Mike Bayer Date: Fri, 18 Jun 2021 21:42:53 +0000 (-0400) Subject: ensure greenlet_spawn propagates BaseException X-Git-Tag: rel_1_4_19~11^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=81cc6829f4538649c3c848767bcab55039767d85;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git ensure greenlet_spawn propagates BaseException Fixed bug in asyncio implementation where the greenlet adaptation system failed to propagate ``BaseException`` subclasses, most notably including ``asyncio.CancelledError``, to the exception handling logic used by the engine to invalidate and clean up the connection, thus preventing connections from being correctly disposed when a task was cancelled. Fixes: #6652 Change-Id: Id3809e6c9e7bced46a7a3b5a0d1906c4168dc4fc --- diff --git a/doc/build/changelog/unreleased_14/6652.rst b/doc/build/changelog/unreleased_14/6652.rst new file mode 100644 index 0000000000..ebc12bae6d --- /dev/null +++ b/doc/build/changelog/unreleased_14/6652.rst @@ -0,0 +1,11 @@ +.. change:: + :tags: bug, asyncio, postgresql + :tickets: 6652 + + Fixed bug in asyncio implementation where the greenlet adaptation system + failed to propagate ``BaseException`` subclasses, most notably including + ``asyncio.CancelledError``, to the exception handling logic used by the + engine to invalidate and clean up the connection, thus preventing + connections from being correctly disposed when a task was cancelled. + + diff --git a/lib/sqlalchemy/util/_concurrency_py3k.py b/lib/sqlalchemy/util/_concurrency_py3k.py index 88294557d7..c6d1fa5d3a 100644 --- a/lib/sqlalchemy/util/_concurrency_py3k.py +++ b/lib/sqlalchemy/util/_concurrency_py3k.py @@ -25,6 +25,8 @@ else: def is_exit_exception(e): + # note asyncio.CancelledError is already BaseException + # so was an exit exception in any case return not isinstance(e, Exception) or isinstance( e, (asyncio.TimeoutError, asyncio.CancelledError) ) @@ -118,7 +120,7 @@ async def greenlet_spawn( # wait for a coroutine from await_ and then return its # result back to it. value = await result - except Exception: + except BaseException: # this allows an exception to be raised within # the moderated greenlet so that it can continue # its expected flow. diff --git a/test/base/test_concurrency_py3k.py b/test/base/test_concurrency_py3k.py index 62a21a8034..45a0eb90e1 100644 --- a/test/base/test_concurrency_py3k.py +++ b/test/base/test_concurrency_py3k.py @@ -46,6 +46,29 @@ class TestAsyncioCompat(fixtures.TestBase): with expect_raises_message(ValueError, "an error"): await greenlet_spawn(go, run1, err) + @async_test + async def test_propagate_cancelled(self): + """test #6652""" + cleanup = [] + + async def async_meth_raise(): + raise asyncio.CancelledError() + + def sync_meth(): + try: + await_only(async_meth_raise()) + except: + cleanup.append(True) + raise + + async def run_w_cancel(): + await greenlet_spawn(sync_meth) + + with expect_raises(asyncio.CancelledError, check_context=False): + await run_w_cancel() + + assert cleanup + @async_test async def test_sync_error(self): def go():