[CalendarServer-changes] [11734] CalendarServer/trunk/twext/enterprise

source_changes at macosforge.org source_changes at macosforge.org
Tue Sep 17 16:41:33 PDT 2013


Revision: 11734
          http://trac.calendarserver.org//changeset/11734
Author:   glyph at apple.com
Date:     2013-09-17 16:41:33 -0700 (Tue, 17 Sep 2013)
Log Message:
-----------
Address a memory leak with circular references in commit hooks.

Modified Paths:
--------------
    CalendarServer/trunk/twext/enterprise/adbapi2.py
    CalendarServer/trunk/twext/enterprise/test/test_adbapi2.py

Modified: CalendarServer/trunk/twext/enterprise/adbapi2.py
===================================================================
--- CalendarServer/trunk/twext/enterprise/adbapi2.py	2013-09-17 23:41:32 UTC (rev 11733)
+++ CalendarServer/trunk/twext/enterprise/adbapi2.py	2013-09-17 23:41:33 UTC (rev 11734)
@@ -84,6 +84,15 @@
 
 
 
+def _destructively(aList):
+    """
+    Destructively iterate a list, popping elements from the beginning.
+    """
+    while aList:
+        yield aList.pop(0)
+
+
+
 def _deriveParameters(cursor, args):
     """
     Some DB-API extensions need to call special extension methods on
@@ -439,8 +448,7 @@
         a Deferred to not interfere with the originally submitted order of
         commands.
         """
-        while self._spool:
-            yield self._spool.pop(0)
+        return _destructively(self._spool)
 
 
     def _unspool(self, other):
@@ -489,7 +497,7 @@
         """
         Callback for C{commit} and C{abort} Deferreds.
         """
-        for operation in self._hooks:
+        for operation in _destructively(self._hooks):
             yield operation()
         returnValue(ignored)
 
@@ -501,7 +509,14 @@
         self._hooks.append(operation)
 
 
+    def clear(self):
+        """
+        Remove all hooks from this operation.
+        """
+        del self._hooks[:]
 
+
+
 class _CommitAndAbortHooks(object):
     """
     Shared implementation of post-commit and post-abort hooks.
@@ -668,6 +683,8 @@
 
     def abort(self):
         self._markComplete()
+        self._commit.clear()
+        self._preCommit.clear()
         result = super(_SingleTxn, self).abort()
         if self in self._pool._waiting:
             self._stopWaiting()
@@ -1595,6 +1612,8 @@
 
 
     def abort(self):
+        self._commit.clear()
+        self._preCommit.clear()
         return self._complete(Abort).addCallback(self._abort.runHooks)
 
 

Modified: CalendarServer/trunk/twext/enterprise/test/test_adbapi2.py
===================================================================
--- CalendarServer/trunk/twext/enterprise/test/test_adbapi2.py	2013-09-17 23:41:32 UTC (rev 11733)
+++ CalendarServer/trunk/twext/enterprise/test/test_adbapi2.py	2013-09-17 23:41:33 UTC (rev 11734)
@@ -18,6 +18,8 @@
 Tests for L{twext.enterprise.adbapi2}.
 """
 
+import gc
+
 from zope.interface.verify import verifyObject
 
 from twisted.python.failure import Failure
@@ -44,6 +46,35 @@
 from twext.enterprise.fixtures import CommitFail
 from twext.enterprise.adbapi2 import Commit
 
+
+class TrashCollector(object):
+    """
+    Test helper for monitoring gc.garbage.
+    """
+    def __init__(self, testCase):
+        self.testCase = testCase
+        testCase.addCleanup(self.checkTrash)
+        self.start()
+
+
+    def start(self):
+        gc.collect()
+        self.garbageStart = len(gc.garbage)
+
+
+    def checkTrash(self):
+        """
+        Ensure that the test has added no additional garbage.
+        """
+        gc.collect()
+        newGarbage = gc.garbage[self.garbageStart:]
+        if newGarbage:
+            # Don't clean up twice.
+            self.start()
+            self.testCase.fail("New garbage: " + repr(newGarbage))
+
+
+
 class AssertResultHelper(object):
     """
     Mixin for asserting about synchronous Deferred results.
@@ -466,7 +497,6 @@
         """
         t = self.createTransaction()
         self.resultOf(t.execSQL("echo", []))
-        import gc
         conns = self.factory.connections
         self.assertEquals(len(conns), 1)
         self.assertEquals(conns[0]._rollbackCount, 0)
@@ -478,6 +508,42 @@
         self.assertEquals(conns[0]._commitCount, 0)
 
 
+    def circularReferenceTest(self, finish):
+        """
+        Collecting a completed (committed or aborted) L{IAsyncTransaction}
+        should not leak any circular references.
+        """
+        tc = TrashCollector(self)
+        commitExecuted = []
+        def carefullyManagedScope():
+            t = self.createTransaction()
+            def holdAReference():
+                """
+                This is a hook that holds a reference to 't'.
+                """
+                commitExecuted.append(True)
+                return t.execSQL("teardown", [])
+            t.preCommit(holdAReference)
+            finish(t)
+        self.failIf(commitExecuted, "Commit hook executed.")
+        carefullyManagedScope()
+        tc.checkTrash()
+
+
+    def test_noGarbageOnCommit(self):
+        """
+        Committing a transaction does not cause gc garbage.
+        """
+        self.circularReferenceTest(lambda txn: txn.commit())
+
+
+    def test_noGarbageOnAbort(self):
+        """
+        Aborting a transaction does not cause gc garbage.
+        """
+        self.circularReferenceTest(lambda txn: txn.abort())
+
+
     def test_tooManyConnectionsWhileOthersFinish(self):
         """
         L{ConnectionPool.connection} will not spawn more than the maximum
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130917/823a7e05/attachment-0001.html>


More information about the calendarserver-changes mailing list