From: Federico Caselli Date: Fri, 30 Apr 2021 20:36:59 +0000 (+0200) Subject: Avoid creating asyncio.Lock on the wrong loop. X-Git-Tag: rel_1_4_13~8 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=fc7a3533417d970e47397f4da874e5e73e9f1c72;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Avoid creating asyncio.Lock on the wrong loop. Fixed a regression introduced by :ticket:`6337` that would create an ``asyncio.Lock`` which could be attached to the wrong loop when instantiating the async engine before any asyncio loop was started, leading to an asyncio error message when attempting to use the engine under certain circumstances. Fixes: #6409 Change-Id: I8119c56b44a7bd70a650c0ea676892d4d7814a8b --- diff --git a/doc/build/changelog/unreleased_14/6409.rst b/doc/build/changelog/unreleased_14/6409.rst new file mode 100644 index 0000000000..a5b1da2a11 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6409.rst @@ -0,0 +1,9 @@ +.. change:: + :tags: bug, asyncio, regression + :tickets: 6409 + + Fixed a regression introduced by :ticket:`6337` that would create an + ``asyncio.Lock`` which could be attached to the wrong loop when + instantiating the async engine before any asyncio loop was started, leading + to an asyncio error message when attempting to use the engine under certain + circumstances. diff --git a/lib/sqlalchemy/util/_concurrency_py3k.py b/lib/sqlalchemy/util/_concurrency_py3k.py index b905f903b8..5f03972b2a 100644 --- a/lib/sqlalchemy/util/_concurrency_py3k.py +++ b/lib/sqlalchemy/util/_concurrency_py3k.py @@ -7,6 +7,7 @@ from typing import Coroutine import greenlet from . import compat +from .langhelpers import memoized_property from .. import exc @@ -132,10 +133,15 @@ async def greenlet_spawn( class AsyncAdaptedLock: - def __init__(self): - self.mutex = asyncio.Lock() + @memoized_property + def mutex(self): + # there should not be a race here for coroutines creating the + # new lock as we are not using await, so therefore no concurrency + return asyncio.Lock() def __enter__(self): + # await is used to acquire the lock only after the first calling + # coroutine has created the mutex. await_fallback(self.mutex.acquire()) return self diff --git a/test/ext/asyncio/test_engine_py3k.py b/test/ext/asyncio/test_engine_py3k.py index dc791215cf..820c82bca6 100644 --- a/test/ext/asyncio/test_engine_py3k.py +++ b/test/ext/asyncio/test_engine_py3k.py @@ -90,6 +90,43 @@ class AsyncEngineTest(EngineFixture): is_false(async_engine == None) + @async_test + async def test_no_attach_to_event_loop(self, testing_engine): + """test #6409""" + + import asyncio + import threading + + errs = [] + + def go(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + engine = testing_engine(asyncio=True, transfer_staticpool=True) + + async def main(): + tasks = [task() for _ in range(2)] + + await asyncio.gather(*tasks) + + async def task(): + async with engine.begin() as connection: + result = await connection.execute(select(1)) + result.all() + + try: + asyncio.run(main()) + except Exception as err: + errs.append(err) + + t = threading.Thread(target=go) + t.start() + t.join() + + if errs: + raise errs[0] + @async_test async def test_connection_info(self, async_engine):