[pypy-svn] r75735 - in pypy/trunk: lib_pypy lib_pypy/pypy_test pypy/doc pypy/module/_stackless pypy/module/_stackless/test pypy/rlib

wildchild at codespeak.net wildchild at codespeak.net
Thu Jul 1 20:51:10 CEST 2010


Author: wildchild
Date: Thu Jul  1 20:51:08 2010
New Revision: 75735

Modified:
   pypy/trunk/lib_pypy/pypy_test/test_coroutine.py
   pypy/trunk/lib_pypy/pypy_test/test_stackless.py
   pypy/trunk/lib_pypy/stackless.py
   pypy/trunk/pypy/doc/stackless.txt
   pypy/trunk/pypy/module/_stackless/interp_coroutine.py
   pypy/trunk/pypy/module/_stackless/rcoroutine.py
   pypy/trunk/pypy/module/_stackless/test/test_coroutine.py
   pypy/trunk/pypy/rlib/rcoroutine.py
Log:
- Implementation of application level CoroutineExit and TaskletExit exceptions (now catchable in user code!).
- Implementation of coroutine.throw() to raise an exception inside a specific coroutine (catchable in user code).
- Enabled a previously skipped test in pypy/module/_stackless/test/test_coroutine.py
- Updated documentation about coroutines/stackless.


Modified: pypy/trunk/lib_pypy/pypy_test/test_coroutine.py
==============================================================================
--- pypy/trunk/lib_pypy/pypy_test/test_coroutine.py	(original)
+++ pypy/trunk/lib_pypy/pypy_test/test_coroutine.py	Thu Jul  1 20:51:08 2010
@@ -112,6 +112,67 @@
         co.kill()
         assert not co.is_alive
 
+    def test_catch_coroutineexit(self):
+        coroutineexit = []
+        co_a = coroutine()
+        co_test = coroutine.getcurrent()
+
+        def a():
+            try:
+                co_test.switch()
+            except CoroutineExit:
+                coroutineexit.append(True)
+                raise 
+        
+        co_a.bind(a)
+        co_a.switch()
+        assert co_a.is_alive
+        
+        co_a.kill()
+        assert coroutineexit == [True]
+        assert not co_a.is_alive
+        
+    def test_throw(self):
+        exceptions = []
+        co = coroutine()
+        def f(main):
+            try:
+                main.switch()
+            except RuntimeError:
+                exceptions.append(True)
+        
+        co.bind(f, coroutine.getcurrent())
+        co.switch()
+        co.throw(RuntimeError)
+        assert exceptions == [True]
+        
+    def test_propagation(self):
+        exceptions = []
+        co = coroutine()
+        co2 = coroutine()
+        def f(main):
+            main.switch()
+        
+        co.bind(f, coroutine.getcurrent())
+        co.switch()
+        
+        try:
+            co.throw(RuntimeError)
+        except RuntimeError:
+            exceptions.append(1)
+            
+        def f2():
+            raise RuntimeError
+        
+        co2.bind(f2)
+            
+        try:
+            co2.switch()
+        except RuntimeError:
+            exceptions.append(2)
+        
+        assert exceptions == [1,2]
+
     def test_bogus_bind(self):
         co = coroutine()
         def f():

Modified: pypy/trunk/lib_pypy/pypy_test/test_stackless.py
==============================================================================
--- pypy/trunk/lib_pypy/pypy_test/test_stackless.py	(original)
+++ pypy/trunk/lib_pypy/pypy_test/test_stackless.py	Thu Jul  1 20:51:08 2010
@@ -226,6 +226,36 @@
         t.kill()
         assert not t.alive
 
+    def test_catch_taskletexit(self):
+        # Tests if TaskletExit can be caught in the tasklet being killed.
+        global taskletexit
+        taskletexit = False
+        
+        def f():
+            try:
+                stackless.schedule()
+            except TaskletExit:
+                global TaskletExit
+                taskletexit = True
+                raise
+            
+            t =  stackless.tasklet(f)()
+            t.run()
+            assert t.alive
+            t.kill()
+            assert not t.alive
+            assert taskletexit
+            
+    def test_autocatch_taskletexit(self):
+        # Tests if TaskletExit is caught correctly in stackless.tasklet.setup(). 
+        def f():
+            stackless.schedule()
+        
+        t = stackless.tasklet(f)()
+        t.run()
+        t.kill()
+
+
     # tests inspired from simple stackless.com examples
 
     def test_construction(self):

Modified: pypy/trunk/lib_pypy/stackless.py
==============================================================================
--- pypy/trunk/lib_pypy/stackless.py	(original)
+++ pypy/trunk/lib_pypy/stackless.py	Thu Jul  1 20:51:08 2010
@@ -114,7 +114,7 @@
 
 import operator
 __all__ = 'run getcurrent getmain schedule tasklet channel coroutine \
-                TaskletExit greenlet'.split()
+                greenlet'.split()
 
 _global_task_id = 0
 _squeue = None
@@ -153,12 +153,12 @@
     _last_task = next
     assert not next.blocked
     if next is not current:
-        next.switch()
+        try:
+            next.switch()
+        except CoroutineExit:
+            raise TaskletExit
     return current
 
-
-class TaskletExit(Exception):pass
-
 def set_schedule_callback(callback):
     global _schedule_callback
     _schedule_callback = callback
@@ -435,6 +435,7 @@
         the tasklet will silently die.
         """
         if not self.is_zombie:
+            # Killing the tasklet by throwing TaskletExit exception.
             coroutine.kill(self)
             _scheduler_remove(self)
             self.alive = False

Modified: pypy/trunk/pypy/doc/stackless.txt
==============================================================================
--- pypy/trunk/pypy/doc/stackless.txt	(original)
+++ pypy/trunk/pypy/doc/stackless.txt	Thu Jul  1 20:51:08 2010
@@ -124,10 +124,17 @@
 
 * ``coro.kill()``
 
-    Kill ``coro`` by sending an exception to it.  (At the moment, the
-    exception is not visible to app-level, which means that you cannot
-    catch it, and that ``try: finally:`` clauses are not honored.  This
-    will be fixed in the future.)
+    Kill ``coro`` by sending a CoroutineExit exception and switching
+    execution immediately to it. This exception can be caught in the 
+    coroutine itself and can be raised from any call to ``coro.switch()``. 
+    This exception isn't propagated to the parent coroutine.
+
+* ``coro.throw(type, value)``
+
+    Insert an exception in ``coro`` an resume switches execution
+    immediately to it. In the coroutine itself, this exception
+    will come from any call to ``coro.switch()`` and can be caught. If the
+    exception isn't caught, it will be propagated to the parent coroutine.
 
 Example
 ~~~~~~~

Modified: pypy/trunk/pypy/module/_stackless/interp_coroutine.py
==============================================================================
--- pypy/trunk/pypy/module/_stackless/interp_coroutine.py	(original)
+++ pypy/trunk/pypy/module/_stackless/interp_coroutine.py	Thu Jul  1 20:51:08 2010
@@ -24,7 +24,9 @@
 from pypy.interpreter.function import StaticMethod
 
 from pypy.module._stackless.stackless_flags import StacklessFlags
-from pypy.module._stackless.rcoroutine import Coroutine, BaseCoState, AbstractThunk
+from pypy.module._stackless.rcoroutine import Coroutine, BaseCoState, AbstractThunk, CoroutineExit
+
+from pypy.module.exceptions.interp_exceptions import W_SystemExit, _new_exception
 
 from pypy.rlib import rstack # for resume points
 from pypy.tool import stdlib_opcode as pythonopcode
@@ -48,6 +50,13 @@
         rstack.resume_point("appthunk", costate, returns=w_result)
         costate.w_tempval = w_result
 
+W_CoroutineExit = _new_exception('CoroutineExit', W_SystemExit,
+                        """Coroutine killed manually.""")
+
+# Should be moved to interp_stackless.py if it's ever implemented... Currently
+# used by pypy/lib/stackless.py.
+W_TaskletExit = _new_exception('TaskletExit', W_SystemExit, 
+            """Tasklet killed manually.""")
 
 class AppCoroutine(Coroutine): # XXX, StacklessFlags):
 
@@ -92,6 +101,13 @@
         w_ret, state.w_tempval = state.w_tempval, space.w_None
         return w_ret
 
+    def switch(self):
+        space = self.space
+        try:
+            Coroutine.switch(self)
+        except CoroutineExit:
+            raise OperationError(self.costate.w_CoroutineExit, space.w_None)
+
     def w_finished(self, w_excinfo):
         pass
 
@@ -102,6 +118,9 @@
             w_excvalue = operror.get_w_value(space)
             w_exctraceback = operror.application_traceback
             w_excinfo = space.newtuple([w_exctype, w_excvalue, w_exctraceback])
+            
+            if w_exctype is self.costate.w_CoroutineExit:
+                self.coroutine_exit = True
         else:
             w_N = space.w_None
             w_excinfo = space.newtuple([w_N, w_N, w_N])
@@ -118,6 +137,23 @@
 
     def w_kill(self):
         self.kill()
+            
+    def w_throw(self, w_type, w_value=None, w_traceback=None):
+        space = self.space
+
+        operror = OperationError(w_type, w_value)
+        operror.normalize_exception(space)
+        
+        if not space.is_w(w_traceback, space.w_None):
+            from pypy.interpreter import pytraceback
+            tb = space.interpclass_w(w_traceback)
+            if tb is None or not space.is_true(space.isinstance(tb, 
+                space.gettypeobject(pytraceback.PyTraceback.typedef))):
+                raise OperationError(space.w_TypeError,
+                      space.wrap("throw: arg 3 must be a traceback or None"))
+            operror.application_traceback = tb
+        
+        self._kill(operror)
 
     def _userdel(self):
         if self.get_is_zombie():
@@ -309,6 +345,7 @@
                       unwrap_spec=['self', W_Root, Arguments]),
     switch = interp2app(AppCoroutine.w_switch),
     kill = interp2app(AppCoroutine.w_kill),
+    throw = interp2app(AppCoroutine.w_throw),
     finished = interp2app(AppCoroutine.w_finished),
     is_alive = GetSetProperty(AppCoroutine.w_get_is_alive),
     is_zombie = GetSetProperty(AppCoroutine.w_get_is_zombie,
@@ -330,6 +367,26 @@
         BaseCoState.__init__(self)
         self.w_tempval = space.w_None
         self.space = space
+
+        # Exporting new exception to space
+        self.w_CoroutineExit = space.gettypefor(W_CoroutineExit)
+        space.setitem(
+                      space.exceptions_module.w_dict, 
+                      space.new_interned_str('CoroutineExit'), 
+                      self.w_CoroutineExit) 
+        space.setitem(space.builtin.w_dict, 
+                      space.new_interned_str('CoroutineExit'), 
+                      self.w_CoroutineExit)
+        
+        # Should be moved to interp_stackless.py if it's ever implemented...
+        self.w_TaskletExit = space.gettypefor(W_TaskletExit)
+        space.setitem(
+                      space.exceptions_module.w_dict, 
+                      space.new_interned_str('TaskletExit'), 
+                      self.w_TaskletExit) 
+        space.setitem(space.builtin.w_dict, 
+                      space.new_interned_str('TaskletExit'), 
+                      self.w_TaskletExit)  
         
     def post_install(self):
         self.current = self.main = AppCoroutine(self.space, state=self)

Modified: pypy/trunk/pypy/module/_stackless/rcoroutine.py
==============================================================================
--- pypy/trunk/pypy/module/_stackless/rcoroutine.py	(original)
+++ pypy/trunk/pypy/module/_stackless/rcoroutine.py	Thu Jul  1 20:51:08 2010
@@ -7,3 +7,4 @@
 BaseCoState = d['BaseCoState']
 AbstractThunk = d['AbstractThunk']
 syncstate = d['syncstate']
+CoroutineExit = d['CoroutineExit']

Modified: pypy/trunk/pypy/module/_stackless/test/test_coroutine.py
==============================================================================
--- pypy/trunk/pypy/module/_stackless/test/test_coroutine.py	(original)
+++ pypy/trunk/pypy/module/_stackless/test/test_coroutine.py	Thu Jul  1 20:51:08 2010
@@ -84,8 +84,7 @@
         assert not co.is_alive
 
     def test_kill_running(self):
-        skip("kill is not really working (there is only CoroutineExit, "
-             "which is not an app-level exception)")
+        coroutineexit = []
         import _stackless as stackless
         main = stackless.coroutine.getcurrent()
         result = []
@@ -96,6 +95,9 @@
                 result.append(1)
                 main.switch()
                 x = 3
+            except CoroutineExit:
+                coroutineexit.append(True)
+                raise
             finally:
                 result.append(x)
             result.append(4)
@@ -107,6 +109,7 @@
         co.kill()
         assert not co.is_alive
         assert result == [1, 2]
+        assert coroutineexit == [True]
 
     def test_bogus_bind(self):
         import _stackless as stackless

Modified: pypy/trunk/pypy/rlib/rcoroutine.py
==============================================================================
--- pypy/trunk/pypy/rlib/rcoroutine.py	(original)
+++ pypy/trunk/pypy/rlib/rcoroutine.py	Thu Jul  1 20:51:08 2010
@@ -32,6 +32,8 @@
 from pypy.rlib.rstack import yield_current_frame_to_caller, resume_point
 from pypy.rlib.objectmodel import we_are_translated
 
+from pypy.interpreter.error import OperationError
+
 try:
     from greenlet import greenlet
     main_greenlet = greenlet.getcurrent()
@@ -158,6 +160,7 @@
             self.costate = state
             self.parent = None
             self.thunk = None
+            self.coroutine_exit = False
 
         def __repr__(self):
             'NOT_RPYTHON'
@@ -236,11 +239,12 @@
                     self = state.current
                     self.finish(exc)
             except CoroutineExit:
-                # ignore a shutdown exception
                 pass
             except Exception, e:
-                # redirect all unhandled exceptions to the parent
-                syncstate.push_exception(e)
+                if self.coroutine_exit is False:
+                    # redirect all unhandled exceptions to the parent
+                    syncstate.push_exception(e)
+
             while self.parent is not None and self.parent.frame is None:
                 # greenlet behavior is fine
                 self.parent = self.parent.parent
@@ -257,10 +261,13 @@
             syncstate.switched(incoming_frame)
 
         def kill(self):
+            self._kill(CoroutineExit())
+
+        def _kill(self, exc):
             if self.frame is None:
                 return
             state = self.costate
-            syncstate.push_exception(CoroutineExit())
+            syncstate.push_exception(exc)
             # careful here - if setting self.parent to state.current would
             # create a loop, break it.  The assumption is that 'self'
             # will die, so that state.current's chain of parents can be



More information about the Pypy-commit mailing list