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 @@
##############################################################################
"""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:
......
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