[pypy-commit] pypy py3.5: frame.clear()

arigo pypy.commits at gmail.com
Thu Aug 18 10:30:14 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: py3.5
Changeset: r86274:79296a54da93
Date: 2016-08-18 16:29 +0200
http://bitbucket.org/pypy/pypy/changeset/79296a54da93/

Log:	frame.clear()

diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -6,7 +6,14 @@
 from rpython.rlib import jit
 
 
-class GeneratorIterator(W_Root):
+class GeneratorOrCoroutine(W_Root):
+    """XXX: move the common functionality here!"""
+
+    def descr_close(self):
+        raise NotImplementedError
+
+
+class GeneratorIterator(GeneratorOrCoroutine):
     "An iterator created by a generator."
     _immutable_fields_ = ['pycode']
 
@@ -333,7 +340,7 @@
                                       get_printable_location = get_printable_coroutine_location_genentry,
                                       name='coroutineentry')
 
-class Coroutine(W_Root):
+class Coroutine(GeneratorOrCoroutine):
     "A coroutine object."
     _immutable_fields_ = ['pycode']
 
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -62,6 +62,7 @@
     __metaclass__ = extendabletype
 
     frame_finished_execution = False
+    frame_generator          = None    # for generators/coroutines
     last_instr               = -1
     last_exception           = None
     f_backref                = jit.vref_None
@@ -240,12 +241,16 @@
 
     def run(self):
         """Start this frame's execution."""
-        if self.getcode().co_flags & pycode.CO_COROUTINE:
-            from pypy.interpreter.generator import Coroutine
-            return self.space.wrap(Coroutine(self))
-        elif self.getcode().co_flags & pycode.CO_GENERATOR:
-            from pypy.interpreter.generator import GeneratorIterator
-            return self.space.wrap(GeneratorIterator(self))
+        if self.getcode().co_flags & (pycode.CO_COROUTINE |
+                                      pycode.CO_GENERATOR):
+            if self.getcode().co_flags & pycode.CO_COROUTINE:
+                from pypy.interpreter.generator import Coroutine
+                gen = Coroutine(self)
+            else:
+                from pypy.interpreter.generator import GeneratorIterator
+                gen = GeneratorIterator(self)
+            self.frame_generator = gen
+            return self.space.wrap(gen)
         else:
             return self.execute_frame()
 
@@ -886,6 +891,29 @@
             frame = frame.f_backref()
         return None
 
+    def descr_clear(self, space):
+        # Clears a random subset of the attributes (e.g. some the fast
+        # locals, but not f_locals).  Also clears last_exception, which
+        # is not quite like CPython when it clears f_exc_* (however
+        # there might not be an observable difference).
+        if not self.frame_finished_execution:
+            if self.frame_generator is None or self.frame_generator.running:
+                raise oefmt(space.w_RuntimeError,
+                            "cannot clear an executing frame")
+            # xxx CPython raises the RuntimeWarning "coroutine was never
+            # awaited" in this case too.  Does it make any sense?
+            self.frame_generator.descr_close()
+
+        self.last_exception = None
+        debug = self.getdebug()
+        if debug is not None:
+            debug.w_f_trace = None
+
+        # clear the locals, including the cell/free vars, and the stack
+        for i in range(len(self.locals_cells_stack_w)):
+            self.locals_cells_stack_w[i] = None
+        self.valuestackdepth = 0
+
 # ____________________________________________________________
 
 def get_block_class(opname):
diff --git a/pypy/interpreter/test/test_pyframe.py b/pypy/interpreter/test/test_pyframe.py
--- a/pypy/interpreter/test/test_pyframe.py
+++ b/pypy/interpreter/test/test_pyframe.py
@@ -545,3 +545,41 @@
         it = yield_raise()
         assert next(it) is KeyError
         assert next(it) is KeyError
+
+    def test_frame_clear(self):
+        import sys, gc, weakref
+        #
+        raises(RuntimeError, sys._getframe().clear)
+        def g():
+            yield 5
+            raises(RuntimeError, sys._getframe().clear)
+            yield 6
+        assert list(g()) == [5, 6]
+        #
+        class A:
+            pass
+        a1 = A(); a1ref = weakref.ref(a1)
+        a2 = A(); a2ref = weakref.ref(a2)
+        seen = []
+        def f():
+            local_a1 = a1
+            for loc in [5, 6, a2]:
+                try:
+                    yield sys._getframe()
+                finally:
+                    seen.append(42)
+                seen.append(43)
+        gen = f()
+        frame = next(gen)
+        a1 = a2 = None
+        gc.collect(); gc.collect()
+        assert a1ref() is not None
+        assert a2ref() is not None
+        assert seen == []
+        frame.clear()
+        assert seen == [42]
+        gc.collect(); gc.collect()
+        assert a1ref() is None, "locals not cleared"
+        assert a2ref() is None, "stack not cleared"
+        #
+        raises(StopIteration, next, gen)
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -605,6 +605,7 @@
 PyFrame.typedef = TypeDef('frame',
     __reduce__ = interp2app(PyFrame.descr__reduce__),
     __setstate__ = interp2app(PyFrame.descr__setstate__),
+    clear = interp2app(PyFrame.descr_clear),
     f_builtins = GetSetProperty(PyFrame.fget_f_builtins),
     f_lineno = GetSetProperty(PyFrame.fget_f_lineno, PyFrame.fset_f_lineno),
     f_back = GetSetProperty(PyFrame.fget_f_back),


More information about the pypy-commit mailing list