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,118 +145,129 @@ 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.
""")
try:
raise POSException.TransactionError, hosed_msg
# It's important that:
#
# - Every object in self._objects is either committed
# or aborted.
# - 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 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.
# - 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:
ncommitted = 0
try:
ncommitted += self._commit_objects(objects, jars,
subtransaction, subj)
ncommitted=0
self._commit_subtrans(jars, subjars)
jarsv = jars.values()
for jar in jarsv:
if not subtransaction:
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:
# Ugh, we got an got an error during commit, so we
# have to clean up.
exc_info = sys.exc_info()
if jarsv is None:
jarsv = jars.values()
self._commit_error(exc_info, objects, ncommitted,
jarsv, subjars)
finally:
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)
j = getattr(o, '_p_jar', o)
if j is not None:
i=id(j)
i = id(j)
if not jars.has_key(i):
jars[i]=j
if subtransaction:
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)
try:
j.tpc_begin(self, subtransaction)
except TypeError:
j.tpc_begin(self)
if hasattr(j, 'commit_sub'):
subj[i]=j
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
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)
j = subjars.pop()
i = id(j)
if not jars.has_key(i):
jars[i]=j
jars[i] = j
j.commit_sub(self)
jarsv = jars.values()
for jar in jarsv:
if not subtransaction:
try: jar=jar.tpc_vote
except: pass
else: jar(self) # last chance to bail
def _finish_one(self, jarsv):
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.
......@@ -268,6 +279,8 @@ class Transaction:
error=sys.exc_info())
raise
def _finish_rest(self, jarsv):
global hosed
try:
while jarsv:
jarsv[-1].tpc_finish(self) # This should never fail
......@@ -286,11 +299,10 @@ class Transaction:
error=sys.exc_info())
raise
except:
t, v, tb = sys.exc_info()
# Ugh, we got an got an error during commit, so we
# have to clean up.
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:]:
......@@ -302,16 +314,13 @@ class Transaction:
pass
# Then, we unwind TPC for the jars that began it.
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())
"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:
......@@ -326,12 +335,6 @@ class Transaction:
raise t, v, tb
finally:
tb = None
del objects[:] # clear registered
if not subtransaction and self._id is not None:
free_transaction()
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