[Zope-Checkins] CVS: Zope3/lib/python/Transaction - Exceptions.py:1.2 IDataManager.py:1.2 ITransaction.py:1.2 __init__.py:1.2 _defaultTransaction.py:1.2

Jim Fulton jim@zope.com
Mon, 10 Jun 2002 19:28:44 -0400


Update of /cvs-repository/Zope3/lib/python/Transaction
In directory cvs.zope.org:/tmp/cvs-serv17445/lib/python/Transaction

Added Files:
	Exceptions.py IDataManager.py ITransaction.py __init__.py 
	_defaultTransaction.py 
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.


=== Zope3/lib/python/Transaction/Exceptions.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+class TransactionError(Exception):
+    """An error occured due to normal transaction processing
+    """
+
+class ConflictError(TransactionError):
+    """Two transactions tried to modify the same object at once
+
+    This transaction should be resubmitted.
+    """


=== Zope3/lib/python/Transaction/IDataManager.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+try:
+    from Interface import Interface
+except ImportError:
+    class Interface: pass
+
+
+class IDataManager(Interface):
+    """Data management interface for storing objects transactionally
+
+    This is currently implemented by ZODB database connections.
+    """
+
+    def abort(object, transaction):
+        """Abort changes made to an object in a transaction"""
+    
+    def tpc_begin(transaction, subtransaction=0):
+        """Begin two-phase commit of a transaction
+
+        If a non-zero subtransaction flag is provided, then begin a
+        sub-transaction.
+        """
+        
+    def commit(object, transaction):
+        """Commit (tentatively) changes made to an object in a transaction
+
+        This method is called during the first stage of a two-phase commit
+        """
+
+    def tpc_vote(transaction):
+        """Promise to commit a transaction
+
+        This is the last chance to fail. A data manager should have
+        all changes saved in a recoverable fashion.
+        
+        Finishes the first phase of a two-phase commit.
+        """
+
+    def tpc_finish(transaction):
+        """Finish the transaction by permanently saving any tentative changes.
+
+        This *must not fail*.
+        """
+
+    def tpc_abort(transaction):
+        """Abort (rollback) any tentative commits performed in the transaction
+        """
+
+    # XXX subtransaction model is pretty primitive.
+    def abort_sub(transaction):
+        """Abort any sub-transaction changes"""
+
+
+    def commit_sub(transaction):
+        """Commit (tentatively) subtransaction changes
+
+        This method is called during the first stage of a two-phase commit
+        """        
+    


=== Zope3/lib/python/Transaction/ITransaction.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+try:
+    from Interface import Interface
+except ImportError:
+    class Interface: pass
+
+class ITransaction(Interface):
+    """Transaction objects
+
+    Application code typically gets these by calling
+    get_transaction().
+    """
+
+    def abort(subtransaction=0):
+        """Abort the current transaction
+
+        If subtransaction is true, then only abort the current subtransaction.
+        """
+
+    def begin(info=None, subtransaction=0):
+        """Begin a transaction
+
+        If info is specified, it must be a string and will be used to
+        initialize the transaction description.
+
+        If subtransaction is true, then begin subtransaction.
+        """
+
+    def commit(subtransaction=0):
+        """Commit a transaction
+
+        If subtransaction is true, then only abort the current subtransaction.
+        """
+
+    def register(object):
+        """Register the object with the current transaction.
+
+        The object may have a '_p_jar' attribute. If it has this
+        attribute then the attribute value may be 'None', or an object
+        that implements the IDataManager interface. If the value is
+        'None', then the object will not affect the current
+        transaction.
+
+        If the object doesn't have a '_p_jar' attribute, then the
+        object must implement the IDataManager interface itself.
+        """
+
+    def note(text):
+        """Add the text to the transaction description
+
+        If there previous description isn't empty, a blank line is
+        added before the new text.
+        """
+
+    def setUser(user_name, path="/"):
+        """Set the transaction user name.
+
+        The user is actually set to the path followed by a space and
+        the user name.
+        """
+
+    def setExtendedInfo(name, value):
+        """Set extended information
+        """


=== Zope3/lib/python/Transaction/__init__.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+def get_transaction():
+    return get_transaction_hook()
+
+from _defaultTransaction import get_transaction as get_transaction_hook
+
+from _defaultTransaction import Transaction


=== Zope3/lib/python/Transaction/_defaultTransaction.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""Transaction management
+
+$Id$
+"""
+import sys
+
+from Transaction.Exceptions import ConflictError, TransactionError
+from zLOG import LOG, ERROR, PANIC
+
+# Flag indicating whether certain errors have occurred.
+hosed = 0
+
+class Transaction:
+    """Simple transaction objects for single-threaded applications."""
+    
+    user = ""
+    description = ""
+    _connections = None
+    _extension = None
+    _sub = None # This is a subtrasaction flag
+
+    # The _non_st_objects variable is either None or a list
+    # of jars that do not support subtransactions. This is used to
+    # manage non-subtransaction-supporting jars during subtransaction
+    # commits and aborts to ensure that they are correctly committed
+    # or aborted in the "outside" transaction.
+    _non_st_objects = None
+    
+    def __init__(self, id=None):
+        self._id = id
+        self._objects = []
+
+    def _init(self):
+        self._objects = []
+        self.user = self.description = ""
+        if self._connections:
+            for c in self._connections.values():
+                c.close()
+            del self._connections
+
+    def sub(self):
+        # Create a manually managed subtransaction for internal use
+        r = self.__class__()
+        r.user = self.user
+        r.description = self.description
+        r._extension = self._extension
+        return r
+        
+    def __str__(self):
+        return "Transaction %s %s" % (self._id or 0, self.user)
+
+    def __del__(self):
+        if self._objects:
+            self.abort(freeme=0)
+
+    def abort(self, subtransaction=0, freeme=1):
+        """Abort the transaction.
+
+        This is called from the application.  This means that we haven't
+        entered two-phase commit yet, so no tpc_ messages are sent.
+        """
+        if subtransaction and (self._non_st_objects is not None):
+            raise TransactionError, (
+                """Attempted to abort a sub-transaction, but a participating
+                data manager doesn't support partial abort.
+                """)
+
+        t = None
+        subj = self._sub
+        subjars = ()
+
+        if not subtransaction:
+
+            # Must add in any non-subtransaction supporting objects that
+            # may have been stowed away from previous subtransaction
+            # commits.
+            if self._non_st_objects is not None:
+                self._objects.extend(self._non_st_objects)
+                self._non_st_objects = None
+
+            if subj is not None:
+                # Abort of top-level transaction after commiting
+                # subtransactions.
+                subjars = subj.values()
+                self._sub = None
+
+        try:
+            # Abort the objects
+            for o in self._objects:
+                try:
+                    j = getattr(o, '_p_jar', o)
+                    if j is not None:
+                        j.abort(o, self)
+                except:
+                    if t is None:
+                        t, v, tb = sys.exc_info()
+
+            # Ugh, we need to abort work done in sub-transactions.
+            while subjars:
+                j = subjars.pop()
+                j.abort_sub(self) # This should never fail
+        
+            if t is not None:
+                raise t, v, tb
+
+        finally:
+            if t is not None:
+                del tb # don't keep traceback in local variable
+            del self._objects[:] # Clear registered
+            if not subtransaction and freeme:
+                if self._id is not None:
+                    free_transaction()
+            else:
+                self._init()
+
+    def begin(self, info=None, subtransaction=None):
+        """Begin a new transaction.
+
+        This aborts any transaction in progres.
+        """
+        if self._objects:
+            self.abort(subtransaction, 0)
+        if info:
+            info = info.split("\t")
+            self.user = info[0].strip()
+            self.description = ("\t".join(info[1:])).strip()
+
+    def commit(self, subtransaction=None):
+        """Finalize the transaction."""
+
+        global hosed
+        
+        objects=self._objects
+        jars={}
+        jarsv = None
+        subj=self._sub
+        subjars=()
+
+        if subtransaction:
+            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
+
+        # 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)
+            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 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:
+
+            # 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:
+                for o in objects:
+                    j=getattr(o, '_p_jar', o)
+                    if j is not None:
+                        i=id(j)
+                        if not (i in jars):
+                            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 (i in jars):
+                        jars[i]=j
+                    j.commit_sub(self)
+
+                jarsv = jars.values()
+                for jar in jarsv:
+                    if not subtransaction:
+                        jar.tpc_vote(self)
+
+                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("Transaction", 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("Transaction", 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:
+                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.
+                if jarsv is None:
+                    jarsv = jars.values()
+                for j in jarsv:
+                    try: j.tpc_abort(self) # This should never fail
+                    except:     
+                        LOG("Transaction", 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()
+                    j.abort_sub(self) # This should never fail
+
+                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._objects.append(object)
+
+    def note(self, text):
+        if self.description:
+            self.description = "%s\n\n%s" % (self.description, text.strip())
+        else: 
+            self.description = text.strip()
+    
+    def setUser(self, user_name, path='/'):
+        self.user = "%s %s" % (path, user_name)
+
+    def setExtendedInfo(self, name, value):
+        if self._extension is None:
+            self._extension = {}
+        self._extension[name] = value
+
+
+############################################################################
+# install get_transaction:
+
+try:
+    import thread
+except:
+    _t = Transaction(None)
+    
+    def get_transaction():
+        return _t
+    
+    def free_transaction():
+        _t.__init__()
+        
+else:
+    _t = {}
+    def get_transaction():
+        id = thread.get_ident()
+        t = _t.get(id)
+        if t is None:
+            _t[id] = t = Transaction(id)
+        return t
+
+    def free_transaction():
+        id = thread.get_ident()
+        try:
+            del _t[id]
+        except KeyError:
+            pass
+
+__all__ = ["Transaction", "get_transaction", "free_transaction"]