[Zodb-checkins]
SVN: ZODB/branches/efge-beforeCommitHook/src/transaction/
Added beforeCommitHook,
a way to register hooks to call at the start of
Florent Guillaume
fg at nuxeo.com
Wed Apr 6 08:32:33 EDT 2005
Log message for revision 29885:
Added beforeCommitHook, a way to register hooks to call at the start of
the commit phase.
Changed:
U ZODB/branches/efge-beforeCommitHook/src/transaction/_transaction.py
U ZODB/branches/efge-beforeCommitHook/src/transaction/interfaces.py
U ZODB/branches/efge-beforeCommitHook/src/transaction/tests/test_transaction.py
-=-
Modified: ZODB/branches/efge-beforeCommitHook/src/transaction/_transaction.py
===================================================================
--- ZODB/branches/efge-beforeCommitHook/src/transaction/_transaction.py 2005-04-06 12:24:21 UTC (rev 29884)
+++ ZODB/branches/efge-beforeCommitHook/src/transaction/_transaction.py 2005-04-06 12:32:33 UTC (rev 29885)
@@ -98,6 +98,18 @@
commit will start with a commit_sub() call instead of a tpc_begin()
call.
+Before-commit hook
+---------------
+
+Sometimes, applications want to execute some code when a transaction is
+committed. For example, one might want to delay object indexing until a
+transaction commits, rather than indexing every time an object is
+changed. Or someone might want to check invariants only after a set of
+operations. A pre-commit hook is available for such use cases, just use
+beforeCommitHook() passing it a callable and arguments. The callable
+will be called with its arguments at the start of the commit (but not
+for substransaction commits).
+
Error handling
--------------
@@ -207,6 +219,9 @@
# raised, incorporating this traceback.
self._failure_traceback = None
+ # Holds hooks added by beforeCommitHook.
+ self._before_commit = []
+
# Raise TransactionFailedError, due to commit()/join()/register()
# getting called when the current transaction has already suffered
# a commit failure.
@@ -282,6 +297,9 @@
if self.status is Status.COMMITFAILED:
self._prior_commit_failed() # doesn't return
+ if not subtransaction:
+ self._callBeforeCommitHooks()
+
if not subtransaction and self._sub and self._resources:
# This commit is for a top-level transaction that has
# previously committed subtransactions. Do one last
@@ -317,6 +335,16 @@
self._synchronizers.map(lambda s: s.afterCompletion(self))
self.log.debug("commit")
+ def beforeCommitHook(self, hook, *args, **kw):
+ self._before_commit.append((hook, args, kw))
+
+ def _callBeforeCommitHooks(self):
+ # Call all hooks registered, allowing further registrations
+ # during processing.
+ while self._before_commit:
+ hook, args, kw = self._before_commit.pop(0)
+ hook(*args, **kw)
+
def _commitResources(self, subtransaction):
# Execute the two-phase commit protocol.
Modified: ZODB/branches/efge-beforeCommitHook/src/transaction/interfaces.py
===================================================================
--- ZODB/branches/efge-beforeCommitHook/src/transaction/interfaces.py 2005-04-06 12:24:21 UTC (rev 29884)
+++ ZODB/branches/efge-beforeCommitHook/src/transaction/interfaces.py 2005-04-06 12:32:33 UTC (rev 29885)
@@ -245,7 +245,24 @@
# Unsure: is this allowed to cause an exception here, during
# the two-phase commit, or can it toss data silently?
+ def beforeCommitHook(hook, *args, **kw):
+ """Register a hook to call before the transaction is committed.
+ The provided hook will be called after the transaction's commit
+ method has been called, but before the commit process has been
+ started. The hook will be passed the given positional and
+ keyword arguments.
+
+ Multiple hooks can be registered and will be called in order.
+ This method can also be called from executing hooks; so
+ executing hooks can register more hooks. (Applications should
+ take care to avoid creating infinite loops by recursively
+ registering hooks.)
+
+ If the transaction aborts, hooks are not called and are
+ discarded.
+ """
+
class IRollback(zope.interface.Interface):
def rollback():
Modified: ZODB/branches/efge-beforeCommitHook/src/transaction/tests/test_transaction.py
===================================================================
--- ZODB/branches/efge-beforeCommitHook/src/transaction/tests/test_transaction.py 2005-04-06 12:24:21 UTC (rev 29884)
+++ ZODB/branches/efge-beforeCommitHook/src/transaction/tests/test_transaction.py 2005-04-06 12:32:33 UTC (rev 29885)
@@ -634,6 +634,84 @@
"""
+def test_beforeCommitHook():
+ """Test the beforeCommitHook
+
+ Lets define a hook to call, and a way to see that it was called.
+
+ >>> log = []
+ >>> def reset_log():
+ ... log[:] = []
+
+ >>> def hook(arg=''):
+ ... log.append('hook'+arg)
+
+ Now register the hook with a transaction.
+
+ >>> from transaction import manager
+ >>> t = manager.begin()
+ >>> t.beforeCommitHook(hook, '1')
+
+ When transaction commit starts, the hook is called, with its
+ arguments.
+
+ >>> t.commit()
+ >>> log
+ ['hook1']
+ >>> reset_log()
+
+ The hook is called before the commit does anything, so even if the
+ commit fails the hook will have been called. To provoke failures in
+ commit, we'll add failing resource manager to the transaction.
+
+ >>> class CommitFailure(Exception):
+ ... pass
+ >>> class FailingDataManager:
+ ... def tpc_begin(self, txn, sub=False):
+ ... raise CommitFailure
+ ... def abort(self, txn):
+ ... pass
+
+ >>> t = manager.begin()
+ >>> t.join(FailingDataManager())
+
+ >>> t.beforeCommitHook(hook, '2')
+ >>> t.commit()
+ Traceback (most recent call last):
+ ...
+ CommitFailure
+ >>> log
+ ['hook2']
+ >>> reset_log()
+
+ If several hooks are defined, they are called in order.
+
+ >>> t = manager.begin()
+ >>> t.beforeCommitHook(hook, '4')
+ >>> t.beforeCommitHook(hook, '5')
+ >>> t.commit()
+ >>> log
+ ['hook4', 'hook5']
+ >>> reset_log()
+
+ While executing, a hook can itself add more hooks, and they will all
+ be called before the real commit starts.
+
+ >>> def recurse(txn, arg=0):
+ ... log.append('rec'+str(arg))
+ ... if arg != 0:
+ ... txn.beforeCommitHook(hook, '-')
+ ... txn.beforeCommitHook(recurse, txn, arg-1)
+
+ >>> t = manager.begin()
+ >>> t.beforeCommitHook(recurse, t, 3)
+ >>> t.commit()
+ >>> log
+ ['rec3', 'hook-', 'rec2', 'hook-', 'rec1', 'hook-', 'rec0']
+ >>> reset_log()
+
+ """
+
def test_suite():
from doctest import DocTestSuite
return unittest.TestSuite((
More information about the Zodb-checkins
mailing list