[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[dak/master] Avoid ressource leaks in ORMObject.clone().



Signed-off-by: Torsten Werner <twerner@debian.org>
---
 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 df38b77..2237099 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 72c2aff..3a8d3cf 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.
-- 
1.5.6.5


Reply to: