From ac463218f45255f92c5ef0390c337fe60506c917 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 3 Aug 2022 12:08:54 -0400 Subject: [PATCH] send in the dragons on async_scoped_session make it clear that async_scoped_session.remove() must be called, else memory will build up. Generally discourage the whole pattern as well, as this is a "framework" pattern and we don't really want to be supporting frameworks. Also indicate that scopefunc must be idempotent and lightweight. Fixes: #8340 Change-Id: Ibc3d21124ae73c3b25ee51966504bbb1975c36b2 (cherry picked from commit c2327ec60f3f3b52a4b3a0daeef39174d96d225e) --- doc/build/orm/extensions/asyncio.rst | 35 ++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/doc/build/orm/extensions/asyncio.rst b/doc/build/orm/extensions/asyncio.rst index 8dec8991a8..c21d561b6b 100644 --- a/doc/build/orm/extensions/asyncio.rst +++ b/doc/build/orm/extensions/asyncio.rst @@ -731,10 +731,21 @@ from using any connection more than once:: Using asyncio scoped session ---------------------------- -The usage of :class:`_asyncio.async_scoped_session` is mostly similar to -:class:`.scoped_session`. However, since there's no "thread-local" concept in -the asyncio context, the "scopefunc" parameter must be provided to the -constructor:: +The "scoped session" pattern used in threaded SQLAlchemy with the +:class:`.scoped_session` object is also available in asyncio, using +an adapted version called :class:`_asyncio.async_scoped_session`. + +.. tip:: SQLAlchemy generally does not recommend the "scoped" pattern + for new development as it relies upon mutable global state that must also be + explicitly torn down when work within the thread or task is complete. + Particularly when using asyncio, it's likely a better idea to pass the + :class:`_asyncio.AsyncSession` directly to the awaitable functions that need + it. + +When using :class:`_asyncio.async_scoped_session`, as there's no "thread-local" +concept in the asyncio context, the "scopefunc" parameter must be provided to +the constructor. The example below illustrates using the +``asyncio.current_task()`` function for this purpose:: from asyncio import current_task @@ -747,7 +758,21 @@ constructor:: some_async_session = AsyncScopedSession() -:class:`_asyncio.async_scoped_session` also includes **proxy +.. warning:: The "scopefunc" used by :class:`_asyncio.async_scoped_session` + is invoked **an arbitrary number of times** within a task, once for each + time the underlying :class:`_asyncio.AsyncSession` is accessed. The function + should therefore be **idempotent** and lightweight, and should not attempt + to create or mutate any state, such as establishing callbacks, etc. + +.. warning:: Using ``current_task()`` for the "key" in the scope requires that + the :meth:`_asyncio.async_scoped_session.remove` method is called from + within the outermost awaitable, to ensure the key is removed from the + registry when the task is complete, otherwise the task handle as well as + the :class:`_asyncio.AsyncSession` will remain in memory, essentially + creating a memory leak. See the following example which illustrates + the correct use of :meth:`_asyncio.async_scoped_session.remove`. + +:class:`_asyncio.async_scoped_session` includes **proxy behavior** similar to that of :class:`.scoped_session`, which means it can be treated as a :class:`_asyncio.AsyncSession` directly, keeping in mind that the usual ``await`` keywords are necessary, including for the -- 2.47.2