Closed
Description
SQLAlchemy version: 1.3
MCVE in CycleTest:
diff --git a/test/aaa_profiling/test_memusage.py b/test/aaa_profiling/test_memusage.py
index be825151d..8867f8447 100644
--- a/test/aaa_profiling/test_memusage.py
+++ b/test/aaa_profiling/test_memusage.py
@@ -1396,3 +1396,14 @@ class CycleTest(_fixtures.FixtureTest):
visit_binary_product(visit, expr)
go()
+
+ def test_session_transaction(self):
+ User, Address = self.classes("User", "Address")
+ configure_mappers()
+
+ @assert_cycles()
+ def go():
+ s = Session()
+ s.query(User).exists()
+
+ go()
Session creates SessionTransaction which has a ref to Session.
This ref cycle is particularly problematic because gc spills weakref WeakValueDictionary.remove all over the call stacks randomly. The WeakValueDictionary is created in orm.session
_sessions = weakref.WeakValueDictionary()
I have no idea how to fix this yet, but this issue is quite critical in my opinion. Thanks!
Metadata
Metadata
Assignees
Labels
Type
Projects
Relationships
Development
No branches or pull requests
Activity
carsonip commentedon Jan 2, 2020
A new SessionTransaction is started when Session.close() is called: (orm/session.py:595)
Although the old SessionTransaction.session = None (orm/session.py:598), a new cycle is created again.
If we are not fixing this, do we have a way to avoid this cycle in application code?
zzzeek commentedon Jan 2, 2020
I see that you mentioned the issue of weakref.refs being cleaned previously but I'm not understanding the problem right now. are you suggesting that WeakValueDictionary simply should not be used because the simple fact that weakrefs point to things that are removed is a performance issue ? can you show the reproduction case ?
zzzeek commentedon Jan 2, 2020
trying the test now.
carsonip commentedon Jan 2, 2020
They are designed to be cleaned. There is no performance problem. The problem I am facing is that they are not being cleaned when Session is closed, but only during gc because Session has a ref cycle. And due to the fact that gc may happen any time (especially when many objects are allocated), it messes up the call stack, showing random WeakValueDictionary.remove at the top of any call stacks.
zzzeek commentedon Jan 2, 2020
here's the fix:
and here's the test:
is there a reason s.close() would not be called on a Session before it is to be discarded?
carsonip commentedon Jan 2, 2020
I do call s.close() in finally, so there is no problem. But I have tried to do self.transaction = None. It fails some tests in the sqlalchemy test suite.
zzzeek commentedon Jan 2, 2020
oh ok, if the new transaction replaced itself then that would happen, let's see
carsonip commentedon Jan 2, 2020
self.transaction = None
would fix this issue and I feel like it is the right fix. But I just don't have enough understanding to fix the tests. :(Not sure if there are better fixes.
zzzeek commentedon Jan 2, 2020
Ok right there's the "autobegin" thing that has to be adjusted here.
zzzeek commentedon Jan 2, 2020
I can adjust this but I don't think it's appropriate for 1.3 because it is a change in documented behavior. "If this session were created with
autocommit=False
, a new transaction is immediately begun." that has to changezzzeek commentedon Jan 2, 2020
so in your application, make your own:
in 1.4 you won't need the above but it will also be harmless
13 remaining items