[Zodb-checkins] SVN: ZODB/branches/jinty-doom/src/transaction/ Add the doom() function to transactions. Look at tests/doom.txt for more info.

Brian Sutherland jinty at web.de
Fri Sep 8 07:13:07 EDT 2006


Log message for revision 70050:
  Add the doom() function to transactions. Look at tests/doom.txt for more info.

Changed:
  U   ZODB/branches/jinty-doom/src/transaction/__init__.py
  U   ZODB/branches/jinty-doom/src/transaction/_manager.py
  U   ZODB/branches/jinty-doom/src/transaction/_transaction.py
  U   ZODB/branches/jinty-doom/src/transaction/interfaces.py
  A   ZODB/branches/jinty-doom/src/transaction/tests/doom.txt
  U   ZODB/branches/jinty-doom/src/transaction/tests/test_transaction.py

-=-
Modified: ZODB/branches/jinty-doom/src/transaction/__init__.py
===================================================================
--- ZODB/branches/jinty-doom/src/transaction/__init__.py	2006-09-08 11:05:58 UTC (rev 70049)
+++ ZODB/branches/jinty-doom/src/transaction/__init__.py	2006-09-08 11:13:06 UTC (rev 70050)
@@ -24,4 +24,5 @@
 begin = manager.begin
 commit = manager.commit
 abort = manager.abort
+doom = manager.doom
 savepoint = manager.savepoint

Modified: ZODB/branches/jinty-doom/src/transaction/_manager.py
===================================================================
--- ZODB/branches/jinty-doom/src/transaction/_manager.py	2006-09-08 11:05:58 UTC (rev 70049)
+++ ZODB/branches/jinty-doom/src/transaction/_manager.py	2006-09-08 11:13:06 UTC (rev 70050)
@@ -85,6 +85,9 @@
     def unregisterSynch(self, synch):
         self._synchs.remove(synch)
 
+    def doom(self):
+        return self.get().doom()
+
     def commit(self, sub=_marker):
         if sub is _marker:
             sub = None

Modified: ZODB/branches/jinty-doom/src/transaction/_transaction.py
===================================================================
--- ZODB/branches/jinty-doom/src/transaction/_transaction.py	2006-09-08 11:05:58 UTC (rev 70049)
+++ ZODB/branches/jinty-doom/src/transaction/_transaction.py	2006-09-08 11:13:06 UTC (rev 70050)
@@ -193,6 +193,8 @@
     COMMITTING   = "Committing"
     COMMITTED    = "Committed"
 
+    DOOMED = "Doomed"
+
     # commit() or commit(True) raised an exception.  All further attempts
     # to commit or join this transaction will raise TransactionFailedError.
     COMMITFAILED = "Commit failed"
@@ -258,6 +260,14 @@
         # List of (hook, args, kws) tuples added by addAfterCommitHook().
         self._after_commit = []
 
+    def doom(self):
+        if self.status is not Status.DOOMED:
+            if self.status is not Status.ACTIVE:
+                # should not doom transactions in the middle,
+                # or after, a commit
+                raise AssertionError()
+            self.status = Status.DOOMED
+
     # Raise TransactionFailedError, due to commit()/join()/register()
     # getting called when the current transaction has already suffered
     # a commit/savepoint failure.
@@ -272,11 +282,12 @@
         if self.status is Status.COMMITFAILED:
             self._prior_operation_failed() # doesn't return
 
-        if self.status is not Status.ACTIVE:
+        if (self.status is not Status.ACTIVE and
+                self.status is not Status.DOOMED):
             # TODO: Should it be possible to join a committing transaction?
             # I think some users want it.
-            raise ValueError("expected txn status %r, but it's %r" % (
-                             Status.ACTIVE, self.status))
+            raise ValueError("expected txn status %r or %r, but it's %r" % (
+                             Status.ACTIVE, Status.DOOMED, self.status))
         # TODO: the prepare check is a bit of a hack, perhaps it would
         # be better to use interfaces.  If this is a ZODB4-style
         # resource manager, it needs to be adapted, too.
@@ -363,6 +374,9 @@
             adapter.objects.append(obj)
 
     def commit(self, subtransaction=_marker, deprecation_wng=True):
+        if self.status is Status.DOOMED:
+            raise interfaces.DoomedTransaction()
+
         if subtransaction is _marker:
             subtransaction = 0
         elif deprecation_wng:

Modified: ZODB/branches/jinty-doom/src/transaction/interfaces.py
===================================================================
--- ZODB/branches/jinty-doom/src/transaction/interfaces.py	2006-09-08 11:05:58 UTC (rev 70049)
+++ ZODB/branches/jinty-doom/src/transaction/interfaces.py	2006-09-08 11:13:06 UTC (rev 70050)
@@ -45,6 +45,10 @@
         """Abort the current transaction.
         """
 
+    def doom():
+        """Doom the current transaction.
+        """
+
     def savepoint(optimistic=False):
         """Create a savepoint from the current transaction.
 
@@ -115,7 +119,17 @@
         This is called from the application.  This can only be called
         before the two-phase commit protocol has been started.
         """
+    
+    def doom():
+        """Doom the transaction.
 
+        Dooms the current transaction. This will cause
+        DoomedTransactionException to be raised on any attempt to commit the
+        transaction.
+
+        Otherwise the transaction will behave as if it was active.
+        """
+
     def savepoint(optimistic=False):
         """Create a savepoint.
 
@@ -453,3 +467,6 @@
         This hook is called when, and only when, a transaction manager's
         begin() method is called explictly.
         """
+
+class DoomedTransaction(Exception):
+    """A commit was attempted on a transaction that was doomed."""

Added: ZODB/branches/jinty-doom/src/transaction/tests/doom.txt
===================================================================
--- ZODB/branches/jinty-doom/src/transaction/tests/doom.txt	2006-09-08 11:05:58 UTC (rev 70049)
+++ ZODB/branches/jinty-doom/src/transaction/tests/doom.txt	2006-09-08 11:13:06 UTC (rev 70050)
@@ -0,0 +1,123 @@
+Dooming Transactions
+====================
+
+A doomed transaction behaves exactly the same way as an active transaction but
+raises an error on any attempt to commit it, thus forcing an abort.
+
+Doom is useful in places where abort is unsafe and an exception cannot be
+raised.  This occurs when the programmer wants the code following the doom to
+run but not commit. It is unsafe to abort in these circumstances as a following
+get() may implicitly open a new transaction.
+
+Any attempt to commit a doomed transaction will raise a DoomedTransaction
+exception.
+
+An example of such a use case can be found in
+zope/app/form/browser/editview.py.  Here a form validation failure must doom
+the transaction as committing the transaction may have side-effects. However,
+the form code must continue to calculate a form containing the error messages
+to return.
+
+For Zope in general, code running within a request should always doom
+transactions rather than aborting them. It is the responsibilty of the
+publication to either abort() or commit() the transaction. Application code can
+use savepoints and doom() safely.
+
+To see how it works we first need to create a stub data manager:
+
+    >>> from transaction.interfaces import IDataManager
+    >>> from zope.interface import implements
+    >>> class DataManager:
+    ...     implements(IDataManager)
+    ...     def __init__(self):
+    ...         self.attr_counter = {}
+    ...     def __getattr__(self, name):
+    ...         def f(transaction):
+    ...             self.attr_counter[name] = self.attr_counter.get(name, 0) + 1
+    ...         return f
+    ...     def total(self):
+    ...         count = 0
+    ...         for access_count in self.attr_counter.values():
+    ...             count += access_count
+    ...         return count
+    ...     def sortKey(self):
+    ...         return 1
+
+Start a new transaction:
+
+    >>> import transaction
+    >>> txn = transaction.begin()
+    >>> dm = DataManager()
+    >>> txn.join(dm)
+
+We can doom a transaction by calling .doom() on it:
+
+    >>> txn.doom()
+
+We can doom it again if we like:
+
+    >>> txn.doom()
+
+The data manager is unchanged at this point:
+
+    >>> dm.total()
+    0
+
+Attempting to commit a doomed transaction any number of times raises a
+DoomedTransaction:
+
+    >>> txn.commit() # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+        ...
+    DoomedTransaction
+    >>> txn.commit() # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+        ...
+    DoomedTransaction
+
+But still leaves the data manager unchanged:
+
+    >>> dm.total()
+    0
+
+But the doomed transaction can be aborted:
+
+    >>> txn.abort()
+
+Which aborts the data manager:
+
+    >>> dm.total()
+    1
+    >>> dm.attr_counter['abort']
+    1
+
+Dooming the current transaction can also be done directly from the transaction
+module. We can also begin a new transaction directly after dooming the old one:
+
+    >>> txn = transaction.begin()
+    >>> transaction.doom()
+    >>> txn = transaction.begin()
+
+After committing a transaction we get an assertion error if we try to doom the
+transaction. This could be made more specific, but trying to doom a transaction
+after it's been committed is probably a programming error:
+
+    >>> txn = transaction.begin()
+    >>> txn.commit()
+    >>> txn.doom()
+    Traceback (most recent call last):
+        ...
+    AssertionError
+
+A doomed transaction should act the same as an active transaction, so we should
+be able to join it:
+
+    >>> txn = transaction.begin()
+    >>> txn.doom()
+    >>> dm2 = DataManager()
+    >>> txn.join(dm2)
+
+Clean up:
+
+    >>> txn = transaction.begin()
+    >>> txn.abort()


Property changes on: ZODB/branches/jinty-doom/src/transaction/tests/doom.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: ZODB/branches/jinty-doom/src/transaction/tests/test_transaction.py
===================================================================
--- ZODB/branches/jinty-doom/src/transaction/tests/test_transaction.py	2006-09-08 11:05:58 UTC (rev 70049)
+++ ZODB/branches/jinty-doom/src/transaction/tests/test_transaction.py	2006-09-08 11:13:06 UTC (rev 70050)
@@ -992,8 +992,9 @@
     """
 
 def test_suite():
-    from zope.testing.doctest import DocTestSuite
+    from zope.testing.doctest import DocTestSuite, DocFileSuite
     return unittest.TestSuite((
+        DocFileSuite('doom.txt'),
         DocTestSuite(),
         unittest.makeSuite(TransactionTests),
         ))



More information about the Zodb-checkins mailing list