]> err.no Git - dak/commitdiff
Avoid ressource leaks in ORMObject.clone().
authorTorsten Werner <twerner@debian.org>
Tue, 1 Feb 2011 09:45:19 +0000 (10:45 +0100)
committerTorsten Werner <twerner@debian.org>
Tue, 1 Feb 2011 09:45:19 +0000 (10:45 +0100)
Signed-off-by: Torsten Werner <twerner@debian.org>
daklib/dbconn.py
tests/dbtest_session.py

index df38b777831c019abfbd64dd394c3d564cd26d69..2237099d75eb296c44b95e478357c6d76ab82031 100755 (executable)
@@ -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)
index 72c2aff63faa1a6377023faf763290b08933f1fc..3a8d3cf2e0a747abfe9f3770467338db94d5814c 100755 (executable)
@@ -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.