[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