[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