Commit 56ab4405 authored by Jeremy Hylton's avatar Jeremy Hylton

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.
parent 722fc658
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
############################################################################## ##############################################################################
"""Transaction management """Transaction management
$Id: Transaction.py,v 1.35 2002/04/12 18:29:15 jeremy Exp $""" $Id: Transaction.py,v 1.36 2002/04/12 19:59:55 jeremy Exp $"""
__version__='$Revision: 1.35 $'[11:-2] __version__='$Revision: 1.36 $'[11:-2]
import time, sys, struct, POSException import time, sys, struct, POSException
from struct import pack from struct import pack
...@@ -145,193 +145,196 @@ class Transaction: ...@@ -145,193 +145,196 @@ class Transaction:
def commit(self, subtransaction=None): def commit(self, subtransaction=None):
'Finalize the transaction' 'Finalize the transaction'
global hosed objects = self._objects
jars = {}
objects=self._objects
jars={}
jarsv = None jarsv = None
subj=self._sub subj = self._sub
subjars=() subjars = ()
if subtransaction: if subtransaction:
if subj is None: self._sub=subj={} if subj is None:
self._sub = subj = {}
else: else:
if subj is not None: if subj is not None:
if objects: if objects:
# Do an implicit sub-transaction commit: # Do an implicit sub-transaction commit:
self.commit(1) self.commit(1)
objects=[] # XXX What does this do?
subjars=subj.values() objects = []
self._sub=None subjars = subj.values()
self._sub = None
# If not a subtransaction, then we need to add any non- # If not a subtransaction, then we need to add any non-
# subtransaction-supporting objects that may have been # subtransaction-supporting objects that may have been
# stowed away during subtransaction commits to _objects. # stowed away during subtransaction commits to _objects.
if (subtransaction is None) and (self._non_st_objects is not None): if (subtransaction is None) and (self._non_st_objects is not None):
append=objects.append objects.extend(self._non_st_objects)
for object in self._non_st_objects:
append(object)
self._non_st_objects = None self._non_st_objects = None
t=v=tb=None
if (objects or subjars) and hosed: if (objects or subjars) and hosed:
# Something really bad happened and we don't # Something really bad happened and we don't
# trust the system state. # trust the system state.
raise POSException.TransactionError, ( raise POSException.TransactionError, hosed_msg
"""A serious error, which was probably a system error, # It's important that:
occurred in a previous database transaction. This #
application may be in an invalid state and must be # - Every object in self._objects is either committed or
restarted before database updates can be allowed. # aborted.
#
Beware though that if the error was due to a serious # - For each object that is committed we call tpc_begin on
system problem, such as a disk full condition, then # it's jar at least once
the application may not come up until you deal with #
the system problem. See your application log for # - For every jar for which we've called tpc_begin on, we
information on the error that lead to this problem. # 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: try:
ncommitted = 0
# 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
try: try:
for o in objects: ncommitted += self._commit_objects(objects, jars,
j=getattr(o, '_p_jar', o) subtransaction, subj)
if j is not None:
i=id(j) self._commit_subtrans(jars, subjars)
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)
jarsv = jars.values() jarsv = jars.values()
for jar in jarsv: for jar in jarsv:
if not subtransaction: if not subtransaction:
try: jar=jar.tpc_vote try:
except: pass vote = jar.tpc_vote
else: jar(self) # last chance to bail except:
pass
try: else:
# Try to finish one jar, since we may be able to vote(self) # last chance to bail
# recover if the first one fails.
if jarsv: # Try to finish one jar, since we may be able to
jarsv[-1].tpc_finish(self) # This should never fail # recover if the first one fails.
jarsv.pop() # It didn't, so it's taken care of. self._finish_one(jarsv)
except: # Once a single jar has finished, it's a fatal (hosed)
# Bug if it does, we need to keep track of it # error if another jar fails.
LOG('ZODB', ERROR, self._finish_rest(jarsv)
"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
except: except:
t, v, tb = sys.exc_info()
# Ugh, we got an got an error during commit, so we # Ugh, we got an got an error during commit, so we
# have to clean up. # have to clean up.
exc_info = sys.exc_info()
# 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.
if jarsv is None: if jarsv is None:
jarsv = jars.values() jarsv = jars.values()
for j in jarsv: self._commit_error(exc_info, objects, ncommitted,
try: jarsv, subjars)
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
finally: finally:
tb = None
del objects[:] # clear registered del objects[:] # clear registered
if not subtransaction and self._id is not None: if not subtransaction and self._id is not None:
free_transaction() 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): def register(self,object):
'Register the given object for transaction control.' 'Register the given object for transaction control.'
self._append(object) self._append(object)
...@@ -351,6 +354,20 @@ class Transaction: ...@@ -351,6 +354,20 @@ class Transaction:
ext=self._extension={} ext=self._extension={}
ext[name]=value 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: # install get_transaction:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment