]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
send in the dragons on async_scoped_session
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 3 Aug 2022 16:08:54 +0000 (12:08 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 3 Aug 2022 16:18:26 +0000 (12:18 -0400)
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

index 8dec8991a886212e0546858eaa6e414662349088..c21d561b6bdaed95f274039a5631e988f1bd30ce 100644 (file)
@@ -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