Author: Armin Rigo <[email protected]>
Branch: stacklet
Changeset: r46363:d6c8d2cd6a3a
Date: 2011-08-08 10:28 +0200
http://bitbucket.org/pypy/pypy/changeset/d6c8d2cd6a3a/

Log:    Fixes, and add a greenlet-style exception passing mechanism.

        I *think* that this is the theoretically correct way to handle
        exceptions, while at the same time it would be theoretically
        incorrect to expose the StackTreeNode to app-level, because that
        would make stacklets non-composable.

diff --git a/pypy/module/_stacklet/interp_stacklet.py 
b/pypy/module/_stacklet/interp_stacklet.py
--- a/pypy/module/_stacklet/interp_stacklet.py
+++ b/pypy/module/_stacklet/interp_stacklet.py
@@ -8,6 +8,7 @@
 from pypy.interpreter.baseobjspace import Wrappable
 from pypy.interpreter.typedef import TypeDef
 from pypy.interpreter.gateway import interp2app
+from pypy.rlib.debug import ll_assert
 
 
 class SThread(StackletThread):
@@ -19,20 +20,39 @@
         self.ec = ec
         self.w_error = space.getattr(w_module, space.wrap('error'))
         self.pending_exception = None
-        self.main_stacklet = None
+        self.current_stack = StackTreeNode(None)
 
     def __del__(self):
         if self.main_stacklet is not None:
             self.main_stacklet.__del__()
         StackletThread.__del__(self)
 
-    def new_stacklet_object(self, h):
+    def new_stacklet_object(self, h, in_new_stack):
+        # Called when we switched somewhere else.  'h' is the handle of
+        # the new stacklet, i.e. what we just switched away from.
+        # (Important: only called when the switch was successful, not
+        # when it raises MemoryError.)
+        #
+        # Update self.current_stack.
+        in_old_stack = self.current_stack
+        ll_assert(in_old_stack.current_stacklet is None,
+                  "in_old_stack should not have a current_stacklet")
+        ll_assert(in_new_stack is not in_old_stack,
+                  "stack switch: switch to itself")
+        in_new_stack.current_stacklet = None
+        self.current_stack = in_new_stack
+        #
         if self.pending_exception is None:
+            #
+            # Normal case.
             if self.is_empty_handle(h):
                 return self.space.w_None
             else:
-                return self.space.wrap(W_Stacklet(self, h))
+                res = W_Stacklet(self, h)
+                in_old_stack.current_stacklet = res
+                return self.space.wrap(res)
         else:
+            # Got an exception; re-raise it.
             e = self.pending_exception
             self.pending_exception = None
             if not self.is_empty_handle(h):
@@ -47,6 +67,41 @@
 ExecutionContext.stacklet_thread = None
 
 
+class StackTreeNode(object):
+    # When one stacklet gets an exception, it is propagated to the
+    # "parent" stacklet.  Parents make a tree.  Each stacklet is
+    # conceptually part of a stack that doesn't change when we switch
+    # away and back.  The "parent stack" is the stack from which we
+    # created the new() stack.  (This part works like greenlets.)
+    #
+    # It is important that we have *no* app-level access to this tree.
+    # It would break the 'composability' of stacklets.
+    #
+    def __init__(self, parent):
+        self.parent = parent
+        self.current_stacklet = None
+
+    def raising_exception(self):
+        while self.current_stacklet is None:
+            self = self.parent
+            ll_assert(self is not None, "StackTreeNode chain is empty!")
+        res = self.current_stacklet
+        self.current_stacklet = None
+        try:
+            return res.consume_handle()
+        except OperationError:
+            ll_assert(False, "StackTreeNode contains an empty stacklet")
+
+    def __repr__(self):
+        s = '|>'
+        k = self
+        while k:
+            s = hex(id(k)) + ' ' + s
+            k = k.parent
+        s = '<StackTreeNode |' + s
+        return s
+
+
 class W_Stacklet(Wrappable):
     def __init__(self, sthread, h):
         self.sthread = sthread
@@ -62,8 +117,6 @@
         h = self.h
         if h:
             self.h = self.sthread.get_null_handle()
-            if self is self.sthread.main_stacklet:
-                self.sthread.main_stacklet = None
             return h
         else:
             space = self.sthread.space
@@ -72,15 +125,20 @@
                 space.wrap("stacklet has already been resumed"))
 
     def switch(self, space):
-        h = self.consume_handle()
         sthread = self.sthread
         ec = sthread.ec
+        stack = sthread.current_stack
         saved_frame_top = ec.topframeref
         try:
-            h = sthread.switch(h)
+            h1 = self.consume_handle()
+            try:
+                h = sthread.switch(h1)
+            except MemoryError:
+                self.h = h1    # try to restore
+                raise
         finally:
             ec.topframeref = saved_frame_top
-        return sthread.new_stacklet_object(h)
+        return sthread.new_stacklet_object(h, stack)
 
     def is_pending(self, space):
         return space.newbool(bool(self.h))
@@ -107,30 +165,39 @@
     start_state.sthread = None
     start_state.w_callable = None
     start_state.args = None
+    ready = False
+    parentstacknode = sthread.current_stack
     #
     try:
         space = sthread.space
-        stacklet = W_Stacklet(sthread, h)
-        if sthread.main_stacklet is None:
-            sthread.main_stacklet = stacklet
-        args = args.prepend(space.wrap(stacklet))
+        stacknode = StackTreeNode(parentstacknode)
+        stacklet = sthread.new_stacklet_object(h, stacknode)
+        ready = True
+        args = args.prepend(stacklet)
         w_result = space.call_args(w_callable, args)
         #
-        result = space.interp_w(W_Stacklet, w_result)
-        return result.consume_handle()
+        try:
+            result = space.interp_w(W_Stacklet, w_result)
+            return result.consume_handle()
+        except OperationError, e:
+            w_value = e.get_w_value(space)
+            msg = 'returning from new stacklet: ' + space.str_w(w_value)
+            raise OperationError(e.w_type, space.wrap(msg))
     #
     except Exception, e:
         sthread.pending_exception = e
         if not we_are_translated():
+            print >> sys.stderr
+            print >> sys.stderr, '*** exception in stacklet ***'
             sthread.pending_tb = sys.exc_info()[2]
-        if sthread.main_stacklet is None:
-            main_stacklet_handle = h
+            import traceback
+            traceback.print_exc(sthread.pending_tb)
+            print >> sys.stderr, '***'
+            #import pdb; pdb.post_mortem(sthread.pending_tb)
+        if ready:
+            return parentstacknode.raising_exception()
         else:
-            main_stacklet_handle = sthread.main_stacklet.h
-            sthread.main_stacklet.h = sthread.get_null_handle()
-            sthread.main_stacklet = None
-        assert main_stacklet_handle
-        return main_stacklet_handle
+            return h      # corner case, try with just returning h...
 
 def stacklet_new(space, w_callable, __args__):
     ec = space.getexecutioncontext()
@@ -140,10 +207,11 @@
     start_state.sthread = sthread
     start_state.w_callable = w_callable
     start_state.args = __args__
+    stack = sthread.current_stack
     saved_frame_top = ec.topframeref
     try:
         ec.topframeref = jit.vref_None
         h = sthread.new(new_stacklet_callback)
     finally:
         ec.topframeref = saved_frame_top
-    return sthread.new_stacklet_object(h)
+    return sthread.new_stacklet_object(h, stack)
diff --git a/pypy/module/_stacklet/test/test_stacklet.py 
b/pypy/module/_stacklet/test/test_stacklet.py
--- a/pypy/module/_stacklet/test/test_stacklet.py
+++ b/pypy/module/_stacklet/test/test_stacklet.py
@@ -98,6 +98,87 @@
         assert h2 is None
         assert seen == [1, 2, 3]
 
+    def test_go_depth2(self):
+        from _stacklet import newstacklet
+        #
+        def depth2(h):
+            seen.append(2)
+            return h
+        #
+        def depth1(h):
+            seen.append(1)
+            h2 = newstacklet(depth2)
+            assert h2 is None
+            seen.append(3)
+            return h
+        #
+        seen = []
+        h = newstacklet(depth1)
+        assert h is None
+        assert seen == [1, 2, 3]
+
+    def test_exception_depth2(self):
+        from _stacklet import newstacklet
+        #
+        def depth2(h):
+            seen.append(2)
+            raise ValueError
+        #
+        def depth1(h):
+            seen.append(1)
+            try:
+                newstacklet(depth2)
+            except ValueError:
+                seen.append(3)
+            return h
+        #
+        seen = []
+        h = newstacklet(depth1)
+        assert h is None
+        assert seen == [1, 2, 3]
+
+    def test_exception_with_switch(self):
+        from _stacklet import newstacklet
+        #
+        def depth1(h):
+            seen.append(1)
+            h = h.switch()
+            seen.append(3)
+            raise ValueError
+        #
+        seen = []
+        h = newstacklet(depth1)
+        seen.append(2)
+        raises(ValueError, h.switch)
+        assert seen == [1, 2, 3]
+
+    def test_exception_with_switch_depth2(self):
+        from _stacklet import newstacklet
+        #
+        def depth2(h):
+            seen.append(4)
+            h = h.switch()
+            seen.append(6)
+            raise ValueError
+        #
+        def depth1(h):
+            seen.append(1)
+            h = h.switch()
+            seen.append(3)
+            h2 = newstacklet(depth2)
+            seen.append(5)
+            raises(ValueError, h2.switch)
+            assert not h2.is_pending()
+            seen.append(7)
+            raise KeyError
+        #
+        seen = []
+        h = newstacklet(depth1)
+        seen.append(2)
+        raises(KeyError, h.switch)
+        assert not h.is_pending()
+        assert seen == [1, 2, 3, 4, 5, 6, 7]
+
     def test_various_depths(self):
         skip("may fail on top of CPython")
         # run it from test_translated, but not while being actually translated
diff --git a/pypy/module/_stacklet/test/test_translated.py 
b/pypy/module/_stacklet/test/test_translated.py
--- a/pypy/module/_stacklet/test/test_translated.py
+++ b/pypy/module/_stacklet/test/test_translated.py
@@ -63,6 +63,7 @@
             if Task.comefrom != -42:
                 assert 0 <= Task.comefrom < 10
                 task = Task.tasks[Task.comefrom]
+                print "setting %r.h = %r" % (task, h)
                 assert task.h is None
                 task.h = h
             else:
@@ -81,6 +82,7 @@
     assert self.h is None
     assert 0 <= Task.comefrom < 10
     task = Task.tasks[Task.comefrom]
+    print "initializing %r.h = %r" % (task, h)
     assert task.h is None
     assert type(h) is _stacklet.Stacklet
     task.h = h
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to