-
Notifications
You must be signed in to change notification settings - Fork 192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: close sqla connections when unloading profile #5728
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ltalirz . Is there no way through the API of sqlalchemy
to tell it to clear weak references? In PsqlDosBackend.close
we are calling engine.close()
and self._session_factory.expunge_all()
and self._session_factory.close()
. Maybe we are missing something here?
If this is nothing to do with sqlalchemy
and this is the only way, fine for me to add it, but since it is specific to the PsqlDosBackend
why not add it to its close
method? This is what is ultimately called when Manager.unload_profile
is called. Would also move the test from test_nodes.py
to test_backend.py
, which is more apt.
d767baf
to
1eaa081
Compare
I guess the problem is that a reference to the session object itself survives somewhere: from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import Session
from sqlalchemy.orm.session import _sessions
Base = declarative_base()
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
data = Column(String)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
a1 = A()
s.add(a1)
s.commit()
# object is not detached
assert not inspect(a1).detached
# close session, which will detach a1
s.close()
# object is detached
assert inspect(a1).detached
assert a1 not in s
assert len(_sessions) == 1
del a1
assert len(_sessions) == 1
e.dispose()
assert len(_sessions) == 1
s.expunge_all()
assert len(_sessions) == 1
del s
assert len(_sessions) == 0 Looking at the code it's not entirely clear to me how this happens (I tried aiida-core/aiida/storage/psql_dos/backend.py Lines 135 to 145 in 16ae9ae
I incorporated your other suggestions - thanks! |
P.S. The original issue raised by Joe was about an error "database ... is being accessed by other users". It almost seems like some other place in AiiDA grabs this session and does stuff to it. |
Note: if I don't load the profile again after unloading it, numerous of the following tests fail with errors like
Logs example: https://github.com/aiidateam/aiida-core/actions/runs/3345686746/jobs/5541529122 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ltalirz . I think this fix might be fine for now, don't see how it can hurt. Still I wonder where the actual problem is. In the shell tests that you shared, you showed that once you delete the reference to the session with del session
the weakref was released. Looking at the PsqlDosBackend
, I noticed that we actually don't even keep track of sessions but simply call the factory each time. When I quickly switched this to create a single session and keep the reference and then in close
calling del
on it explicitly, the test you added also passed, even when I remove the explicit gc.collect
. The problem is though that this does not seem a 100% reproducible. Sometimes the test still fails. I am wondering if time plays a role here and sometimes the assert comes before the weakref has had the chance to be resolved. Anyway, I think we can still merge this for the time being after the test is updated.
When calling `Manager.unload_profile`, the `close` method is called on the `StorageBackend`. For the `PsqlDosBackend` this means cleaning up all SqlAlchemy connections and sessions. This was implemented, however, SqlAlchemy would often still be holding on to at least one session in the `WeakrefValueDictionary` of `sqlalchemy.orm.sessions._sessions`. It turns out however that the reference was already cleared in fact, as calling the garbage collector explicitly cleans the session references.
4a553bf
to
a99981e
Compare
Fixes #5506
After unloading an AiiDA profile in the psql_dos backend, sqlalchemy would keep a weakref to a connection alive (which points to a reference cycle somewhere).
Calling the garbage collector explicitly cleares the connections.