Commits

Armin Rigo committed 6c835b5

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

  • Participants
  • Parent commits 22ab9f0
  • Branches stm-gc

Comments (0)

Files changed (3)

File lib_pypy/transaction.py

 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.
     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

File pypy/module/transaction/interp_epoll.py

     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
     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]

File pypy/module/transaction/test/test_epoll.py

             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):