[Zope3-checkins] CVS: Zope3/lib/python/Transaction - Manager.py:1.1 Transaction.py:1.1 IDataManager.py:1.3 ITransaction.py:1.3 __init__.py:1.3 _defaultTransaction.py:1.5

Jeremy Hylton jeremy@zope.com
Wed, 24 Jul 2002 19:02:54 -0400


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

Modified Files:
	IDataManager.py ITransaction.py __init__.py 
	_defaultTransaction.py 
Added Files:
	Manager.py Transaction.py 
Log Message:
Revise Transaction API based on some discussion on persistence SIG.

The core of the change is that objects register with their data
managers and data managers join the transaction.  This simplifies the
bookkeeping done by the transaction.

The new API is based on the TP monitor API discussed in Transaction
Processing by Gray and Reuter.  The rough correspondence between the
old API and the new API is:

prepare() was tpc_begin() + commit() + tpc_vote()
commit() was tpc_finish()
abort() was tpc_abort()
savepoint() was commit(1)

The abort() and commit() methods of the old API are gone.  It's up to
the data managers to do the per-object accounting.

The basic Transaction package now defines a Transaction object without
the ZODB metadata fields.  ZODB will grown its own Transaction that
extends the base class.

Use an explicit Manager class and subclass that implements policy
associating each thread with a different transaction.


=== Added File Zope3/lib/python/Transaction/Manager.py ===
from IDataManager import IRollback
from Transaction import Transaction, Status

# XXX need to change asserts of transaction status into explicit checks
# that raise some exception

# XXX need lots of error checking

class TransactionManager(object):

    txn_factory = Transaction

    def __init__(self):
        pass

    def new(self):
        return self.txn_factory(self)

    def commit(self, txn):
        assert txn._status is Status.ACTIVE
        prepare_ok = True
        for r in txn._resources:
            if prepare_ok and not r.prepare(txn):
                prepare_ok = False
        txn._status = Status.PREPARED
        if prepare_ok:
            self._commit(txn)
        else:
            self.abort(txn)

    def _commit(self, txn):
        # finish the two-phase commit
        for r in txn._resources:
            r.commit(txn)
        txn._status = Status.COMMITTED

    def abort(self, txn):
        assert txn._status in (Status.ACTIVE, Status.PREPARED)
        for r in txn._resources:
            r.abort(txn)
        txn._status = Status.ABORTED

    def savepoint(self, txn):
        return Rollback([r.savepoint(txn) for r in txn._resources])

class Rollback(object):

    __implements__ = IRollback

    def __init__(self, resources):
        self._resources = resources

    def rollback(self):
        for r in self._resources:
            r.rollback()

# make the transaction manager visible to client code
import thread

class ThreadedTransactionManager(TransactionManager):

    def __init__(self):
        self._pool = {}

    def new(self):
        tid = thread.get_ident()
        txn = self._pool.get(tid)
        if txn is None:
            txn = super(ThreadedTransactionManager, self).new()
            self._pool[tid] = txn
        return txn

    def _commit(self, txn):
        tid = thread.get_ident()
        assert self._pool[tid] is txn
        super(ThreadedTransactionManager, self)._commit(txn)
        del self._pool[tid]

    def abort(self, txn):
        tid = thread.get_ident()
        assert self._pool[tid] is txn
        super(ThreadedTransactionManager, self).abort(txn)
        del self._pool[tid]
            


=== Added File Zope3/lib/python/Transaction/Transaction.py ===
# XXX The fact that this module has the same name as the package makes
# explicit imports impossible elsewhere.  Pick a new name?

from ITransaction import ITransaction

class Set(dict):

    def add(self, k):
        self[k] = 1

class Status:

    ACTIVE = "Active"
    PREPARED = "Prepared"
    COMMITTED = "Committed"
    ABORTED = "Aborted"

class Transaction:

    __implements__ = ITransaction

    def __init__(self, manager=None, parent=None):
        self._manager = manager
        self._parent = None
        self._status = Status.ACTIVE
        self._resources = Set()

    def __repr__(self):
        return "<%s %X %s>" % (self.__class__.__name__, id(self), self._status)

    def begin(self, parent=None):
        """Begin a transaction.

        If parent is not None, it is the parent transaction for this one.
        """
        assert self._manager is not None
        if parent is not None:
            t = Transaction(_self.manager, self)
            return t

    def commit(self):
        """Commit a transaction."""
        assert self._manager is not None
        self._manager.commit(self)

    def abort(self):
        """Rollback to initial state."""
        assert self._manager is not None
        self._manager.abort(self)

    def savepoint(self):
        """Save current progress and return a savepoint."""
        assert self._manager is not None
        return self._manager.savepoint(self)

    def join(self, resource):
        """resource is participating in the transaction."""
        assert self._manager is not None
        self._resources.add(resource)

    def status(self):
        """Return the status of the transaction."""
        return self._status


=== Zope3/lib/python/Transaction/IDataManager.py 1.2 => 1.3 ===
     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
+    def prepare(transaction):
+        """Begin two-phase commit of a transaction.
 
-        This method is called during the first stage of a two-phase commit
+        DataManager should return True or False.
         """
-
-    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.
+    def abort(transaction):
+        """Abort changes made by transaction."""
+    
+    def commit(transaction):
+        """Commit changes made by transaction."""
 
-        This *must not fail*.
-        """
+    def savepoint(transaction):
+        """Do tentative commit of changes to this point.
 
-    def tpc_abort(transaction):
-        """Abort (rollback) any tentative commits performed in the transaction
+        Should return an object implementing IRollback
         """
-
-    # 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
-        """        
+        
+class IRollback(Interface):
     
+    def rollback():
+        """Rollback changes since savepoint."""


=== Zope3/lib/python/Transaction/ITransaction.py 1.2 => 1.3 ===
     get_transaction().
     """
 
-    def abort(subtransaction=0):
-        """Abort the current transaction
+    def abort():
+        """Abort the current transaction."""
 
-        If subtransaction is true, then only abort the current subtransaction.
-        """
+    def begin():
+        """Begin a transaction."""
 
-    def begin(info=None, subtransaction=0):
-        """Begin a transaction
+    def commit():
+        """Commit a transaction."""
 
-        If info is specified, it must be a string and will be used to
-        initialize the transaction description.
+    def join(resource):
+        """Join a resource manager to the current transaction."""
 
-        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
-        """
+    def status():
+        """Return status of the current transaction."""


=== Zope3/lib/python/Transaction/__init__.py 1.2 => 1.3 ===
 # FOR A PARTICULAR PURPOSE.
 # 
 ##############################################################################
-def get_transaction():
-    return get_transaction_hook()
 
-from _defaultTransaction import get_transaction as get_transaction_hook
+from Manager import ThreadedTransactionManager
 
-from _defaultTransaction import Transaction
+_manager = ThreadedTransactionManager()
+get_transaction = _manager.new
+
+def set_factory(factory):
+    _manager.txn_factory = factory


=== Zope3/lib/python/Transaction/_defaultTransaction.py 1.4 => 1.5 ===
             while jarsv:
                 jarsv[-1].tpc_finish(self) # This should never fail
                 jarsv.pop() # It didn't, so it's taken care of.
-        except:                        
+        except:
             # But if it does, we need to yell FIRE!  Someone finished,
             # so don't allow any more work without at least a restart!
             hosed = 1