[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