From b8f3795c314213197754bd476cc43c5dc2bb0b8d Mon Sep 17 00:00:00 2001 From: Torsten Werner Date: Tue, 1 Feb 2011 10:45:19 +0100 Subject: [PATCH] Avoid ressource leaks in ORMObject.clone(). Signed-off-by: Torsten Werner --- daklib/dbconn.py | 26 +++++++++++++++++--------- tests/dbtest_session.py | 3 +++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/daklib/dbconn.py b/daklib/dbconn.py index df38b777..2237099d 100755 --- a/daklib/dbconn.py +++ b/daklib/dbconn.py @@ -299,25 +299,33 @@ class ORMObject(object): ''' Clones the current object in a new session and returns the new clone. A fresh session is created if the optional session parameter is not - provided. + provided. The function will fail if a session is provided and has + unflushed changes. - RATIONALE: SQLAlchemy's session is not thread safe. This method allows - cloning of an existing object to allow several threads to work with - their own instances of an ORMObject. + RATIONALE: SQLAlchemy's session is not thread safe. This method clones + an existing object to allow several threads to work with their own + instances of an ORMObject. - WARNING: Only persistent (committed) objects can be cloned. + WARNING: Only persistent (committed) objects can be cloned. Changes + made to the original object that are not committed yet will get lost. + The session of the new object will always be rolled back to avoid + ressource leaks. ''' - if session is None: - session = DBConn().session() if self.session() is None: - raise RuntimeError('Method clone() failed for detached object:\n%s' % - self) + raise RuntimeError( \ + 'Method clone() failed for detached object:\n%s' % self) self.session().flush() mapper = object_mapper(self) primary_key = mapper.primary_key_from_instance(self) object_class = self.__class__ + if session is None: + session = DBConn().session() + elif len(session.new) + len(session.dirty) + len(session.deleted) > 0: + raise RuntimeError( \ + 'Method clone() failed due to unflushed changes in session.') new_object = session.query(object_class).get(primary_key) + session.rollback() if new_object is None: raise RuntimeError( \ 'Method clone() failed for non-persistent object:\n%s' % self) diff --git a/tests/dbtest_session.py b/tests/dbtest_session.py index 72c2aff6..3a8d3cf2 100755 --- a/tests/dbtest_session.py +++ b/tests/dbtest_session.py @@ -168,6 +168,9 @@ class SessionTestCase(DBDakTestCase): uid3 = uid1.clone(session = new_session) self.assertEqual(uid1.uid, uid3.uid) self.assertTrue(uid3 in new_session) + # test for ressource leaks with mass cloning + for _ in xrange(1, 1000): + uid1.clone() def classes_to_clean(self): # We need to clean all Uid objects in case some test fails. -- 2.39.2