[pypy-commit] pypy stm-gc: Document transaction.py. Synchronize the exception behavior with module/transaction/.

arigo noreply at buildbot.pypy.org
Sat Mar 31 18:17:15 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: stm-gc
Changeset: r54112:6c835b5c43e5
Date: 2012-03-31 18:16 +0200
http://bitbucket.org/pypy/pypy/changeset/6c835b5c43e5/

Log:	Document transaction.py. Synchronize the exception behavior with
	module/transaction/.

diff --git a/lib_pypy/transaction.py b/lib_pypy/transaction.py
--- a/lib_pypy/transaction.py
+++ b/lib_pypy/transaction.py
@@ -5,19 +5,41 @@
 print >> sys.stderr, "warning: using lib_pypy/transaction.py, the emulator"
 
 _pending = {}
+_in_transaction = False
+
+
+class TransactionError(Exception):
+    pass
+
 
 def set_num_threads(num):
-    pass
+    """Set the number of threads to use.  In a real implementation,
+    the transactions will attempt to use 'num' threads in parallel.
+    """
 
-def add(f, *args):
+
+def add(f, *args, **kwds):
+    """Register the call 'f(*args, **kwds)' as running a new
+    transaction.  If we are currently running in a transaction too, the
+    new transaction will only start after the end of the current
+    transaction.  Note that if the same or another transaction raises an
+    exception in the meantime, all pending transactions are cancelled.
+    """
     r = random.random()
     assert r not in _pending    # very bad luck if it is
-    _pending[r] = (f, args)
+    _pending[r] = (f, args, kwds)
+
 
 def add_epoll(ep, callback):
-    for key, (f, args) in _pending.items():
+    """Register the epoll object (from the 'select' module).  For any
+    event (fd, events) detected by 'ep', a new transaction will be
+    started invoking 'callback(fd, events)'.  Note that all fds should
+    be registered with the flag select.EPOLLONESHOT, and re-registered
+    from the callback if needed.
+    """
+    for key, (f, args, kwds) in _pending.items():
         if getattr(f, '_reads_from_epoll_', None) is ep:
-            raise ValueError("add_epoll(ep): ep is already registered")
+            raise TransactionError("add_epoll(ep): ep is already registered")
     def poll_reader():
         # assume only one epoll is added.  If the _pending list is
         # now empty, wait.  If not, then just poll non-blockingly.
@@ -33,18 +55,33 @@
     add(poll_reader)
 
 def remove_epoll(ep):
-    for key, (f, args) in _pending.items():
+    """Explicitly unregister the epoll object.  Note that raising an
+    exception in a transaction also cancels any add_epoll().
+    """
+    for key, (f, args, kwds) in _pending.items():
         if getattr(f, '_reads_from_epoll_', None) is ep:
             del _pending[key]
             break
     else:
-        raise ValueError("remove_epoll(ep): ep is not registered")
+        raise TransactionError("remove_epoll(ep): ep is not registered")
 
 def run():
+    """Run the pending transactions, as well as all transactions started
+    by them, and so on.  The order is random and undeterministic.  Must
+    be called from the main program, i.e. not from within another
+    transaction.  If at some point all transactions are done, returns.
+    If a transaction raises an exception, it propagates here; in this
+    case all pending transactions are cancelled.
+    """
+    global _pending, _in_transaction
+    if _in_transaction:
+        raise TransactionError("recursive invocation of transaction.run()")
     pending = _pending
     try:
+        _in_transaction = True
         while pending:
-            _, (f, args) = pending.popitem()
-            f(*args)
+            _, (f, args, kwds) = pending.popitem()
+            f(*args, **kwds)
     finally:
+        _in_transaction = False
         pending.clear()   # this is the behavior we get with interp_transaction
diff --git a/pypy/module/transaction/interp_epoll.py b/pypy/module/transaction/interp_epoll.py
--- a/pypy/module/transaction/interp_epoll.py
+++ b/pypy/module/transaction/interp_epoll.py
@@ -112,7 +112,7 @@
     if state.epolls is None:
         state.epolls = {}
     elif epoller in state.epolls:
-        raise OperationError(space.w_ValueError,
+        raise OperationError(state.w_error,
             space.wrap("add_epoll(ep): ep is already registered"))
     pending = EPollPending(space, epoller, w_callback)
     state.epolls[epoller] = pending
@@ -126,7 +126,7 @@
     else:
         pending = state.epolls.get(epoller, None)
     if pending is None:
-        raise OperationError(space.w_ValueError,
+        raise OperationError(state.w_error,
             space.wrap("remove_epoll(ep): ep is not registered"))
     pending.force_quit = True
     del state.epolls[epoller]
diff --git a/pypy/module/transaction/test/test_epoll.py b/pypy/module/transaction/test/test_epoll.py
--- a/pypy/module/transaction/test/test_epoll.py
+++ b/pypy/module/transaction/test/test_epoll.py
@@ -70,6 +70,17 @@
             transaction.run()
             # assert didn't deadlock
 
+    def test_errors(self):
+        import transaction, select
+        epoller = select.epoll()
+        callback = lambda *args: not_actually_callable
+        transaction.add_epoll(epoller, callback)
+        raises(transaction.TransactionError,
+               transaction.add_epoll, epoller, callback)
+        transaction.remove_epoll(epoller)
+        raises(transaction.TransactionError,
+               transaction.remove_epoll, epoller)
+
 
 class AppTestEpollEmulator(AppTestEpoll):
     def setup_class(cls):


More information about the pypy-commit mailing list