[Zope-dev] ZPatterns: commitSubtransaction() gives AttributeError on commit_sub

Brian Lloyd brian@digicool.com
Mon, 26 Feb 2001 16:19:59 -0500


This is a multi-part message in MIME format.

------=_NextPart_000_0003_01C0A00F.F6BD7960
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

> req = context.REQUEST
> context.propertysheets.info.manage_changeProperties(longdesc=req.f
> orm['longdesc'])
> context.commitSubtransaction()
> req.RESPONSE.redirect(req['URL1']+'/edit_longdescForm')
> 
> context is a DataSkin instance.  When I submit the form, I get an
> Attribute Error on commit_sub (traceback below in case it helps
> anyone).  As far as I can see from the ZPattern's source, I'm making
> the call correctly and it is an appropriate call.  Any clues?
> 
> This is Zope 2.3.1b1 and Steve's convenience release of ZPatterns.
> 
> --RDM
> 
> Traceback:

> AttributeError: (see above)

I believe we've tracked this down and fixed it properly - the 
fix will be in 2.3.1 b2, but I've a attached a copy of the 
updated Transaction.py that you can drop into lib/python/ZODB, 
then restart. It would be great if you get a chance to confirm 
the fix and let me know how it goes...

Note I've cc'ed a few other folks who have run into this 
specific problem - if any of you can spare a few moments 
to verify the fix and let me know if you still see any 
problems, I'd really appreciate it.

Thanks!


Brian Lloyd        brian@digicool.com
Software Engineer  540.371.6909              
Digital Creations  http://www.digicool.com 

------=_NextPart_000_0003_01C0A00F.F6BD7960
Content-Type: text/plain;
	name="Transaction.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="Transaction.py"

#########################################################################=
#####=0A=
# =0A=
# Zope Public License (ZPL) Version 1.0=0A=
# -------------------------------------=0A=
# =0A=
# Copyright (c) Digital Creations.  All rights reserved.=0A=
# =0A=
# This license has been certified as Open Source(tm).=0A=
# =0A=
# Redistribution and use in source and binary forms, with or without=0A=
# modification, are permitted provided that the following conditions are=0A=
# met:=0A=
# =0A=
# 1. Redistributions in source code must retain the above copyright=0A=
#    notice, this list of conditions, and the following disclaimer.=0A=
# =0A=
# 2. Redistributions in binary form must reproduce the above copyright=0A=
#    notice, this list of conditions, and the following disclaimer in=0A=
#    the documentation and/or other materials provided with the=0A=
#    distribution.=0A=
# =0A=
# 3. Digital Creations requests that attribution be given to Zope=0A=
#    in any manner possible. Zope includes a "Powered by Zope"=0A=
#    button that is installed by default. While it is not a license=0A=
#    violation to remove this button, it is requested that the=0A=
#    attribution remain. A significant investment has been put=0A=
#    into Zope, and this effort will continue if the Zope community=0A=
#    continues to grow. This is one way to assure that growth.=0A=
# =0A=
# 4. All advertising materials and documentation mentioning=0A=
#    features derived from or use of this software must display=0A=
#    the following acknowledgement:=0A=
# =0A=
#      "This product includes software developed by Digital Creations=0A=
#      for use in the Z Object Publishing Environment=0A=
#      (http://www.zope.org/)."=0A=
# =0A=
#    In the event that the product being advertised includes an=0A=
#    intact Zope distribution (with copyright and license included)=0A=
#    then this clause is waived.=0A=
# =0A=
# 5. Names associated with Zope or Digital Creations must not be used to=0A=
#    endorse or promote products derived from this software without=0A=
#    prior written permission from Digital Creations.=0A=
# =0A=
# 6. Modified redistributions of any form whatsoever must retain=0A=
#    the following acknowledgment:=0A=
# =0A=
#      "This product includes software developed by Digital Creations=0A=
#      for use in the Z Object Publishing Environment=0A=
#      (http://www.zope.org/)."=0A=
# =0A=
#    Intact (re-)distributions of any official Zope release do not=0A=
#    require an external acknowledgement.=0A=
# =0A=
# 7. Modifications are encouraged but must be packaged separately as=0A=
#    patches to official Zope releases.  Distributions that do not=0A=
#    clearly separate the patches from the original work must be clearly=0A=
#    labeled as unofficial distributions.  Modifications which do not=0A=
#    carry the name Zope may be packaged in any form, as long as they=0A=
#    conform to all of the clauses above.=0A=
# =0A=
# =0A=
# Disclaimer=0A=
# =0A=
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY=0A=
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE=0A=
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR=0A=
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS=0A=
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,=0A=
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT=0A=
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF=0A=
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND=0A=
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,=0A=
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT=0A=
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF=0A=
#   SUCH DAMAGE.=0A=
# =0A=
# =0A=
# This software consists of contributions made by Digital Creations and=0A=
# many individuals on behalf of Digital Creations.  Specific=0A=
# attributions are listed in the accompanying credits file.=0A=
# =0A=
#########################################################################=
#####=0A=
"""Transaction management=0A=
=0A=
$Id: Transaction.py,v 1.24.54.4 2001/02/26 20:32:02 brian Exp $"""=0A=
__version__=3D'$Revision: 1.24.54.4 $'[11:-2]=0A=
=0A=
import time, sys, struct, POSException=0A=
from struct import pack=0A=
from string import split, strip, join=0A=
from zLOG import LOG, ERROR, PANIC=0A=
from POSException import ConflictError=0A=
=0A=
# Flag indicating whether certain errors have occurred.=0A=
hosed=3D0=0A=
=0A=
class Transaction:=0A=
    'Simple transaction objects for single-threaded applications.'=0A=
    user=3D''=0A=
    description=3D''=0A=
    _connections=3DNone=0A=
    _extension=3DNone=0A=
    _sub=3DNone # This is a subtrasaction flag=0A=
=0A=
    # The _non_st_objects variable is either None or a list=0A=
    # of jars that do not support subtransactions. This is used to=0A=
    # manage non-subtransaction-supporting jars during subtransaction=0A=
    # commits and aborts to ensure that they are correctly committed=0A=
    # or aborted in the "outside" transaction.=0A=
    _non_st_objects=3DNone=0A=
    =0A=
    def __init__(self, id=3DNone):=0A=
        self._id=3Did=0A=
        self._objects=3D[]=0A=
        self._append=3Dself._objects.append=0A=
=0A=
    def _init(self):=0A=
        self._objects=3D[]=0A=
        self._append=3Dself._objects.append=0A=
        self.user=3Dself.description=3D''=0A=
        if self._connections:=0A=
            for c in self._connections.values(): c.close()=0A=
            del self._connections=0A=
=0A=
    def sub(self):=0A=
        # Create a manually managed subtransaction for internal use=0A=
        r=3Dself.__class__()=0A=
        r.user=3Dself.user=0A=
        r.description=3Dself.description=0A=
        r._extension=3Dself._extension=0A=
        return r=0A=
        =0A=
    def __str__(self): return "%.3f\t%s" % (self._id, self.user)=0A=
=0A=
    def __del__(self):=0A=
        if self._objects: self.abort(freeme=3D0)=0A=
=0A=
    def abort(self, subtransaction=3D0, freeme=3D1):=0A=
        '''Abort the transaction.=0A=
=0A=
        This is called from the application.  This means that we haven\'t=0A=
        entered two-phase commit yet, so no tpc_ messages are sent.=0A=
        '''=0A=
=0A=
        if subtransaction and (self._non_st_objects is not None):=0A=
            raise POSException.TransactionError, (=0A=
                """Attempted to abort a sub-transaction, but a =
participating=0A=
                data manager doesn't support partial abort.=0A=
                """)=0A=
=0A=
        t=3Dv=3Dtb=3DNone=0A=
        subj=3Dself._sub=0A=
        subjars=3D()=0A=
=0A=
        if not subtransaction:=0A=
=0A=
            # Must add in any non-subtransaction supporting objects that=0A=
            # may have been stowed away from previous subtransaction=0A=
            # commits.=0A=
            if self._non_st_objects is not None:=0A=
                append=3Dself._objects.append=0A=
                for object in self._non_st_objects:=0A=
                    append(object)=0A=
                self._non_st_objects =3D None=0A=
=0A=
            if subj is not None:=0A=
                # Abort of top-level transaction after commiting=0A=
                # subtransactions.=0A=
                subjars=3Dsubj.values()=0A=
                self._sub=3DNone=0A=
=0A=
        try:=0A=
            # Abort the objects=0A=
            for o in self._objects:=0A=
                try:=0A=
                    j=3Dgetattr(o, '_p_jar', o)=0A=
                    if j is not None: j.abort(o, self)=0A=
                except:=0A=
                    if t is None:=0A=
                        t,v,tb=3Dsys.exc_info()=0A=
=0A=
            # Ugh, we need to abort work done in sub-transactions.=0A=
            while subjars:=0A=
                j=3Dsubjars.pop()=0A=
                j.abort_sub(self) # This should never fail=0A=
        =0A=
            if t is not None: raise t,v,tb=0A=
=0A=
        finally:=0A=
            tb=3DNone=0A=
            del self._objects[:] # Clear registered=0A=
            if not subtransaction and freeme:=0A=
                if self._id is not None: free_transaction()=0A=
            else: self._init()=0A=
=0A=
    def begin(self, info=3DNone, subtransaction=3DNone):=0A=
        '''Begin a new transaction.=0A=
=0A=
        This aborts any transaction in progres.=0A=
        '''=0A=
        if self._objects: self.abort(subtransaction, 0)=0A=
        if info:=0A=
            info=3Dsplit(info,'\t')=0A=
            self.user=3Dstrip(info[0])=0A=
            self.description=3Dstrip(join(info[1:],'\t'))=0A=
=0A=
    def commit(self, subtransaction=3DNone):=0A=
        'Finalize the transaction'=0A=
=0A=
        objects=3Dself._objects=0A=
        jars=3D{}=0A=
        jarsv =3D None=0A=
        subj=3Dself._sub=0A=
        subjars=3D()=0A=
=0A=
        if subtransaction:=0A=
            if subj is None: self._sub=3Dsubj=3D{}=0A=
        else:=0A=
            if subj is not None:=0A=
                if objects:=0A=
                    # Do an implicit sub-transaction commit:=0A=
                    self.commit(1)=0A=
                    objects=3D[]=0A=
                subjars=3Dsubj.values()=0A=
                self._sub=3DNone=0A=
=0A=
        # If not a subtransaction, then we need to add any non-=0A=
        # subtransaction-supporting objects that may have been=0A=
        # stowed away during subtransaction commits to _objects.=0A=
        if (subtransaction is None) and (self._non_st_objects is not =
None):=0A=
            append=3Dobjects.append=0A=
            for object in self._non_st_objects:=0A=
                append(object)=0A=
            self._non_st_objects =3D None=0A=
=0A=
        t=3Dv=3Dtb=3DNone=0A=
=0A=
        if (objects or subjars) and hosed:=0A=
            # Something really bad happened and we don't=0A=
            # trust the system state.=0A=
            raise POSException.TransactionError, (=0A=
                =0A=
                """A serious error, which was probably a system error,=0A=
                occurred in a previous database transaction.  This=0A=
                application may be in an invalid state and must be=0A=
                restarted before database updates can be allowed.=0A=
=0A=
                Beware though that if the error was due to a serious=0A=
                system problem, such as a disk full condition, then=0A=
                the application may not come up until you deal with=0A=
                the system problem.  See your application log for=0A=
                information on the error that lead to this problem.=0A=
                """)=0A=
=0A=
        try:=0A=
=0A=
            # It's important that:=0A=
            #=0A=
            # - Every object in self._objects is either committed=0A=
            #   or aborted.=0A=
            #=0A=
            # - For each object that is committed=0A=
            #   we call tpc_begin on it's jar at least once=0A=
            #=0A=
            # - For every jar for which we've called tpc_begin on,=0A=
            #   we either call tpc_abort or tpc_finish. It is OK=0A=
            #   to call these multiple times, as the storage is=0A=
            #   required to ignore these calls if tpc_begin has not=0A=
            #   been called.=0A=
            =0A=
            ncommitted=3D0=0A=
            try:=0A=
                for o in objects:=0A=
                    j=3Dgetattr(o, '_p_jar', o)=0A=
                    if j is not None:=0A=
                        i=3Did(j)=0A=
                        if not jars.has_key(i):=0A=
                            jars[i]=3Dj=0A=
                            if subtransaction:=0A=
=0A=
                                # If a jar does not support =
subtransactions,=0A=
                                # we need to save it away to be =
committed in=0A=
                                # the outer transaction.=0A=
                                try: j.tpc_begin(self, subtransaction)=0A=
                                except TypeError:=0A=
                                    j.tpc_begin(self)=0A=
=0A=
                                if hasattr(j, 'commit_sub'):=0A=
                                    subj[i]=3Dj=0A=
                                else:=0A=
                                    if self._non_st_objects is None:=0A=
                                        self._non_st_objects =3D []=0A=
                                    self._non_st_objects.append(o)=0A=
                                    continue=0A=
=0A=
                            else:=0A=
                                j.tpc_begin(self)=0A=
                        j.commit(o,self)=0A=
                    ncommitted=3Dncommitted+1=0A=
=0A=
                # Commit work done in subtransactions=0A=
                while subjars:=0A=
                    j=3Dsubjars.pop()=0A=
                    i=3Did(j)=0A=
                    if not jars.has_key(i):=0A=
                        jars[i]=3Dj=0A=
                    =0A=
                    j.commit_sub(self)=0A=
=0A=
                jarsv =3D jars.values()=0A=
                for jar in jarsv:=0A=
                    if not subtransaction:=0A=
                        try: jar=3Djar.tpc_vote=0A=
                        except: pass=0A=
                        else: jar(self) # last chance to bail=0A=
=0A=
                try:=0A=
                    # Try to finish one jar, since we may be able to=0A=
                    # recover if the first one fails.=0A=
                    if jarsv:=0A=
                        jarsv[-1].tpc_finish(self) # This should never =
fail=0A=
                        jarsv.pop() # It didn't, so it's taken care of.=0A=
                except:=0A=
                    # Bug if it does, we need to keep track of it=0A=
                    LOG('ZODB', ERROR,=0A=
                        "A storage error occurred in the last phase of a =
"=0A=
                        "two-phase commit.  This shouldn\'t happen. ",=0A=
                        error=3Dsys.exc_info())=0A=
                    raise=0A=
=0A=
                try:=0A=
                    while jarsv:=0A=
                        jarsv[-1].tpc_finish(self) # This should never =
fail=0A=
                        jarsv.pop() # It didn't, so it's taken care of.=0A=
                except:                        =0A=
                    # Bug if it does, we need to yell FIRE!=0A=
                    # Someone finished, so don't allow any more=0A=
                    # work without at least a restart!=0A=
                    global hosed=0A=
                    hosed=3D1=0A=
                    LOG('ZODB', PANIC,=0A=
                        "A storage error occurred in the last phase of a =
"=0A=
                        "two-phase commit.  This shouldn\'t happen. "=0A=
                        "The application may be in a hosed state, so "=0A=
                        "transactions will not be allowed to commit "=0A=
                        "until the site/storage is reset by a restart. ",=0A=
                        error=3Dsys.exc_info())=0A=
                    raise=0A=
                =0A=
            except:=0A=
                t,v,tb=3Dsys.exc_info()=0A=
=0A=
                # Ugh, we got an got an error during commit, so we=0A=
                # have to clean up.=0A=
=0A=
                # First, we have to abort any uncommitted objects.=0A=
                for o in objects[ncommitted:]:=0A=
                    try:=0A=
                        j=3Dgetattr(o, '_p_jar', o)=0A=
                        if j is not None: j.abort(o, self)=0A=
                    except: pass=0A=
=0A=
                # Then, we unwind TPC for the jars that began it.=0A=
                if jarsv is None:=0A=
                    jarsv =3D jars.values()=0A=
                for j in jarsv:=0A=
                    try: j.tpc_abort(self) # This should never fail=0A=
                    except: pass=0A=
=0A=
                # Ugh, we need to abort work done in sub-transactions.=0A=
                while subjars:=0A=
                    j=3Dsubjars.pop()=0A=
                    j.abort_sub(self) # This should never fail=0A=
=0A=
                raise t,v,tb=0A=
=0A=
        finally:=0A=
            tb=3DNone=0A=
            del objects[:] # clear registered=0A=
            if not subtransaction and self._id is not None: =
free_transaction()=0A=
=0A=
    def register(self,object):=0A=
        'Register the given object for transaction control.'=0A=
        self._append(object)=0A=
=0A=
    def note(self, text):=0A=
        if self.description:=0A=
            self.description =3D "%s\n\n%s" % (self.description, =
strip(text))=0A=
        else: =0A=
            self.description =3D strip(text)=0A=
    =0A=
    def setUser(self, user_name, path=3D'/'):=0A=
        self.user=3D"%s %s" % (path, user_name)=0A=
=0A=
    def setExtendedInfo(self, name, value):=0A=
        ext=3Dself._extension=0A=
        if ext is None:=0A=
            ext=3Dself._extension=3D{}=0A=
        ext[name]=3Dvalue=0A=
=0A=
=0A=
#########################################################################=
###=0A=
# install get_transaction:=0A=
=0A=
try:=0A=
    import thread=0A=
=0A=
except:=0A=
    _t=3DTransaction(None)=0A=
    def get_transaction(_t=3D_t): return _t=0A=
    def free_transaction(_t=3D_t): _t.__init__()=0A=
=0A=
else:=0A=
    _t=3D{}=0A=
    def get_transaction(_id=3Dthread.get_ident, _t=3D_t, get=3D_t.get, =
None=3DNone):=0A=
        id=3D_id()=0A=
        t=3Dget(id, None)=0A=
        if t is None: _t[id]=3Dt=3DTransaction(id)=0A=
        return t=0A=
=0A=
    def free_transaction(_id=3Dthread.get_ident, _t=3D_t):=0A=
        id=3D_id()=0A=
        try: del _t[id]=0A=
        except KeyError: pass=0A=
=0A=
    del thread=0A=
=0A=
del _t=0A=
=0A=
import __main__ =0A=
__main__.__builtins__.get_transaction=3Dget_transaction=0A=
    =0A=

------=_NextPart_000_0003_01C0A00F.F6BD7960--