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,118 +145,129 @@ class Transaction: ...@@ -145,118 +145,129 @@ 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,
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:
# It's important that: # It's important that:
# #
# - Every object in self._objects is either committed # - Every object in self._objects is either committed or
# or aborted. # aborted.
# #
# - For each object that is committed # - For each object that is committed we call tpc_begin on
# we call tpc_begin on it's jar at least once # it's jar at least once
# #
# - For every jar for which we've called tpc_begin on, # - For every jar for which we've called tpc_begin on, we
# we either call tpc_abort or tpc_finish. It is OK # either call tpc_abort or tpc_finish. It is OK to call
# to call these multiple times, as the storage is # these multiple times, as the storage is required to ignore
# required to ignore these calls if tpc_begin has not # these calls if tpc_begin has not been called.
# 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: 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: for o in objects:
j=getattr(o, '_p_jar', o) j = getattr(o, '_p_jar', o)
if j is not None: if j is not None:
i=id(j) i = id(j)
if not jars.has_key(i): if not jars.has_key(i):
jars[i]=j jars[i] = j
if subtransaction:
if subtransaction:
# If a jar does not support subtransactions, # If a jar does not support subtransactions,
# we need to save it away to be committed in # we need to save it away to be committed in
# the outer transaction. # the outer transaction.
try: j.tpc_begin(self, subtransaction) try:
j.tpc_begin(self, subtransaction)
except TypeError: except TypeError:
j.tpc_begin(self) j.tpc_begin(self)
if hasattr(j, 'commit_sub'): if hasattr(j, 'commit_sub'):
subj[i]=j subj[i] = j
else: else:
if self._non_st_objects is None: if self._non_st_objects is None:
self._non_st_objects = [] self._non_st_objects = []
self._non_st_objects.append(o) self._non_st_objects.append(o)
continue continue
else: else:
j.tpc_begin(self) j.tpc_begin(self)
j.commit(o,self) j.commit(o, self)
ncommitted=ncommitted+1 ncommitted += 1
return ncommitted
def _commit_subtrans(self, jars, subjars):
# Commit work done in subtransactions # Commit work done in subtransactions
while subjars: while subjars:
j=subjars.pop() j = subjars.pop()
i=id(j) i = id(j)
if not jars.has_key(i): if not jars.has_key(i):
jars[i]=j jars[i] = j
j.commit_sub(self) j.commit_sub(self)
jarsv = jars.values() def _finish_one(self, jarsv):
for jar in jarsv:
if not subtransaction:
try: jar=jar.tpc_vote
except: pass
else: jar(self) # last chance to bail
try: try:
# Try to finish one jar, since we may be able to
# recover if the first one fails.
if jarsv: if jarsv:
jarsv[-1].tpc_finish(self) # This should never fail jarsv[-1].tpc_finish(self) # This should never fail
jarsv.pop() # It didn't, so it's taken care of. jarsv.pop() # It didn't, so it's taken care of.
...@@ -268,6 +279,8 @@ class Transaction: ...@@ -268,6 +279,8 @@ class Transaction:
error=sys.exc_info()) error=sys.exc_info())
raise raise
def _finish_rest(self, jarsv):
global hosed
try: try:
while jarsv: while jarsv:
jarsv[-1].tpc_finish(self) # This should never fail jarsv[-1].tpc_finish(self) # This should never fail
...@@ -286,11 +299,10 @@ class Transaction: ...@@ -286,11 +299,10 @@ class Transaction:
error=sys.exc_info()) error=sys.exc_info())
raise raise
except: def _commit_error(self, (t, v, tb),
t, v, tb = sys.exc_info() objects, ncommitted, jarsv, subjars):
# handle an exception raised during commit
# Ugh, we got an got an error during commit, so we # takes sys.exc_info() as argument
# have to clean up.
# First, we have to abort any uncommitted objects. # First, we have to abort any uncommitted objects.
for o in objects[ncommitted:]: for o in objects[ncommitted:]:
...@@ -302,16 +314,13 @@ class Transaction: ...@@ -302,16 +314,13 @@ class Transaction:
pass pass
# Then, we unwind TPC for the jars that began it. # Then, we unwind TPC for the jars that began it.
if jarsv is None:
jarsv = jars.values()
for j in jarsv: for j in jarsv:
try: try:
j.tpc_abort(self) # This should never fail j.tpc_abort(self) # This should never fail
except: except:
LOG('ZODB', ERROR, LOG('ZODB', ERROR,
"A storage error occured during object abort " "A storage error occured during object abort. This "
"This shouldn't happen. ", "shouldn't happen. ", error=sys.exc_info())
error=sys.exc_info())
# Ugh, we need to abort work done in sub-transactions. # Ugh, we need to abort work done in sub-transactions.
while subjars: while subjars:
...@@ -326,12 +335,6 @@ class Transaction: ...@@ -326,12 +335,6 @@ class Transaction:
raise t, v, tb 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): 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