[Zodb-checkins] CVS: StandaloneZODB/ZODB - Transaction.py:1.36

Jeremy Hylton jeremy@zope.com
Fri, 12 Apr 2002 15:59:56 -0400


Update of /cvs-repository/StandaloneZODB/ZODB
In directory cvs.zope.org:/tmp/cvs-serv26318

Modified Files:
	Transaction.py 
Log Message:
Refactor the commit() method.

Break up the logic into a bunch of helper methods:
 _commit_objects()
 _commit_subtrans()
 _finish_one()
 _finish_rest()
and possibly
 _commit_error()

As a result of the changes, the high-level logic of a commit fits into
28 lines inside a try/finally.  There are lots of details hidden in
the methods, but the names capture the high-level behavior of the
helpers.


=== StandaloneZODB/ZODB/Transaction.py 1.35 => 1.36 ===
         'Finalize the transaction'
 
-        global hosed
-        
-        objects=self._objects
-        jars={}
+        objects = self._objects
+        jars = {}
         jarsv = None
-        subj=self._sub
-        subjars=()
+        subj = self._sub
+        subjars = ()
 
         if subtransaction:
-            if subj is None: self._sub=subj={}
+            if subj is None:
+                self._sub = subj = {}
         else:
             if subj is not None:
                 if objects:
                     # Do an implicit sub-transaction commit:
                     self.commit(1)
-                    objects=[]
-                subjars=subj.values()
-                self._sub=None
+                    # XXX What does this do?
+                    objects = []
+                subjars = subj.values()
+                self._sub = None
 
         # If not a subtransaction, then we need to add any non-
         # subtransaction-supporting objects that may have been
         # stowed away during subtransaction commits to _objects.
         if (subtransaction is None) and (self._non_st_objects is not None):
-            append=objects.append
-            for object in self._non_st_objects:
-                append(object)
+            objects.extend(self._non_st_objects)
             self._non_st_objects = None
 
-        t=v=tb=None
-
         if (objects or subjars) and hosed:
             # Something really bad happened and we don't
             # trust the system state.
-            raise POSException.TransactionError, (
-                
-                """A serious error, which was probably a system error,
-                occurred in a previous database transaction.  This
-                application may be in an invalid state and must be
-                restarted before database updates can be allowed.
-
-                Beware though that if the error was due to a serious
-                system problem, such as a disk full condition, then
-                the application may not come up until you deal with
-                the system problem.  See your application log for
-                information on the error that lead to this problem.
-                """)
+            raise POSException.TransactionError, hosed_msg
 
+        # It's important that:
+        #
+        # - Every object in self._objects is either committed or
+        #   aborted.
+        #
+        # - For each object that is committed we call tpc_begin on
+        #   it's jar at least once
+        #
+        # - For every jar for which we've called tpc_begin on, we
+        #   either call tpc_abort or tpc_finish. It is OK to call
+        #   these multiple times, as the storage is required to ignore
+        #   these calls if tpc_begin has not been called.
         try:
-
-            # It's important that:
-            #
-            # - Every object in self._objects is either committed
-            #   or aborted.
-            #
-            # - For each object that is committed
-            #   we call tpc_begin on it's jar at least once
-            #
-            # - For every jar for which we've called tpc_begin on,
-            #   we either call tpc_abort or tpc_finish. It is OK
-            #   to call these multiple times, as the storage is
-            #   required to ignore these calls if tpc_begin has not
-            #   been called.
-            
-            ncommitted=0
+            ncommitted = 0
             try:
-                for o in objects:
-                    j=getattr(o, '_p_jar', o)
-                    if j is not None:
-                        i=id(j)
-                        if not jars.has_key(i):
-                            jars[i]=j
-                            if subtransaction:
-
-                                # If a jar does not support subtransactions,
-                                # we need to save it away to be committed in
-                                # the outer transaction.
-                                try: j.tpc_begin(self, subtransaction)
-                                except TypeError:
-                                    j.tpc_begin(self)
-
-                                if hasattr(j, 'commit_sub'):
-                                    subj[i]=j
-                                else:
-                                    if self._non_st_objects is None:
-                                        self._non_st_objects = []
-                                    self._non_st_objects.append(o)
-                                    continue
-
-                            else:
-                                j.tpc_begin(self)
-                        j.commit(o,self)
-                    ncommitted=ncommitted+1
-
-                # Commit work done in subtransactions
-                while subjars:
-                    j=subjars.pop()
-                    i=id(j)
-                    if not jars.has_key(i):
-                        jars[i]=j
-                    
-                    j.commit_sub(self)
+                ncommitted += self._commit_objects(objects, jars,
+                                                   subtransaction, subj)
+
+                self._commit_subtrans(jars, subjars)
 
                 jarsv = jars.values()
                 for jar in jarsv:
                     if not subtransaction:
-                        try: jar=jar.tpc_vote
-                        except: pass
-                        else: jar(self) # last chance to bail
-
-                try:
-                    # Try to finish one jar, since we may be able to
-                    # recover if the first one fails.
-                    if jarsv:
-                        jarsv[-1].tpc_finish(self) # This should never fail
-                        jarsv.pop() # It didn't, so it's taken care of.
-                except:
-                    # Bug if it does, we need to keep track of it
-                    LOG('ZODB', ERROR,
-                        "A storage error occurred in the last phase of a "
-                        "two-phase commit.  This shouldn\'t happen. ",
-                        error=sys.exc_info())
-                    raise
-
-                try:
-                    while jarsv:
-                        jarsv[-1].tpc_finish(self) # This should never fail
-                        jarsv.pop() # It didn't, so it's taken care of.
-                except:                        
-                    # Bug if it does, we need to yell FIRE!
-                    # Someone finished, so don't allow any more
-                    # work without at least a restart!
-                    hosed = 1
-                    LOG('ZODB', PANIC,
-                        "A storage error occurred in the last phase of a "
-                        "two-phase commit.  This shouldn\'t happen. "
-                        "The application may be in a hosed state, so "
-                        "transactions will not be allowed to commit "
-                        "until the site/storage is reset by a restart. ",
-                        error=sys.exc_info())
-                    raise
-                
+                        try:
+                            vote = jar.tpc_vote
+                        except:
+                            pass
+                        else:
+                            vote(self) # last chance to bail
+
+                # Try to finish one jar, since we may be able to
+                # recover if the first one fails.  
+                self._finish_one(jarsv)
+                # Once a single jar has finished, it's a fatal (hosed)
+                # error if another jar fails.
+                self._finish_rest(jarsv)
             except:
-                t, v, tb = sys.exc_info()
-
                 # Ugh, we got an got an error during commit, so we
                 # have to clean up.
-
-                # First, we have to abort any uncommitted objects.
-                for o in objects[ncommitted:]:
-                    try:
-                        j = getattr(o, '_p_jar', o)
-                        if j is not None:
-                            j.abort(o, self)
-                    except:
-                        pass
-
-                # Then, we unwind TPC for the jars that began it.
+                exc_info = sys.exc_info()
                 if jarsv is None:
                     jarsv = jars.values()
-                for j in jarsv:
-                    try:
-                        j.tpc_abort(self) # This should never fail
-                    except:     
-                        LOG('ZODB', ERROR,
-                            "A storage error occured during object abort "
-                            "This shouldn't happen. ",
-                            error=sys.exc_info())
-                
-                # Ugh, we need to abort work done in sub-transactions.
-                while subjars:
-                    j = subjars.pop()
-                    try:
-                        j.abort_sub(self) # This should never fail
-                    except:
-                        LOG('ZODB', ERROR,
-                            "A storage error occured during sub-transaction "
-                            "object abort.  This shouldn't happen.",
-                            error=sys.exc_info())
-
-                raise t, v, tb
-
+                self._commit_error(exc_info, objects, ncommitted,
+                                   jarsv, subjars)
         finally:
-            tb = None
             del objects[:] # clear registered
             if not subtransaction and self._id is not None:
                 free_transaction()
 
+    def _commit_objects(self, objects, jars, subtransaction, subj):
+        # commit objects and return number of commits
+        ncommitted = 0
+        for o in objects:
+            j = getattr(o, '_p_jar', o)
+            if j is not None:
+                i = id(j)
+                if not jars.has_key(i):
+                    jars[i] = j
+                    
+                    if subtransaction:
+                        # If a jar does not support subtransactions,
+                        # we need to save it away to be committed in
+                        # the outer transaction.
+                        try:
+                            j.tpc_begin(self, subtransaction)
+                        except TypeError:
+                            j.tpc_begin(self)
+
+                        if hasattr(j, 'commit_sub'):
+                            subj[i] = j
+                        else:
+                            if self._non_st_objects is None:
+                                self._non_st_objects = []
+                            self._non_st_objects.append(o)
+                            continue
+                    else:
+                        j.tpc_begin(self)
+                j.commit(o, self)
+            ncommitted += 1
+        return ncommitted
+
+    def _commit_subtrans(self, jars, subjars):
+        # Commit work done in subtransactions
+        while subjars:
+            j = subjars.pop()
+            i = id(j)
+            if not jars.has_key(i):
+                jars[i] = j
+            j.commit_sub(self)
+
+    def _finish_one(self, jarsv):
+        try:
+            if jarsv:
+                jarsv[-1].tpc_finish(self) # This should never fail
+                jarsv.pop() # It didn't, so it's taken care of.
+        except:
+            # Bug if it does, we need to keep track of it
+            LOG('ZODB', ERROR,
+                "A storage error occurred in the last phase of a "
+                "two-phase commit.  This shouldn\'t happen. ",
+                error=sys.exc_info())
+            raise
+
+    def _finish_rest(self, jarsv):
+        global hosed
+        try:
+            while jarsv:
+                jarsv[-1].tpc_finish(self) # This should never fail
+                jarsv.pop() # It didn't, so it's taken care of.
+        except:                        
+            # Bug if it does, we need to yell FIRE!
+            # Someone finished, so don't allow any more
+            # work without at least a restart!
+            hosed = 1
+            LOG('ZODB', PANIC,
+                "A storage error occurred in the last phase of a "
+                "two-phase commit.  This shouldn\'t happen. "
+                "The application may be in a hosed state, so "
+                "transactions will not be allowed to commit "
+                "until the site/storage is reset by a restart. ",
+                error=sys.exc_info())
+            raise
+            
+    def _commit_error(self, (t, v, tb),
+                      objects, ncommitted, jarsv, subjars):
+        # handle an exception raised during commit
+        # takes sys.exc_info() as argument
+        
+        # First, we have to abort any uncommitted objects.
+        for o in objects[ncommitted:]:
+            try:
+                j = getattr(o, '_p_jar', o)
+                if j is not None:
+                    j.abort(o, self)
+            except:
+                pass
+
+        # Then, we unwind TPC for the jars that began it.
+        for j in jarsv:
+            try:
+                j.tpc_abort(self) # This should never fail
+            except:     
+                LOG('ZODB', ERROR,
+                    "A storage error occured during object abort. This "
+                    "shouldn't happen. ", error=sys.exc_info())
+                
+        # Ugh, we need to abort work done in sub-transactions.
+        while subjars:
+            j = subjars.pop()
+            try:
+                j.abort_sub(self) # This should never fail
+            except:
+                LOG('ZODB', ERROR,
+                    "A storage error occured during sub-transaction "
+                    "object abort.  This shouldn't happen.",
+                    error=sys.exc_info())
+
+        raise t, v, tb
+
     def register(self,object):
         'Register the given object for transaction control.'
         self._append(object)
@@ -350,6 +353,20 @@
         if ext is None:
             ext=self._extension={}
         ext[name]=value
+
+hosed_msg = \
+"""A serious error, which was probably a system error,
+occurred in a previous database transaction.  This
+application may be in an invalid state and must be
+restarted before database updates can be allowed.
+
+Beware though that if the error was due to a serious
+system problem, such as a disk full condition, then
+the application may not come up until you deal with
+the system problem.  See your application log for
+information on the error that lead to this problem.
+"""
+          
 
 
 ############################################################################