From 56ab4405b851f3b6016e6716baf363766e9c4e97 Mon Sep 17 00:00:00 2001 From: Jeremy Hylton <jeremy@svn.zope.org> Date: Fri, 12 Apr 2002 19:59:55 +0000 Subject: [PATCH] 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. --- src/ZODB/Transaction.py | 327 +++++++++++++++++++++------------------- 1 file changed, 172 insertions(+), 155 deletions(-) diff --git a/src/ZODB/Transaction.py b/src/ZODB/Transaction.py index 795e315a..8bef8ea3 100644 --- a/src/ZODB/Transaction.py +++ b/src/ZODB/Transaction.py @@ -13,8 +13,8 @@ ############################################################################## """Transaction management -$Id: Transaction.py,v 1.35 2002/04/12 18:29:15 jeremy Exp $""" -__version__='$Revision: 1.35 $'[11:-2] +$Id: Transaction.py,v 1.36 2002/04/12 19:59:55 jeremy Exp $""" +__version__='$Revision: 1.36 $'[11:-2] import time, sys, struct, POSException from struct import pack @@ -145,193 +145,196 @@ class Transaction: def commit(self, subtransaction=None): '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) @@ -351,6 +354,20 @@ class Transaction: 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. +""" + + ############################################################################ # install get_transaction: -- GitLab