]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Ensure _simple_lazy_clause bind names are fixed before cloning
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 Feb 2019 00:46:17 +0000 (19:46 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 20 Feb 2019 00:46:17 +0000 (19:46 -0500)
Fixed a regression in 1.2 due to the introduction of baked queries for
relationship lazy loaders, where a race condition is created during the
generation of the "lazy clause" which occurs within a memoized attribute. If
two threads initialize the memoized attribute concurrently, the baked query
could be generated with bind parameter keys that are then replaced with new
keys by the next run, leading to a lazy load query that specifies the
related criteria as ``None``. The fix establishes that the parameter names
are fixed before the new clause and parameter objects are generated, so that
the names are the same every time.

Fixes: #4507
Change-Id: I605b824e028c87bc20ca8c2577227cdf6a591064

doc/build/changelog/unreleased_12/4507.rst [new file with mode: 0644]
lib/sqlalchemy/orm/strategies.py
test/ext/test_baked.py

diff --git a/doc/build/changelog/unreleased_12/4507.rst b/doc/build/changelog/unreleased_12/4507.rst
new file mode 100644 (file)
index 0000000..39b37b9
--- /dev/null
@@ -0,0 +1,13 @@
+.. change::
+   :tags: bug, orm
+   :tickets: 4507
+
+   Fixed a regression in 1.2 due to the introduction of baked queries for
+   relationship lazy loaders, where a race condition is created during the
+   generation of the "lazy clause" which occurs within a memoized attribute. If
+   two threads initialize the memoized attribute concurrently, the baked query
+   could be generated with bind parameter keys that are then replaced with new
+   keys by the next run, leading to a lazy load query that specifies the
+   related criteria as ``None``. The fix establishes that the parameter names
+   are fixed before the new clause and parameter objects are generated, so that
+   the names are the same every time.
index 3e7372fac72cee98cfe2ede966f6736a545600af..ec3c9790f2250906ec6df699c6d6761057623dbf 100644 (file)
@@ -595,6 +595,9 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
 
         def visit_bindparam(bindparam):
             bindparam.unique = False
+        visitors.traverse(criterion, {}, {"bindparam": visit_bindparam})
+
+        def visit_bindparam(bindparam):
             if bindparam._identifying_key in bind_to_col:
                 params.append(
                     (
index 57c9447999f58f5f6b8d51a90486f3d5ce9e8f96..55cd9376bf31756487736ee3bbfeccc9cc251880 100644 (file)
@@ -1325,6 +1325,21 @@ class LazyLoaderTest(testing.AssertsCompiledSQL, BakedTest):
             ),
         )
 
+    def test_simple_lazy_clause_no_race_on_generate(self):
+        User, Address = self._o2m_fixture()
+
+        expr1, paramdict1 = (
+            User.addresses.property._lazy_strategy._simple_lazy_clause
+        )
+
+        # delete the attr, as though a concurrent thread is also generating it
+        del User.addresses.property._lazy_strategy._simple_lazy_clause
+        expr2, paramdict2 = (
+            User.addresses.property._lazy_strategy._simple_lazy_clause
+        )
+
+        eq_(paramdict1, paramdict2)
+
     # additional tests:
     # 1. m2m w lazyload
     # 2. o2m lazyload where m2o backrefs have an eager load, test