[pypy-commit] pypy continulet-pickle: Yay! The first tests pass.
arigo
noreply at buildbot.pypy.org
Wed Sep 14 21:52:20 CEST 2011
Author: Armin Rigo <arigo at tunes.org>
Branch: continulet-pickle
Changeset: r47275:fae2526c2764
Date: 2011-09-14 20:55 +0200
http://bitbucket.org/pypy/pypy/changeset/fae2526c2764/
Log: Yay! The first tests pass.
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,7 +24,6 @@
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:
@@ -43,12 +43,7 @@
global_state.origin = self
sthread = build_sthread(self.space)
self.sthread = sthread
- try:
- h = sthread.new(new_stacklet_callback)
- except MemoryError:
- global_state.clear()
- raise getmemoryerror(self.space)
- #
+ h = sthread.new(new_stacklet_callback)
post_switch(sthread, h)
def switch(self, w_to):
@@ -75,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:
@@ -85,12 +80,7 @@
# double switch: the final destination is to.h
global_state.destination = to
#
- try:
- h = sthread.switch(global_state.destination.h)
- except MemoryError:
- global_state.clear()
- raise getmemoryerror(self.space)
- #
+ h = sthread.switch(global_state.destination.h)
return post_switch(sthread, h)
def descr_switch(self, w_value=None, w_to=None):
@@ -124,17 +114,36 @@
# __getnewargs__ or __getstate__ defined in the subclass, etc.
# Doing the right thing looks involved, though...
space = self.space
+ if self.sthread is None:
+ raise geterror(space, "cannot pickle (yet) a continulet that is "
+ "not initialized")
+ if self.sthread.is_empty_handle(self.h):
+ raise geterror(space, "cannot pickle (yet) a continulet that is "
+ "already finished")
w_continulet_type = space.type(space.wrap(self))
- if self.sthread is None:
- args = [getunpickle(space),
- space.newtuple([w_continulet_type])]
- w_dict = self.getdict(space)
- if w_dict is not None:
- args = args + [w_dict]
- return space.newtuple(args)
- else:
- raise geterror(space, "cannot pickle yet a continulet that was "
- "initialized but not started")
+ w_frame = space.wrap(self.bottomframe)
+ 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 descr__setstate__(self, w_args):
+ if self.sthread is not None:
+ raise geterror(space, "continulet.__setstate__() on an already-"
+ "initialized continulet")
+ space = self.space
+ w_frame, w_dict = space.fixedview(w_args, expected_length=2)
+ self.bottomframe = space.interp_w(PyFrame, w_frame)
+ if not space.is_w(w_dict, space.w_None):
+ self.setdict(w_dict)
+ #
+ global_state.origin = self
+ sthread = build_sthread(self.space)
+ self.sthread = sthread
+ self.h = sthread.new(resume_trampoline_callback)
+ global_state.origin = None
def W_Continulet___new__(space, w_subtype, __args__):
@@ -158,6 +167,7 @@
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__),
)
@@ -172,7 +182,6 @@
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)
# 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
@@ -197,10 +206,6 @@
cs = space.fromcache(State)
return OperationError(cs.w_error, space.wrap(message))
-def getmemoryerror(space):
- cs = space.fromcache(State)
- return cs.w_memoryerror
-
def get_entrypoint_pycode(space):
cs = space.fromcache(State)
return cs.entrypoint_pycode
@@ -255,6 +260,66 @@
global_state.destination = self
return self.h
+def resume_trampoline_callback(h, arg):
+ from pypy.tool import stdlib_opcode as pythonopcode
+ self = global_state.origin
+ self.h = h
+ space = self.space
+ try:
+ h = self.sthread.switch(self.h)
+ try:
+ w_result = post_switch(self.sthread, h)
+ operr = None
+ except OperationError, operr:
+ pass
+ #
+ while True:
+ ec = self.sthread.ec
+ frame = ec.topframeref()
+ if frame.pycode is get_entrypoint_pycode(space):
+ break
+ #
+ 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 (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
+ #
+ # 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, operr:
+ pass
+ if operr:
+ raise operr
+ except Exception, e:
+ global_state.propagate_exception = e
+ else:
+ global_state.w_value = w_result
+ self.sthread.ec.topframeref = jit.vref_None
+ global_state.origin = self
+ global_state.destination = self
+ return self.h
+
def post_switch(sthread, h):
origin = global_state.origin
diff --git a/pypy/module/_continuation/test/test_zpickle.py b/pypy/module/_continuation/test/test_zpickle.py
--- a/pypy/module/_continuation/test/test_zpickle.py
+++ b/pypy/module/_continuation/test/test_zpickle.py
@@ -1,6 +1,61 @@
from pypy.conftest import gettestobjspace
+class AppTestCopy:
+ def setup_class(cls):
+ cls.space = gettestobjspace(usemodules=('_continuation',),
+ CALL_METHOD=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 lst2 == []
+ xxx
+
+ 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__
+
+
class AppTestPickle:
version = 0
@@ -23,16 +78,8 @@
""")
cls.w_version = cls.space.wrap(cls.version)
- 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_pickle_continulet_empty(self):
+ skip("pickle a not-initialized continulet")
from _continuation import continulet
lst = [4]
co = continulet.__new__(continulet)
@@ -51,6 +98,7 @@
assert result == [5, co2]
def test_pickle_continulet_empty_subclass(self):
+ skip("pickle a not-initialized continulet")
from test_pickle_continulet import continulet, A
lst = [4]
co = continulet.__new__(A)
@@ -73,11 +121,51 @@
assert res is None
assert result == [5, co2]
- def test_pickle_coroutine_not_started(self):
+ def test_pickle_continulet_not_started(self):
from _continuation import continulet, error
import pickle
- co = continulet([].append)
- raises(error, pickle.dumps, co) # xxx for now
+ lst = []
+ co = continulet(lst.append)
+ pckl = pickle.dumps((co, lst))
+ print pckl
+ co2, lst2 = pickle.loads(pckl)
+ assert co is not co2
+ assert lst2 == []
+ xxx
+
+ 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__
class AppTestPickle_v1(AppTestPickle):
More information about the pypy-commit
mailing list