[Checkins] SVN: transaction/branches/sphinx/ Overlooked.

Tres Seaver cvs-admin at zope.org
Mon Dec 17 20:28:54 UTC 2012


Log message for revision 128705:
  Overlooked.

Changed:
  _U  transaction/branches/sphinx/
  A   transaction/branches/sphinx/docs/datamanager.rst
  A   transaction/branches/sphinx/docs/resourcemanager.rst

-=-
Added: transaction/branches/sphinx/docs/datamanager.rst
===================================================================
--- transaction/branches/sphinx/docs/datamanager.rst	                        (rev 0)
+++ transaction/branches/sphinx/docs/datamanager.rst	2012-12-17 20:28:54 UTC (rev 128705)
@@ -0,0 +1,391 @@
+Writing a Data Manager
+======================
+
+Simple Data Manager
+------------------
+
+.. doctest::
+
+   >>> from transaction.tests.SampleDataManager import DataManager
+
+This :class:`transaction.tests.SampleDataManager.DataManager` class
+provides a trivial data-manager implementation and docstrings to illustrate
+the the protocol and to provide a tool for writing tests.
+
+Our sample data manager has state that is updated through an inc
+method and through transaction operations.
+
+
+When we create a sample data manager:
+
+.. doctest::
+
+   >>> dm = DataManager()
+
+It has two bits of state, state:
+
+.. doctest::
+
+   >>> dm.state
+   0
+
+and delta:
+
+.. doctest::
+
+   >>> dm.delta
+   0
+
+Both of which are initialized to 0.  state is meant to model
+committed state, while delta represents tentative changes within a
+transaction.  We change the state by calling inc:
+
+.. doctest::
+
+   >>> dm.inc()
+
+which updates delta:
+
+.. doctest::
+
+   >>> dm.delta
+   1
+
+but state isn't changed until we commit the transaction:
+
+.. doctest::
+
+   >>> dm.state
+   0
+
+To commit the changes, we use 2-phase commit. We execute the first
+stage by calling prepare.  We need to pass a transation. Our
+sample data managers don't really use the transactions for much,
+so we'll be lazy and use strings for transactions:
+
+.. doctest::
+
+   >>> t1 = '1'
+   >>> dm.prepare(t1)
+
+The sample data manager updates the state when we call prepare:
+
+.. doctest::
+
+   >>> dm.state
+   1
+   >>> dm.delta
+   1
+
+This is mainly so we can detect some affect of calling the methods.
+
+Now if we call commit:
+
+.. doctest::
+
+   >>> dm.commit(t1)
+
+Our changes are"permanent".  The state reflects the changes and the
+delta has been reset to 0.
+
+.. doctest::
+
+   >>> dm.state
+   1
+   >>> dm.delta
+   0
+
+The :meth:`prepare` Method
+----------------------------
+
+Prepare to commit data
+
+.. doctest::
+
+   >>> dm = DataManager()
+   >>> dm.inc()
+   >>> t1 = '1'
+   >>> dm.prepare(t1)
+   >>> dm.commit(t1)
+   >>> dm.state
+   1
+   >>> dm.inc()
+   >>> t2 = '2'
+   >>> dm.prepare(t2)
+   >>> dm.abort(t2)
+   >>> dm.state
+   1
+
+It is en error to call prepare more than once without an intervening
+commit or abort:
+
+.. doctest::
+
+   >>> dm.prepare(t1)
+
+   >>> dm.prepare(t1)
+   Traceback (most recent call last):
+   ...
+   TypeError: Already prepared
+
+   >>> dm.prepare(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: Already prepared
+
+   >>> dm.abort(t1)
+
+If there was a preceeding savepoint, the transaction must match:
+
+.. doctest::
+
+   >>> rollback = dm.savepoint(t1)
+   >>> dm.prepare(t2)
+   Traceback (most recent call last):
+   ,,,
+   TypeError: ('Transaction missmatch', '2', '1')
+
+   >>> dm.prepare(t1)
+
+The :meth:`abort` method
+--------------------------
+
+The abort method can be called before two-phase commit to
+throw away work done in the transaction:
+
+.. doctest::
+
+   >>> dm = DataManager()
+   >>> dm.inc()
+   >>> dm.state, dm.delta
+   (0, 1)
+   >>> t1 = '1'
+   >>> dm.abort(t1)
+   >>> dm.state, dm.delta
+   (0, 0)
+
+The abort method also throws away work done in savepoints:
+
+.. doctest::
+
+   >>> dm.inc()
+   >>> r = dm.savepoint(t1)
+   >>> dm.inc()
+   >>> r = dm.savepoint(t1)
+   >>> dm.state, dm.delta
+   (0, 2)
+   >>> dm.abort(t1)
+   >>> dm.state, dm.delta
+   (0, 0)
+
+If savepoints are used, abort must be passed the same
+transaction:
+
+.. doctest::
+
+   >>> dm.inc()
+   >>> r = dm.savepoint(t1)
+   >>> t2 = '2'
+   >>> dm.abort(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Transaction missmatch', '2', '1')
+
+   >>> dm.abort(t1)
+
+The abort method is also used to abort a two-phase commit:
+
+.. doctest::
+
+   >>> dm.inc()
+   >>> dm.state, dm.delta
+   (0, 1)
+   >>> dm.prepare(t1)
+   >>> dm.state, dm.delta
+   (1, 1)
+   >>> dm.abort(t1)
+   >>> dm.state, dm.delta
+   (0, 0)
+
+Of course, the transactions passed to prepare and abort must
+match:
+
+.. doctest::
+
+   >>> dm.prepare(t1)
+   >>> dm.abort(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Transaction missmatch', '2', '1')
+
+   >>> dm.abort(t1)
+
+
+
+The :meth:`commit` method
+---------------------------
+
+Called to omplete two-phase commit
+
+.. doctest::
+
+   >>> dm = DataManager()
+   >>> dm.state
+   0
+   >>> dm.inc()
+
+We start two-phase commit by calling prepare:
+
+.. doctest::
+
+   >>> t1 = '1'
+   >>> dm.prepare(t1)
+
+   We complete it by calling commit:
+
+.. doctest::
+
+   >>> dm.commit(t1)
+   >>> dm.state
+   1
+
+It is an error ro call commit without calling prepare first:
+
+.. doctest::
+
+   >>> dm.inc()
+   >>> t2 = '2'
+   >>> dm.commit(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: Not prepared to commit
+
+   >>> dm.prepare(t2)
+   >>> dm.commit(t2)
+
+If course, the transactions given to prepare and commit must
+be the same:
+
+.. doctest::
+
+   >>> dm.inc()
+   >>> t3 = '3'
+   >>> dm.prepare(t3)
+   >>> dm.commit(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Transaction missmatch', '2', '3')
+
+
+The :meth:`savepoint` method
+------------------------------
+
+Provide the ability to rollback transaction state
+
+Savepoints provide a way to:
+
+ - Save partial transaction work. For some data managers, this
+   could allow resources to be used more efficiently.
+
+ - Provide the ability to revert state to a point in a
+   transaction without aborting the entire transaction.  In
+   other words, savepoints support partial aborts.
+
+Savepoints don't use two-phase commit. If there are errors in
+setting or rolling back to savepoints, the application should
+abort the containing transaction.  This is *not* the
+responsibility of the data manager.
+
+Savepoints are always associated with a transaction. Any work
+done in a savepoint's transaction is tentative until the
+transaction is committed using two-phase commit.
+
+.. doctest::
+
+   >>> dm = DataManager()
+   >>> dm.inc()
+   >>> t1 = '1'
+   >>> r = dm.savepoint(t1)
+   >>> dm.state, dm.delta
+   (0, 1)
+   >>> dm.inc()
+   >>> dm.state, dm.delta
+   (0, 2)
+   >>> r.rollback()
+   >>> dm.state, dm.delta
+   (0, 1)
+   >>> dm.prepare(t1)
+   >>> dm.commit(t1)
+   >>> dm.state, dm.delta
+   (1, 0)
+
+Savepoints must have the same transaction:
+
+.. doctest::
+
+   >>> r1 = dm.savepoint(t1)
+   >>> dm.state, dm.delta
+   (1, 0)
+   >>> dm.inc()
+   >>> dm.state, dm.delta
+   (1, 1)
+   >>> t2 = '2'
+   >>> r2 = dm.savepoint(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Transaction missmatch', '2', '1')
+
+   >>> r2 = dm.savepoint(t1)
+   >>> dm.inc()
+   >>> dm.state, dm.delta
+   (1, 2)
+
+If we rollback to an earlier savepoint, we discard all work
+done later:
+
+.. doctest::
+
+   >>> r1.rollback()
+   >>> dm.state, dm.delta
+   (1, 0)
+
+and we can no longer rollback to the later savepoint:
+
+.. doctest::
+
+   >>> r2.rollback()
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Attempt to roll back to invalid save point', 3, 2)
+
+We can roll back to a savepoint as often as we like:
+
+.. doctest::
+
+   >>> r1.rollback()
+   >>> r1.rollback()
+   >>> r1.rollback()
+   >>> dm.state, dm.delta
+   (1, 0)
+
+   >>> dm.inc()
+   >>> dm.inc()
+   >>> dm.inc()
+   >>> dm.state, dm.delta
+   (1, 3)
+   >>> r1.rollback()
+   >>> dm.state, dm.delta
+   (1, 0)
+
+But we can't rollback to a savepoint after it has been
+committed:
+
+.. doctest::
+
+   >>> dm.prepare(t1)
+   >>> dm.commit(t1)
+
+   >>> r1.rollback()
+   Traceback (most recent call last):
+   ...
+   TypeError: Attempt to rollback stale rollback

Added: transaction/branches/sphinx/docs/resourcemanager.rst
===================================================================
--- transaction/branches/sphinx/docs/resourcemanager.rst	                        (rev 0)
+++ transaction/branches/sphinx/docs/resourcemanager.rst	2012-12-17 20:28:54 UTC (rev 128705)
@@ -0,0 +1,399 @@
+Writing a Resource Manager
+==========================
+
+Simple Resource Manager
+-----------------------
+
+.. doctest::
+
+   >>> from transaction.tests.SampleResourceManager import ResourceManager
+
+This :class:`transaction.tests.SampleResourceManager.ResourceManager`
+class provides a trivial resource-manager implementation and doc
+strings to illustrate the protocol and to provide a tool for writing
+tests.
+
+Our sample resource manager has state that is updated through an inc
+method and through transaction operations.
+
+When we create a sample resource manager:
+
+.. doctest::
+
+   >>> rm = ResourceManager()
+
+It has two pieces state, state and delta, both initialized to 0:
+
+.. doctest::
+
+   >>> rm.state
+   0
+   >>> rm.delta
+   0
+
+state is meant to model committed state, while delta represents
+tentative changes within a transaction.  We change the state by
+calling inc:
+
+.. doctest::
+
+   >>> rm.inc()
+
+which updates delta:
+
+.. doctest::
+
+   >>> rm.delta
+   1
+
+but state isn't changed until we commit the transaction:
+
+.. doctest::
+
+   >>> rm.state
+   0
+
+To commit the changes, we use 2-phase commit.  We execute the first
+stage by calling prepare.  We need to pass a transation. Our
+sample resource managers don't really use the transactions for much,
+so we'll be lazy and use strings for transactions.  The sample
+resource manager updates the state when we call tpc_vote:
+
+
+.. doctest::
+
+   >>> t1 = '1'
+   >>> rm.tpc_begin(t1)
+   >>> rm.state, rm.delta
+   (0, 1)
+
+   >>> rm.tpc_vote(t1)
+   >>> rm.state, rm.delta
+   (1, 1)
+
+   Now if we call tpc_finish:
+
+   >>> rm.tpc_finish(t1)
+
+Our changes are "permanent".  The state reflects the changes and the
+delta has been reset to 0.
+
+.. doctest::
+
+   >>> rm.state, rm.delta
+   (1, 0)
+
+
+The :meth:`tpc_begin` Method
+-----------------------------
+
+Called by the transaction manager to ask the RM to prepare to commit data.
+
+.. doctest::
+
+   >>> rm = ResourceManager()
+   >>> rm.inc()
+   >>> t1 = '1'
+   >>> rm.tpc_begin(t1)
+   >>> rm.tpc_vote(t1)
+   >>> rm.tpc_finish(t1)
+   >>> rm.state
+   1
+   >>> rm.inc()
+   >>> t2 = '2'
+   >>> rm.tpc_begin(t2)
+   >>> rm.tpc_vote(t2)
+   >>> rm.tpc_abort(t2)
+   >>> rm.state
+   1
+
+It is an error to call tpc_begin more than once without completing
+two-phase commit:
+
+.. doctest::
+
+   >>> rm.tpc_begin(t1)
+
+   >>> rm.tpc_begin(t1)
+   Traceback (most recent call last):
+   ...
+   ValueError: txn in state 'tpc_begin' but expected one of (None,)
+   >>> rm.tpc_abort(t1)
+
+If there was a preceeding savepoint, the transaction must match:
+
+.. doctest::
+
+   >>> rollback = rm.savepoint(t1)
+   >>> rm.tpc_begin(t2)
+   Traceback (most recent call last):
+   ,,,
+   TypeError: ('Transaction missmatch', '2', '1')
+
+   >>> rm.tpc_begin(t1)
+
+
+The :meth:`tpc_vote` Method
+---------------------------
+
+Verify that a data manager can commit the transaction.
+
+This is the last chance for a data manager to vote 'no'.  A
+data manager votes 'no' by raising an exception.
+
+Passed `transaction`, which is the ITransaction instance associated with the
+transaction being committed.
+
+
+The :meth:`tpc_finish` Method
+-----------------------------
+
+Complete two-phase commit
+
+.. doctest::
+
+   >>> rm = ResourceManager()
+   >>> rm.state
+   0
+   >>> rm.inc()
+
+   We start two-phase commit by calling prepare:
+
+   >>> t1 = '1'
+   >>> rm.tpc_begin(t1)
+   >>> rm.tpc_vote(t1)
+
+   We complete it by calling tpc_finish:
+
+   >>> rm.tpc_finish(t1)
+   >>> rm.state
+   1
+
+It is an error ro call tpc_finish without calling tpc_vote:
+
+.. doctest::
+
+   >>> rm.inc()
+   >>> t2 = '2'
+   >>> rm.tpc_begin(t2)
+   >>> rm.tpc_finish(t2)
+   Traceback (most recent call last):
+   ...
+   ValueError: txn in state 'tpc_begin' but expected one of ('tpc_vote',)
+
+   >>> rm.tpc_abort(t2)  # clean slate
+
+   >>> rm.tpc_begin(t2)
+   >>> rm.tpc_vote(t2)
+   >>> rm.tpc_finish(t2)
+
+Of course, the transactions given to tpc_begin and tpc_finish must
+be the same:
+
+.. doctest::
+
+   >>> rm.inc()
+   >>> t3 = '3'
+   >>> rm.tpc_begin(t3)
+   >>> rm.tpc_vote(t3)
+   >>> rm.tpc_finish(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Transaction missmatch', '2', '3')
+
+
+The :meth:`tpc_abort` Method
+-----------------------------
+
+Abort a transaction
+
+The abort method can be called before two-phase commit to
+throw away work done in the transaction:
+
+.. doctest::
+
+   >>> rm = ResourceManager()
+   >>> rm.inc()
+   >>> rm.state, rm.delta
+   (0, 1)
+   >>> t1 = '1'
+   >>> rm.tpc_abort(t1)
+   >>> rm.state, rm.delta
+   (0, 0)
+
+The abort method also throws away work done in savepoints:
+
+.. doctest::
+
+   >>> rm.inc()
+   >>> r = rm.savepoint(t1)
+   >>> rm.inc()
+   >>> r = rm.savepoint(t1)
+   >>> rm.state, rm.delta
+   (0, 2)
+   >>> rm.tpc_abort(t1)
+   >>> rm.state, rm.delta
+   (0, 0)
+
+If savepoints are used, abort must be passed the same
+transaction:
+
+.. doctest::
+
+   >>> rm.inc()
+   >>> r = rm.savepoint(t1)
+   >>> t2 = '2'
+   >>> rm.tpc_abort(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Transaction missmatch', '2', '1')
+
+   >>> rm.tpc_abort(t1)
+
+The abort method is also used to abort a two-phase commit:
+
+.. doctest::
+
+   >>> rm.inc()
+   >>> rm.state, rm.delta
+   (0, 1)
+   >>> rm.tpc_begin(t1)
+   >>> rm.state, rm.delta
+   (0, 1)
+   >>> rm.tpc_vote(t1)
+   >>> rm.state, rm.delta
+   (1, 1)
+   >>> rm.tpc_abort(t1)
+   >>> rm.state, rm.delta
+   (0, 0)
+
+Of course, the transactions passed to prepare and abort must
+match:
+
+.. doctest::
+
+   >>> rm.tpc_begin(t1)
+   >>> rm.tpc_abort(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Transaction missmatch', '2', '1')
+
+   >>> rm.tpc_abort(t1)
+
+This should never fail.
+
+
+The :meth:`savepoint` Method
+----------------------------
+
+Provide the ability to rollback transaction state
+
+Savepoints provide a way to:
+
+ - Save partial transaction work. For some resource managers, this
+   could allow resources to be used more efficiently.
+
+ - Provide the ability to revert state to a point in a
+   transaction without aborting the entire transaction.  In
+   other words, savepoints support partial aborts.
+
+Savepoints don't use two-phase commit. If there are errors in
+setting or rolling back to savepoints, the application should
+abort the containing transaction.  This is *not* the
+responsibility of the resource manager.
+
+Savepoints are always associated with a transaction. Any work
+done in a savepoint's transaction is tentative until the
+transaction is committed using two-phase commit.
+
+.. doctest::
+
+   >>> rm = ResourceManager()
+   >>> rm.inc()
+   >>> t1 = '1'
+   >>> r = rm.savepoint(t1)
+   >>> rm.state, rm.delta
+   (0, 1)
+   >>> rm.inc()
+   >>> rm.state, rm.delta
+   (0, 2)
+   >>> r.rollback()
+   >>> rm.state, rm.delta
+   (0, 1)
+   >>> rm.tpc_begin(t1)
+   >>> rm.tpc_vote(t1)
+   >>> rm.tpc_finish(t1)
+   >>> rm.state, rm.delta
+   (1, 0)
+
+Savepoints must have the same transaction:
+
+.. doctest::
+
+   >>> r1 = rm.savepoint(t1)
+   >>> rm.state, rm.delta
+   (1, 0)
+   >>> rm.inc()
+   >>> rm.state, rm.delta
+   (1, 1)
+   >>> t2 = '2'
+   >>> r2 = rm.savepoint(t2)
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Transaction missmatch', '2', '1')
+
+   >>> r2 = rm.savepoint(t1)
+   >>> rm.inc()
+   >>> rm.state, rm.delta
+   (1, 2)
+
+If we rollback to an earlier savepoint, we discard all work
+done later:
+
+.. doctest::
+
+   >>> r1.rollback()
+   >>> rm.state, rm.delta
+   (1, 0)
+
+and we can no longer rollback to the later savepoint:
+
+.. doctest::
+
+   >>> r2.rollback()
+   Traceback (most recent call last):
+   ...
+   TypeError: ('Attempt to roll back to invalid save point', 3, 2)
+
+We can roll back to a savepoint as often as we like:
+
+.. doctest::
+
+   >>> r1.rollback()
+   >>> r1.rollback()
+   >>> r1.rollback()
+   >>> rm.state, rm.delta
+   (1, 0)
+
+   >>> rm.inc()
+   >>> rm.inc()
+   >>> rm.inc()
+   >>> rm.state, rm.delta
+   (1, 3)
+   >>> r1.rollback()
+   >>> rm.state, rm.delta
+   (1, 0)
+
+But we can't rollback to a savepoint after it has been
+committed:
+
+.. doctest::
+
+   >>> rm.tpc_begin(t1)
+   >>> rm.tpc_vote(t1)
+   >>> rm.tpc_finish(t1)
+
+   >>> r1.rollback()
+   Traceback (most recent call last):
+   ...
+   TypeError: Attempt to rollback stale rollback



More information about the checkins mailing list