I noticed some weird behavior with exceptions and transactions recently
(in particular, a WSGI server which relies on a __del__ to close its
client connection wasn't disconnecting from the client immediately when
a conflict error was raised by ZODB, but would indeed disconnect if a
conflict error had not been raised).

I tracked this down to the zope transaction module leaking frame stacks
via retrieving traceback objects from sys.exc_info() without
subsequently removing them from local scope.

With the attached patch, the issue was solved.  If no one complains,
I'll apply it to the transaction trunk.


- C


Index: transaction/_transaction.py
===================================================================
--- transaction/_transaction.py	(revision 116336)
+++ transaction/_transaction.py	(working copy)
@@ -329,9 +329,15 @@
             self._commitResources()
             self.status = Status.COMMITTED
         except:
-            t, v, tb = self._saveAndGetCommitishError()
-            self._callAfterCommitHooks(status=False)
-            raise t, v, tb
+            t = None
+            v = None
+            tb = None
+            try:
+                t, v, tb = self._saveAndGetCommitishError()
+                self._callAfterCommitHooks(status=False)
+                raise t, v, tb
+            finally:
+                del t, v, tb
         else:
             if self._manager:
                 self._manager.free(self)
@@ -343,14 +349,18 @@
         self.status = Status.COMMITFAILED
         # Save the traceback for TransactionFailedError.
         ft = self._failure_traceback = StringIO()
-        t, v, tb = sys.exc_info()
-        # Record how we got into commit().
-        traceback.print_stack(sys._getframe(1), None, ft)
-        # Append the stack entries from here down to the exception.
-        traceback.print_tb(tb, None, ft)
-        # Append the exception type and value.
-        ft.writelines(traceback.format_exception_only(t, v))
-        return t, v, tb
+        try:
+            t, v, tb = sys.exc_info()
+            # Record how we got into commit().
+            traceback.print_stack(sys._getframe(1), None, ft)
+            # Append the stack entries from here down to the exception.
+            traceback.print_tb(tb, None, ft)
+            # Append the exception type and value.
+            ft.writelines(traceback.format_exception_only(t, v))
+            return t, v, tb
+        finally:
+            del t, v, tb
+        
 
     def _saveAndRaiseCommitishError(self):
         t, v, tb = self._saveAndGetCommitishError()
@@ -442,10 +452,13 @@
             # to revert the changes in each of the resource managers.
             t, v, tb = sys.exc_info()
             try:
-                self._cleanup(L)
+                try:
+                    self._cleanup(L)
+                finally:
+                    self._synchronizers.map(lambda s: s.afterCompletion(self))
+                raise t, v, tb
             finally:
-                self._synchronizers.map(lambda s: s.afterCompletion(self))
-            raise t, v, tb
+                del t, v, tb
 
     def _cleanup(self, L):
         # Called when an exception occurs during tpc_vote or tpc_finish.
@@ -469,26 +482,33 @@
 
         self._synchronizers.map(lambda s: s.beforeCompletion(self))
 
-        tb = None
-        for rm in self._resources:
-            try:
-                rm.abort(self)
-            except:
-                if tb is None:
-                    t, v, tb = sys.exc_info()
-                self.log.error("Failed to abort resource manager: %s",
-                               rm, exc_info=sys.exc_info())
+        try:
 
-        if self._manager:
-            self._manager.free(self)
+            t = None
+            v = None
+            tb = None
+            
+            for rm in self._resources:
+                try:
+                    rm.abort(self)
+                except:
+                    if tb is None:
+                        t, v, tb = sys.exc_info()
+                    self.log.error("Failed to abort resource manager: %s",
+                                   rm, exc_info=sys.exc_info())
 
-        self._synchronizers.map(lambda s: s.afterCompletion(self))
+            if self._manager:
+                self._manager.free(self)
 
-        self.log.debug("abort")
+            self._synchronizers.map(lambda s: s.afterCompletion(self))
 
-        if tb is not None:
-            raise t, v, tb
+            self.log.debug("abort")
 
+            if tb is not None:
+                raise t, v, tb
+        finally:
+            del t, v, tb
+
     def note(self, text):
         text = text.strip()
         if self.description:
@@ -542,20 +562,26 @@
         self.manager.tpc_vote(txn)
 
     def abort(self, txn):
+        t = None
+        v = None
         tb = None
-        for o in self.objects:
-            try:
-                self.manager.abort(o, txn)
-            except:
-                # Capture the first exception and re-raise it after
-                # aborting all the other objects.
-                if tb is None:
-                    t, v, tb = sys.exc_info()
-                txn.log.error("Failed to abort object: %s",
-                              object_hint(o), exc_info=sys.exc_info())
-        if tb is not None:
-            raise t, v, tb
+        try:
+            for o in self.objects:
+                try:
+                    self.manager.abort(o, txn)
+                except:
+                    # Capture the first exception and re-raise it after
+                    # aborting all the other objects.
+                    if tb is None:
+                        t, v, tb = sys.exc_info()
+                    txn.log.error("Failed to abort object: %s",
+                                  object_hint(o), exc_info=sys.exc_info())
 
+            if tb is not None:
+                raise t, v, tb
+        finally:
+            del t, v, tb
+
 def rm_cmp(rm1, rm2):
     return cmp(rm1.sortKey(), rm2.sortKey())
 
_______________________________________________
For more information about ZODB, see the ZODB Wiki:
http://www.zope.org/Wikis/ZODB/

ZODB-Dev mailing list  -  ZODB-Dev@zope.org
https://mail.zope.org/mailman/listinfo/zodb-dev

Reply via email to