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