[pypy-commit] pypy continulet-pickle: Tests pass.

arigo noreply at buildbot.pypy.org
Wed Sep 14 21:52:24 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: continulet-pickle
Changeset: r47278:1f82c034af3e
Date: 2011-09-14 21:32 +0200
http://bitbucket.org/pypy/pypy/changeset/1f82c034af3e/

Log:	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
@@ -109,41 +109,12 @@
         return self.space.newbool(valid)
 
     def descr__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:
-            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))
-        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)
+        from pypy.module._continuation import interp_pickle
+        return interp_pickle.reduce(self)
 
     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
+        from pypy.module._continuation import interp_pickle
+        interp_pickle.setstate(self, w_args)
 
 
 def W_Continulet___new__(space, w_subtype, __args__):
@@ -170,7 +141,6 @@
     __setstate__= interp2app(W_Continulet.descr__setstate__),
     )
 
-
 # ____________________________________________________________
 
 # Continulet objects maintain a dummy frame object in order to ensure
@@ -214,10 +184,6 @@
     cs = space.fromcache(State)
     return cs.w_module_dict
 
-def getunpickle(space):
-    cs = space.fromcache(State)
-    return cs.w_unpickle
-
 # ____________________________________________________________
 
 
@@ -227,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
 
@@ -260,68 +229,6 @@
     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()
-            last_level = frame.pycode is get_entrypoint_pycode(space)
-            #
-            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 last_level:
-                break
-        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
     self = global_state.destination
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,118 @@
+from pypy.tool import stdlib_opcode as pythonopcode
+from pypy.rlib import jit
+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
+
+
+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:
+        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))
+    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 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)
+    sthread.frame2continulet.set(self.bottomframe, self)
+    self.sthread = sthread
+    self.h = sthread.new(resume_trampoline_callback)
+    global_state.origin = None
+
+# ____________________________________________________________
+
+def resume_trampoline_callback(h, arg):
+    self = global_state.origin
+    self.h = h
+    space = self.space
+    try:
+        sthread = self.sthread
+        h = sthread.switch(self.h)
+        try:
+            w_result = post_switch(sthread, h)
+            operr = None
+        except OperationError, operr:
+            pass
+        #
+        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, operr:
+                pass
+            if exit_continulet is not None:
+                self = exit_continulet
+                break
+        if operr:
+            raise operr
+    except Exception, e:
+        global_state.propagate_exception = e
+    else:
+        global_state.w_value = w_result
+    sthread.ec.topframeref = jit.vref_None
+    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
@@ -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
--- a/pypy/module/_continuation/test/test_zpickle.py
+++ b/pypy/module/_continuation/test/test_zpickle.py
@@ -30,6 +30,31 @@
         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')
@@ -134,10 +159,13 @@
         co = continulet(lst.append)
         pckl = pickle.dumps((co, lst))
         print pckl
-        co2, lst2 = pickle.loads(pckl)
-        assert co is not co2
-        assert lst2 == []
-        xxx
+        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


More information about the pypy-commit mailing list