summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/elements.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-06-07 15:00:20 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-06-08 09:45:10 -0400
commit117878f7870377f143917a22160320a891eb0211 (patch)
tree3b87965f0fa78a9643afccf8646f693e6782f4b8 /lib/sqlalchemy/sql/elements.py
parent938c5d1033085289b4cbbd4b9229eaa3ad90b66d (diff)
downloadsqlalchemy-117878f7870377f143917a22160320a891eb0211.tar.gz
fix race conditions in lambda statements
Fixed multiple observed race conditions related to :func:`.lambda_stmt`, including an initial "dogpile" issue when a new Python code object is initially analyzed among multiple simultaneous threads which created both a performance issue as well as some internal corruption of state. Additionally repaired observed race condition which could occur when "cloning" an expression construct that is also in the process of being compiled or otherwise accessed in a different thread due to memoized attributes altering the ``__dict__`` while iterated, for Python versions prior to 3.10; in particular the lambda SQL construct is sensitive to this as it holds onto a single statement object persistently. The iteration has been refined to use ``dict.copy()`` with or without an additional iteration instead. Fixes: #8098 Change-Id: I4e0b627bfa187f1780dc68ec81b94db1c78f846a
Diffstat (limited to 'lib/sqlalchemy/sql/elements.py')
-rw-r--r--lib/sqlalchemy/sql/elements.py9
1 files changed, 8 insertions, 1 deletions
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index 6032253c2..57cbdd8fc 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -390,7 +390,14 @@ class ClauseElement(
skip = self._memoized_keys
c = self.__class__.__new__(self.__class__)
- c.__dict__ = {k: v for k, v in self.__dict__.items() if k not in skip}
+
+ if skip:
+ # ensure this iteration remains atomic
+ c.__dict__ = {
+ k: v for k, v in self.__dict__.copy().items() if k not in skip
+ }
+ else:
+ c.__dict__ = self.__dict__.copy()
# this is a marker that helps to "equate" clauses to each other
# when a Select returns its list of FROM clauses. the cloning