[pypy-commit] pypy default: Merge 'continulet-pickle': provides basic pickling support to
arigo
noreply at buildbot.pypy.org
Mon Sep 26 18:24:32 CEST 2011
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r47612:264c5750f695
Date: 2011-09-26 17:34 +0200
http://bitbucket.org/pypy/pypy/changeset/264c5750f695/
Log: Merge 'continulet-pickle': provides basic pickling support to
continulets. Extends naturally to greenlets and coroutines.
Tasklets are a bit more of a mess, so this is left unsolved for now.
diff --git a/lib_pypy/greenlet.py b/lib_pypy/greenlet.py
--- a/lib_pypy/greenlet.py
+++ b/lib_pypy/greenlet.py
@@ -96,7 +96,16 @@
@property
def gr_frame(self):
- raise NotImplementedError("attribute 'gr_frame' of greenlet objects")
+ # xxx this doesn't work when called on either the current or
+ # the main greenlet of another thread
+ if self is getcurrent():
+ return None
+ if self.__main:
+ self = getcurrent()
+ f = _continulet.__reduce__(self)[2][0]
+ if not f:
+ return None
+ return f.f_back.f_back.f_back # go past start(), __switch(), switch()
# ____________________________________________________________
# Internal stuff
diff --git a/lib_pypy/pypy_test/test_stackless_pickling.py b/lib_pypy/pypy_test/test_stackless_pickling.py
--- a/lib_pypy/pypy_test/test_stackless_pickling.py
+++ b/lib_pypy/pypy_test/test_stackless_pickling.py
@@ -1,7 +1,3 @@
-"""
-this test should probably not run from CPython or py.py.
-I'm not entirely sure, how to do that.
-"""
from __future__ import absolute_import
from py.test import skip
try:
@@ -16,11 +12,15 @@
class Test_StacklessPickling:
+ def test_pickle_main_coroutine(self):
+ import stackless, pickle
+ s = pickle.dumps(stackless.coroutine.getcurrent())
+ print s
+ c = pickle.loads(s)
+ assert c is stackless.coroutine.getcurrent()
+
def test_basic_tasklet_pickling(self):
- try:
- import stackless
- except ImportError:
- skip("can't load stackless and don't know why!!!")
+ import stackless
from stackless import run, schedule, tasklet
import pickle
diff --git a/lib_pypy/stackless.py b/lib_pypy/stackless.py
--- a/lib_pypy/stackless.py
+++ b/lib_pypy/stackless.py
@@ -5,7 +5,6 @@
"""
-import traceback
import _continuation
class TaskletExit(Exception):
@@ -14,33 +13,46 @@
CoroutineExit = TaskletExit
+def _coroutine_getcurrent():
+ "Returns the current coroutine (i.e. the one which called this function)."
+ try:
+ return _tls.current_coroutine
+ except AttributeError:
+ # first call in this thread: current == main
+ return _coroutine_getmain()
+
+def _coroutine_getmain():
+ try:
+ return _tls.main_coroutine
+ except AttributeError:
+ # create the main coroutine for this thread
+ continulet = _continuation.continulet
+ main = coroutine()
+ main._frame = continulet.__new__(continulet)
+ main._is_started = -1
+ _tls.current_coroutine = _tls.main_coroutine = main
+ return _tls.main_coroutine
+
+
class coroutine(object):
- "we can't have continulet as a base, because continulets can't be rebound"
+ _is_started = 0 # 0=no, 1=yes, -1=main
def __init__(self):
self._frame = None
- self.is_zombie = False
-
- def __getattr__(self, attr):
- return getattr(self._frame, attr)
-
- def __del__(self):
- self.is_zombie = True
- del self._frame
- self._frame = None
def bind(self, func, *argl, **argd):
"""coro.bind(f, *argl, **argd) -> None.
binds function f to coro. f will be called with
arguments *argl, **argd
"""
- if self._frame is None or not self._frame.is_pending():
- def run(c):
- _tls.current_coroutine = self
- return func(*argl, **argd)
- self._frame = frame = _continuation.continulet(run)
- else:
+ if self.is_alive:
raise ValueError("cannot bind a bound coroutine")
+ def run(c):
+ _tls.current_coroutine = self
+ self._is_started = 1
+ return func(*argl, **argd)
+ self._is_started = 0
+ self._frame = _continuation.continulet(run)
def switch(self):
"""coro.switch() -> returnvalue
@@ -48,7 +60,7 @@
f finishes, the returnvalue is that of f, otherwise
None is returned
"""
- current = _getcurrent()
+ current = _coroutine_getcurrent()
try:
current._frame.switch(to=self._frame)
finally:
@@ -56,37 +68,30 @@
def kill(self):
"""coro.kill() : kill coroutine coro"""
- current = _getcurrent()
+ current = _coroutine_getcurrent()
try:
current._frame.throw(CoroutineExit, to=self._frame)
finally:
_tls.current_coroutine = current
- def _is_alive(self):
- if self._frame is None:
- return False
- return not self._frame.is_pending()
- is_alive = property(_is_alive)
- del _is_alive
+ @property
+ def is_alive(self):
+ return self._is_started < 0 or (
+ self._frame is not None and self._frame.is_pending())
- def getcurrent():
- """coroutine.getcurrent() -> the currently running coroutine"""
- return _getcurrent()
- getcurrent = staticmethod(getcurrent)
+ @property
+ def is_zombie(self):
+ return self._is_started > 0 and not self._frame.is_pending()
+
+ getcurrent = staticmethod(_coroutine_getcurrent)
def __reduce__(self):
- raise TypeError, 'pickling is not possible based upon continulets'
+ if self._is_started < 0:
+ return _coroutine_getmain, ()
+ else:
+ return type(self), (), self.__dict__
-def _getcurrent():
- "Returns the current coroutine (i.e. the one which called this function)."
- try:
- return _tls.current_coroutine
- except AttributeError:
- # first call in this thread: current == main
- _coroutine_create_main()
- return _tls.current_coroutine
-
try:
from thread import _local
except ImportError:
@@ -95,14 +100,8 @@
_tls = _local()
-def _coroutine_create_main():
- # create the main coroutine for this thread
- _tls.current_coroutine = None
- main_coroutine = coroutine()
- typ = _continuation.continulet
- main_coroutine._frame = typ.__new__(typ)
- _tls.main_coroutine = main_coroutine
- _tls.current_coroutine = main_coroutine
+
+# ____________________________________________________________
from collections import deque
@@ -148,10 +147,7 @@
_last_task = next
assert not next.blocked
if next is not current:
- #try:
- next.switch()
- #except CoroutineExit: --- they are the same anyway
- # raise TaskletExit
+ next.switch()
return current
def set_schedule_callback(callback):
@@ -175,34 +171,6 @@
raise self.type, self.value, self.traceback
#
-# helpers for pickling
-#
-
-_stackless_primitive_registry = {}
-
-def register_stackless_primitive(thang, retval_expr='None'):
- import types
- func = thang
- if isinstance(thang, types.MethodType):
- func = thang.im_func
- code = func.func_code
- _stackless_primitive_registry[code] = retval_expr
- # It is not too nice to attach info via the code object, but
- # I can't think of a better solution without a real transform.
-
-def rewrite_stackless_primitive(coro_state, alive, tempval):
- flags, frame, thunk, parent = coro_state
- while frame is not None:
- retval_expr = _stackless_primitive_registry.get(frame.f_code)
- if retval_expr:
- # this tasklet needs to stop pickling here and return its value.
- tempval = eval(retval_expr, globals(), frame.f_locals)
- coro_state = flags, frame, thunk, parent
- break
- frame = frame.f_back
- return coro_state, alive, tempval
-
-#
#
class channel(object):
@@ -354,8 +322,6 @@
"""
return self._channel_action(None, -1)
- register_stackless_primitive(receive, retval_expr='receiver.tempval')
-
def send_exception(self, exp_type, msg):
self.send(bomb(exp_type, exp_type(msg)))
@@ -372,9 +338,8 @@
the runnables list.
"""
return self._channel_action(msg, 1)
-
- register_stackless_primitive(send)
-
+
+
class tasklet(coroutine):
"""
A tasklet object represents a tiny task in a Python thread.
@@ -456,7 +421,7 @@
self.func = None
coroutine.bind(self, _func)
- back = _getcurrent()
+ back = _coroutine_getcurrent()
coroutine.switch(self)
self.alive = True
_scheduler_append(self)
@@ -480,39 +445,6 @@
raise RuntimeError, "The current tasklet cannot be removed."
# not sure if I will revive this " Use t=tasklet().capture()"
_scheduler_remove(self)
-
- def __reduce__(self):
- one, two, coro_state = coroutine.__reduce__(self)
- assert one is coroutine
- assert two == ()
- # we want to get rid of the parent thing.
- # for now, we just drop it
- a, frame, c, d = coro_state
-
- # Removing all frames related to stackless.py.
- # They point to stuff we don't want to be pickled.
-
- pickleframe = frame
- while frame is not None:
- if frame.f_code == schedule.func_code:
- # Removing everything including and after the
- # call to stackless.schedule()
- pickleframe = frame.f_back
- break
- frame = frame.f_back
- if d:
- assert isinstance(d, coroutine)
- coro_state = a, pickleframe, c, None
- coro_state, alive, tempval = rewrite_stackless_primitive(coro_state, self.alive, self.tempval)
- inst_dict = self.__dict__.copy()
- inst_dict.pop('tempval', None)
- return self.__class__, (), (coro_state, alive, tempval, inst_dict)
-
- def __setstate__(self, (coro_state, alive, tempval, inst_dict)):
- coroutine.__setstate__(self, coro_state)
- self.__dict__.update(inst_dict)
- self.alive = alive
- self.tempval = tempval
def getmain():
"""
@@ -601,30 +533,7 @@
global _last_task
_global_task_id = 0
_main_tasklet = coroutine.getcurrent()
- try:
- _main_tasklet.__class__ = tasklet
- except TypeError: # we are running pypy-c
- class TaskletProxy(object):
- """TaskletProxy is needed to give the _main_coroutine tasklet behaviour"""
- def __init__(self, coro):
- self._coro = coro
-
- def __getattr__(self,attr):
- return getattr(self._coro,attr)
-
- def __str__(self):
- return '<tasklet %s a:%s>' % (self._task_id, self.is_alive)
-
- def __reduce__(self):
- return getmain, ()
-
- __repr__ = __str__
-
-
- global _main_coroutine
- _main_coroutine = _main_tasklet
- _main_tasklet = TaskletProxy(_main_tasklet)
- assert _main_tasklet.is_alive and not _main_tasklet.is_zombie
+ _main_tasklet.__class__ = tasklet # XXX HAAAAAAAAAAAAAAAAAAAAACK
_last_task = _main_tasklet
tasklet._init.im_func(_main_tasklet, label='main')
_squeue = deque()
diff --git a/py/_code/source.py b/py/_code/source.py
--- a/py/_code/source.py
+++ b/py/_code/source.py
@@ -139,7 +139,7 @@
trysource = self[start:end]
if trysource.isparseable():
return start, end
- return start, end
+ return start, len(self)
def getblockend(self, lineno):
# XXX
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -66,7 +66,7 @@
make_sure_not_resized(self.locals_stack_w)
check_nonneg(self.nlocals)
#
- if space.config.objspace.honor__builtins__ and w_globals is not None:
+ if space.config.objspace.honor__builtins__:
self.builtin = space.builtin.pick_builtin(w_globals)
# regular functions always have CO_OPTIMIZED and CO_NEWLOCALS.
# class bodies only have CO_NEWLOCALS.
diff --git a/pypy/module/_continuation/__init__.py b/pypy/module/_continuation/__init__.py
--- a/pypy/module/_continuation/__init__.py
+++ b/pypy/module/_continuation/__init__.py
@@ -37,4 +37,5 @@
interpleveldefs = {
'continulet': 'interp_continuation.W_Continulet',
'permute': 'interp_continuation.permute',
+ '_p': 'interp_continuation.unpickle', # pickle support
}
diff --git a/pypy/module/_continuation/interp_continuation.py b/pypy/module/_continuation/interp_continuation.py
--- a/pypy/module/_continuation/interp_continuation.py
+++ b/pypy/module/_continuation/interp_continuation.py
@@ -6,6 +6,7 @@
from pypy.interpreter.typedef import TypeDef
from pypy.interpreter.gateway import interp2app
from pypy.interpreter.pycode import PyCode
+from pypy.interpreter.pyframe import PyFrame
class W_Continulet(Wrappable):
@@ -23,24 +24,27 @@
if ec.stacklet_thread is not self.sthread:
global_state.clear()
raise geterror(self.space, "inter-thread support is missing")
- return ec
def descr_init(self, w_callable, __args__):
if self.sthread is not None:
raise geterror(self.space, "continulet already __init__ialized")
+ #
+ # hackish: build the frame "by hand", passing it the correct arguments
+ space = self.space
+ w_args, w_kwds = __args__.topacked()
+ bottomframe = space.createframe(get_entrypoint_pycode(space),
+ get_w_module_dict(space), None)
+ bottomframe.locals_stack_w[0] = space.wrap(self)
+ bottomframe.locals_stack_w[1] = w_callable
+ bottomframe.locals_stack_w[2] = w_args
+ bottomframe.locals_stack_w[3] = w_kwds
+ self.bottomframe = bottomframe
+ #
global_state.origin = self
- global_state.w_callable = w_callable
- global_state.args = __args__
- self.bottomframe = make_fresh_frame(self.space)
- self.sthread = build_sthread(self.space)
- try:
- self.h = self.sthread.new(new_stacklet_callback)
- if self.sthread.is_empty_handle(self.h): # early return
- raise MemoryError
- except MemoryError:
- self.sthread = None
- global_state.clear()
- raise getmemoryerror(self.space)
+ sthread = build_sthread(self.space)
+ self.sthread = sthread
+ h = sthread.new(new_stacklet_callback)
+ post_switch(sthread, h)
def switch(self, w_to):
sthread = self.sthread
@@ -66,7 +70,7 @@
if sthread.is_empty_handle(to.h):
global_state.clear()
raise geterror(self.space, "continulet already finished")
- ec = self.check_sthread()
+ self.check_sthread()
#
global_state.origin = self
if to is None:
@@ -76,13 +80,8 @@
# double switch: the final destination is to.h
global_state.destination = to
#
- try:
- do_switch(sthread, global_state.destination.h)
- except MemoryError:
- global_state.clear()
- raise getmemoryerror(self.space)
- #
- return get_result()
+ h = sthread.switch(global_state.destination.h)
+ return post_switch(sthread, h)
def descr_switch(self, w_value=None, w_to=None):
global_state.w_value = w_value
@@ -109,12 +108,26 @@
and not self.sthread.is_empty_handle(self.h))
return self.space.newbool(valid)
+ def descr__reduce__(self):
+ from pypy.module._continuation import interp_pickle
+ return interp_pickle.reduce(self)
+
+ def descr__setstate__(self, w_args):
+ from pypy.module._continuation import interp_pickle
+ interp_pickle.setstate(self, w_args)
+
def W_Continulet___new__(space, w_subtype, __args__):
r = space.allocate_instance(W_Continulet, w_subtype)
r.__init__(space)
return space.wrap(r)
+def unpickle(space, w_subtype):
+ """Pickle support."""
+ r = space.allocate_instance(W_Continulet, w_subtype)
+ r.__init__(space)
+ return space.wrap(r)
+
W_Continulet.typedef = TypeDef(
'continulet',
@@ -124,9 +137,10 @@
switch = interp2app(W_Continulet.descr_switch),
throw = interp2app(W_Continulet.descr_throw),
is_pending = interp2app(W_Continulet.descr_is_pending),
+ __reduce__ = interp2app(W_Continulet.descr__reduce__),
+ __setstate__= interp2app(W_Continulet.descr__setstate__),
)
-
# ____________________________________________________________
# Continulet objects maintain a dummy frame object in order to ensure
@@ -135,27 +149,40 @@
class State:
def __init__(self, space):
- from pypy.interpreter.astcompiler.consts import CO_OPTIMIZED
- self.space = space
+ self.space = space
w_module = space.getbuiltinmodule('_continuation')
self.w_error = space.getattr(w_module, space.wrap('error'))
- self.w_memoryerror = OperationError(space.w_MemoryError, space.w_None)
- self.dummy_pycode = PyCode(space, 0, 0, 0, CO_OPTIMIZED,
- '', [], [], [], '',
- '<bottom of continulet>', 0, '', [], [],
- hidden_applevel=True)
+ # the following function switches away immediately, so that
+ # continulet.__init__() doesn't immediately run func(), but it
+ # also has the hidden purpose of making sure we have a single
+ # bottomframe for the whole duration of the continulet's run.
+ # Hackish: only the func_code is used, and used in the context
+ # of w_globals == this module, so we can access the name
+ # 'continulet' directly.
+ w_code = space.appexec([], '''():
+ def start(c, func, args, kwds):
+ if continulet.switch(c) is not None:
+ raise TypeError(
+ "can\'t send non-None value to a just-started continulet")
+ return func(c, *args, **kwds)
+ return start.func_code
+ ''')
+ self.entrypoint_pycode = space.interp_w(PyCode, w_code)
+ self.entrypoint_pycode.hidden_applevel = True
+ self.w_unpickle = w_module.get('_p')
+ self.w_module_dict = w_module.getdict(space)
def geterror(space, message):
cs = space.fromcache(State)
return OperationError(cs.w_error, space.wrap(message))
-def getmemoryerror(space):
+def get_entrypoint_pycode(space):
cs = space.fromcache(State)
- return cs.w_memoryerror
+ return cs.entrypoint_pycode
-def make_fresh_frame(space):
+def get_w_module_dict(space):
cs = space.fromcache(State)
- return space.FrameClass(space, cs.dummy_pycode, None, None)
+ return cs.w_module_dict
# ____________________________________________________________
@@ -166,6 +193,9 @@
StackletThread.__init__(self, space.config)
self.space = space
self.ec = ec
+ # for unpickling
+ from pypy.rlib.rweakref import RWeakKeyDictionary
+ self.frame2continulet = RWeakKeyDictionary(PyFrame, W_Continulet)
ExecutionContext.stacklet_thread = None
@@ -176,8 +206,6 @@
def clear(self):
self.origin = None
self.destination = None
- self.w_callable = None
- self.args = None
self.w_value = None
self.propagate_exception = None
global_state = GlobalState()
@@ -185,27 +213,13 @@
def new_stacklet_callback(h, arg):
- self = global_state.origin
- w_callable = global_state.w_callable
- args = global_state.args
+ self = global_state.origin
+ self.h = h
global_state.clear()
- try:
- do_switch(self.sthread, h)
- except MemoryError:
- return h # oups! do an early return in this case
- #
space = self.space
try:
- assert self.sthread.ec.topframeref() is None
- self.sthread.ec.topframeref = jit.non_virtual_ref(self.bottomframe)
- if global_state.propagate_exception is not None:
- raise global_state.propagate_exception # just propagate it further
- if global_state.w_value is not space.w_None:
- raise OperationError(space.w_TypeError, space.wrap(
- "can't send non-None value to a just-started continulet"))
-
- args = args.prepend(self.space.wrap(self))
- w_result = space.call_args(w_callable, args)
+ frame = self.bottomframe
+ w_result = frame.execute_frame()
except Exception, e:
global_state.propagate_exception = e
else:
@@ -215,9 +229,7 @@
global_state.destination = self
return self.h
-
-def do_switch(sthread, h):
- h = sthread.switch(h)
+def post_switch(sthread, h):
origin = global_state.origin
self = global_state.destination
global_state.origin = None
@@ -228,6 +240,8 @@
sthread.ec.topframeref = self.bottomframe.f_backref
self.bottomframe.f_backref = origin.bottomframe.f_backref
origin.bottomframe.f_backref = current
+ #
+ return get_result()
def get_result():
if global_state.propagate_exception:
diff --git a/pypy/module/_continuation/interp_pickle.py b/pypy/module/_continuation/interp_pickle.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/interp_pickle.py
@@ -0,0 +1,128 @@
+from pypy.tool import stdlib_opcode as pythonopcode
+from pypy.rlib import jit
+from pypy.interpreter.error import OperationError
+from pypy.interpreter.pyframe import PyFrame
+from pypy.module._continuation.interp_continuation import State, global_state
+from pypy.module._continuation.interp_continuation import build_sthread
+from pypy.module._continuation.interp_continuation import post_switch
+from pypy.module._continuation.interp_continuation import get_result, geterror
+
+
+def getunpickle(space):
+ cs = space.fromcache(State)
+ return cs.w_unpickle
+
+
+def reduce(self):
+ # xxx this is known to be not completely correct with respect
+ # to subclasses, e.g. no __slots__ support, no looking for a
+ # __getnewargs__ or __getstate__ defined in the subclass, etc.
+ # Doing the right thing looks involved, though...
+ space = self.space
+ if self.sthread is None:
+ w_frame = space.w_False
+ elif self.sthread.is_empty_handle(self.h):
+ w_frame = space.w_None
+ else:
+ w_frame = space.wrap(self.bottomframe)
+ w_continulet_type = space.type(space.wrap(self))
+ w_dict = self.getdict(space) or space.w_None
+ args = [getunpickle(space),
+ space.newtuple([w_continulet_type]),
+ space.newtuple([w_frame, w_dict]),
+ ]
+ return space.newtuple(args)
+
+def setstate(self, w_args):
+ space = self.space
+ if self.sthread is not None:
+ raise geterror(space, "continulet.__setstate__() on an already-"
+ "initialized continulet")
+ w_frame, w_dict = space.fixedview(w_args, expected_length=2)
+ if not space.is_w(w_dict, space.w_None):
+ self.setdict(space, w_dict)
+ if space.is_w(w_frame, space.w_False):
+ return # not initialized
+ sthread = build_sthread(self.space)
+ self.sthread = sthread
+ self.bottomframe = space.interp_w(PyFrame, w_frame, can_be_None=True)
+ #
+ global_state.origin = self
+ if self.bottomframe is not None:
+ sthread.frame2continulet.set(self.bottomframe, self)
+ self.h = sthread.new(resume_trampoline_callback)
+ get_result() # propagate the eventual MemoryError
+
+# ____________________________________________________________
+
+def resume_trampoline_callback(h, arg):
+ self = global_state.origin
+ self.h = h
+ space = self.space
+ sthread = self.sthread
+ try:
+ global_state.clear()
+ if self.bottomframe is None:
+ w_result = space.w_None
+ else:
+ h = sthread.switch(self.h)
+ try:
+ w_result = post_switch(sthread, h)
+ operr = None
+ except OperationError, e:
+ w_result = None
+ operr = e
+ #
+ while True:
+ ec = sthread.ec
+ frame = ec.topframeref()
+ assert frame is not None # XXX better error message
+ exit_continulet = sthread.frame2continulet.get(frame)
+ #
+ continue_after_call(frame)
+ #
+ # small hack: unlink frame out of the execution context,
+ # because execute_frame will add it there again
+ ec.topframeref = frame.f_backref
+ #
+ try:
+ w_result = frame.execute_frame(w_result, operr)
+ operr = None
+ except OperationError, e:
+ w_result = None
+ operr = e
+ if exit_continulet is not None:
+ self = exit_continulet
+ break
+ sthread.ec.topframeref = jit.vref_None
+ if operr:
+ raise operr
+ except Exception, e:
+ global_state.propagate_exception = e
+ else:
+ global_state.w_value = w_result
+ global_state.origin = self
+ global_state.destination = self
+ return self.h
+
+def continue_after_call(frame):
+ code = frame.pycode.co_code
+ instr = frame.last_instr
+ opcode = ord(code[instr])
+ map = pythonopcode.opmap
+ call_ops = [map['CALL_FUNCTION'], map['CALL_FUNCTION_KW'],
+ map['CALL_FUNCTION_VAR'], map['CALL_FUNCTION_VAR_KW'],
+ map['CALL_METHOD']]
+ assert opcode in call_ops # XXX check better, and complain better
+ instr += 1
+ oparg = ord(code[instr]) | ord(code[instr + 1]) << 8
+ nargs = oparg & 0xff
+ nkwds = (oparg >> 8) & 0xff
+ if nkwds == 0: # only positional arguments
+ # fast paths leaves things on the stack, pop them
+ if (frame.space.config.objspace.opcodes.CALL_METHOD and
+ opcode == map['CALL_METHOD']):
+ frame.dropvalues(nargs + 2)
+ elif opcode == map['CALL_FUNCTION']:
+ frame.dropvalues(nargs + 1)
+ frame.last_instr = instr + 1 # continue after the call
diff --git a/pypy/module/_continuation/test/test_stacklet.py b/pypy/module/_continuation/test/test_stacklet.py
--- a/pypy/module/_continuation/test/test_stacklet.py
+++ b/pypy/module/_continuation/test/test_stacklet.py
@@ -13,7 +13,7 @@
from _continuation import continulet
#
def empty_callback(c):
- pass
+ never_called
#
c = continulet(empty_callback)
assert type(c) is continulet
@@ -36,7 +36,7 @@
from _continuation import continulet, error
#
def empty_callback(c1):
- pass
+ never_called
#
c = continulet(empty_callback)
raises(error, c.__init__, empty_callback)
diff --git a/pypy/module/_continuation/test/test_zpickle.py b/pypy/module/_continuation/test/test_zpickle.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_continuation/test/test_zpickle.py
@@ -0,0 +1,262 @@
+from pypy.conftest import gettestobjspace
+
+
+class AppTestCopy:
+ def setup_class(cls):
+ cls.space = gettestobjspace(usemodules=('_continuation',),
+ CALL_METHOD=True)
+ cls.space.config.translation.continuation = True
+
+ def test_basic_setup(self):
+ from _continuation import continulet
+ lst = [4]
+ co = continulet(lst.append)
+ assert lst == [4]
+ res = co.switch()
+ assert res is None
+ assert lst == [4, co]
+
+ def test_copy_continulet_not_started(self):
+ from _continuation import continulet, error
+ import copy
+ lst = []
+ co = continulet(lst.append)
+ co2, lst2 = copy.deepcopy((co, lst))
+ #
+ assert lst == []
+ co.switch()
+ assert lst == [co]
+ #
+ assert lst2 == []
+ co2.switch()
+ assert lst2 == [co2]
+
+ def test_copy_continulet_not_started_multiple(self):
+ from _continuation import continulet, error
+ import copy
+ lst = []
+ co = continulet(lst.append)
+ co2, lst2 = copy.deepcopy((co, lst))
+ co3, lst3 = copy.deepcopy((co, lst))
+ co4, lst4 = copy.deepcopy((co, lst))
+ #
+ assert lst == []
+ co.switch()
+ assert lst == [co]
+ #
+ assert lst2 == []
+ co2.switch()
+ assert lst2 == [co2]
+ #
+ assert lst3 == []
+ co3.switch()
+ assert lst3 == [co3]
+ #
+ assert lst4 == []
+ co4.switch()
+ assert lst4 == [co4]
+
+ def test_copy_continulet_real(self):
+ import new, sys
+ mod = new.module('test_copy_continulet_real')
+ sys.modules['test_copy_continulet_real'] = mod
+ exec '''if 1:
+ from _continuation import continulet
+ import copy
+ def f(co, x):
+ co.switch(x + 1)
+ co.switch(x + 2)
+ return x + 3
+ co = continulet(f, 40)
+ res = co.switch()
+ assert res == 41
+ co2 = copy.deepcopy(co)
+ #
+ res = co2.switch()
+ assert res == 42
+ assert co2.is_pending()
+ res = co2.switch()
+ assert res == 43
+ assert not co2.is_pending()
+ #
+ res = co.switch()
+ assert res == 42
+ assert co.is_pending()
+ res = co.switch()
+ assert res == 43
+ assert not co.is_pending()
+ ''' in mod.__dict__
+
+ def test_copy_continulet_already_finished(self):
+ from _continuation import continulet, error
+ import copy
+ lst = []
+ co = continulet(lst.append)
+ co.switch()
+ co2 = copy.deepcopy(co)
+ assert not co.is_pending()
+ assert not co2.is_pending()
+ raises(error, co.__init__, lst.append)
+ raises(error, co2.__init__, lst.append)
+ raises(error, co.switch)
+ raises(error, co2.switch)
+
+
+class AppTestPickle:
+ version = 0
+
+ def setup_class(cls):
+ cls.space = gettestobjspace(usemodules=('_continuation',),
+ CALL_METHOD=True)
+ cls.space.appexec([], """():
+ global continulet, A, __name__
+
+ import sys
+ __name__ = 'test_pickle_continulet'
+ thismodule = type(sys)(__name__)
+ sys.modules[__name__] = thismodule
+
+ from _continuation import continulet
+ class A(continulet):
+ pass
+
+ thismodule.__dict__.update(globals())
+ """)
+ cls.w_version = cls.space.wrap(cls.version)
+
+ def test_pickle_continulet_empty(self):
+ from _continuation import continulet
+ lst = [4]
+ co = continulet.__new__(continulet)
+ import pickle
+ pckl = pickle.dumps(co, self.version)
+ print repr(pckl)
+ co2 = pickle.loads(pckl)
+ assert co2 is not co
+ assert not co.is_pending()
+ assert not co2.is_pending()
+ # the empty unpickled coroutine can still be used:
+ result = [5]
+ co2.__init__(result.append)
+ res = co2.switch()
+ assert res is None
+ assert result == [5, co2]
+
+ def test_pickle_continulet_empty_subclass(self):
+ from test_pickle_continulet import continulet, A
+ lst = [4]
+ co = continulet.__new__(A)
+ co.foo = 'bar'
+ co.bar = 'baz'
+ import pickle
+ pckl = pickle.dumps(co, self.version)
+ print repr(pckl)
+ co2 = pickle.loads(pckl)
+ assert co2 is not co
+ assert not co.is_pending()
+ assert not co2.is_pending()
+ assert type(co) is type(co2) is A
+ assert co.foo == co2.foo == 'bar'
+ assert co.bar == co2.bar == 'baz'
+ # the empty unpickled coroutine can still be used:
+ result = [5]
+ co2.__init__(result.append)
+ res = co2.switch()
+ assert res is None
+ assert result == [5, co2]
+
+ def test_pickle_continulet_not_started(self):
+ from _continuation import continulet, error
+ import pickle
+ lst = []
+ co = continulet(lst.append)
+ pckl = pickle.dumps((co, lst))
+ print pckl
+ del co, lst
+ for i in range(2):
+ print 'resume...'
+ co2, lst2 = pickle.loads(pckl)
+ assert lst2 == []
+ co2.switch()
+ assert lst2 == [co2]
+
+ def test_pickle_continulet_real(self):
+ import new, sys
+ mod = new.module('test_pickle_continulet_real')
+ sys.modules['test_pickle_continulet_real'] = mod
+ mod.version = self.version
+ exec '''if 1:
+ from _continuation import continulet
+ import pickle
+ def f(co, x):
+ co.switch(x + 1)
+ co.switch(x + 2)
+ return x + 3
+ co = continulet(f, 40)
+ res = co.switch()
+ assert res == 41
+ pckl = pickle.dumps(co, version)
+ print repr(pckl)
+ co2 = pickle.loads(pckl)
+ #
+ res = co2.switch()
+ assert res == 42
+ assert co2.is_pending()
+ res = co2.switch()
+ assert res == 43
+ assert not co2.is_pending()
+ #
+ res = co.switch()
+ assert res == 42
+ assert co.is_pending()
+ res = co.switch()
+ assert res == 43
+ assert not co.is_pending()
+ ''' in mod.__dict__
+
+ def test_pickle_continulet_real_subclass(self):
+ import new, sys
+ mod = new.module('test_pickle_continulet_real_subclass')
+ sys.modules['test_pickle_continulet_real_subclass'] = mod
+ mod.version = self.version
+ exec '''if 1:
+ from _continuation import continulet
+ import pickle
+ class A(continulet):
+ def __init__(self):
+ crash
+ def f(co):
+ co.switch(co.x + 1)
+ co.switch(co.x + 2)
+ return co.x + 3
+ co = A.__new__(A)
+ continulet.__init__(co, f)
+ co.x = 40
+ res = co.switch()
+ assert res == 41
+ pckl = pickle.dumps(co, version)
+ print repr(pckl)
+ co2 = pickle.loads(pckl)
+ #
+ assert type(co2) is A
+ res = co2.switch()
+ assert res == 42
+ assert co2.is_pending()
+ res = co2.switch()
+ assert res == 43
+ assert not co2.is_pending()
+ #
+ res = co.switch()
+ assert res == 42
+ assert co.is_pending()
+ res = co.switch()
+ assert res == 43
+ assert not co.is_pending()
+ ''' in mod.__dict__
+
+
+class AppTestPickle_v1(AppTestPickle):
+ version = 1
+
+class AppTestPickle_v2(AppTestPickle):
+ version = 2
diff --git a/pypy/module/test_lib_pypy/test_greenlet.py b/pypy/module/test_lib_pypy/test_greenlet.py
--- a/pypy/module/test_lib_pypy/test_greenlet.py
+++ b/pypy/module/test_lib_pypy/test_greenlet.py
@@ -258,3 +258,25 @@
assert sys.exc_info() == (None, None, None)
greenlet(f).switch()
+
+ def test_gr_frame(self):
+ from greenlet import greenlet
+ import sys
+ def f2():
+ assert g.gr_frame is None
+ gmain.switch()
+ assert g.gr_frame is None
+ def f1():
+ assert gmain.gr_frame is gmain_frame
+ assert g.gr_frame is None
+ f2()
+ assert g.gr_frame is None
+ gmain = greenlet.getcurrent()
+ assert gmain.gr_frame is None
+ gmain_frame = sys._getframe()
+ g = greenlet(f1)
+ assert g.gr_frame is None
+ g.switch()
+ assert g.gr_frame.f_code.co_name == 'f2'
+ g.switch()
+ assert g.gr_frame is None
diff --git a/pypy/module/test_lib_pypy/test_stackless_pickle.py b/pypy/module/test_lib_pypy/test_stackless_pickle.py
--- a/pypy/module/test_lib_pypy/test_stackless_pickle.py
+++ b/pypy/module/test_lib_pypy/test_stackless_pickle.py
@@ -1,25 +1,27 @@
-import py; py.test.skip("XXX port me")
+import py
+py.test.skip("in-progress, maybe")
from pypy.conftest import gettestobjspace, option
class AppTest_Stackless:
def setup_class(cls):
- import py.test
- py.test.importorskip('greenlet')
- space = gettestobjspace(usemodules=('_stackless', '_socket'))
+ space = gettestobjspace(usemodules=('_continuation', '_socket'))
cls.space = space
- # cannot test the unpickle part on top of py.py
+ if option.runappdirect:
+ cls.w_lev = space.wrap(14)
+ else:
+ cls.w_lev = space.wrap(2)
def test_pickle(self):
import new, sys
mod = new.module('mod')
sys.modules['mod'] = mod
+ mod.lev = self.lev
try:
exec '''
import pickle, sys
import stackless
-lev = 14
ch = stackless.channel()
seen = []
diff --git a/pypy/rlib/rstacklet.py b/pypy/rlib/rstacklet.py
--- a/pypy/rlib/rstacklet.py
+++ b/pypy/rlib/rstacklet.py
@@ -99,12 +99,20 @@
return False
def add(self, h):
if not self.sthread.is_empty_handle(h):
+ if h == self.sthread.get_null_handle():
+ raise StackletDebugError("unexpected null handle")
self.active.append(h)
def remove(self, h):
try:
i = self.active.index(h)
except ValueError:
- raise StackletDebugError
+ if self.sthread.is_empty_handle(h):
+ msg = "empty stacklet handle"
+ elif h == self.sthread.get_null_handle():
+ msg = "unexpected null handle"
+ else:
+ msg = "double usage of handle %r" % (h,)
+ raise StackletDebugError(msg)
del self.active[i]
debug = Debug()
diff --git a/pypy/rlib/test/test_rstacklet.py b/pypy/rlib/test/test_rstacklet.py
--- a/pypy/rlib/test/test_rstacklet.py
+++ b/pypy/rlib/test/test_rstacklet.py
@@ -264,6 +264,10 @@
gcrootfinder = 'shadowstack'
+def test_dont_keep_debug_to_true():
+ assert not rstacklet.DEBUG
+
+
def target(*args):
return entry_point, None
diff --git a/pypy/tool/pytest/appsupport.py b/pypy/tool/pytest/appsupport.py
--- a/pypy/tool/pytest/appsupport.py
+++ b/pypy/tool/pytest/appsupport.py
@@ -72,7 +72,9 @@
space = self.space
retval = []
for arg in self.code.getargs():
- w_val = space.getitem(self.w_locals, space.wrap(arg))
+ w_val = space.finditem(self.w_locals, space.wrap(arg))
+ if w_val is None:
+ w_val = space.wrap('<no value found>')
retval.append((arg, w_val))
return retval
More information about the pypy-commit
mailing list