]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
ensure greenlet_spawn propagates BaseException
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 18 Jun 2021 21:42:53 +0000 (17:42 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 19 Jun 2021 03:14:20 +0000 (23:14 -0400)
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

doc/build/changelog/unreleased_14/6652.rst [new file with mode: 0644]
lib/sqlalchemy/util/_concurrency_py3k.py
test/base/test_concurrency_py3k.py

diff --git a/doc/build/changelog/unreleased_14/6652.rst b/doc/build/changelog/unreleased_14/6652.rst
new file mode 100644 (file)
index 0000000..ebc12ba
--- /dev/null
@@ -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.
+
+
index 88294557d7c0a74225be5c6665a845a7602124c7..c6d1fa5d3a878a0629d69cf58df8192711772b1e 100644 (file)
@@ -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.
index 62a21a80347de27186bbbdcc35bf803115dd8c4e..45a0eb90e1ba3c2fe65199c1bb5c418a4ee719d6 100644 (file)
@@ -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():