[pypy-svn] r56085 - in pypy/branch/async-del/pypy: interpreter interpreter/test module/gc

arigo at codespeak.net arigo at codespeak.net
Thu Jun 26 11:26:12 CEST 2008


Author: arigo
Date: Thu Jun 26 11:26:11 2008
New Revision: 56085

Modified:
   pypy/branch/async-del/pypy/interpreter/baseobjspace.py
   pypy/branch/async-del/pypy/interpreter/executioncontext.py
   pypy/branch/async-del/pypy/interpreter/test/test_typedef.py
   pypy/branch/async-del/pypy/interpreter/typedef.py
   pypy/branch/async-del/pypy/module/gc/interp_gc.py
Log:
Attempting to fix the issue by delaying calls to app-level dels.


Modified: pypy/branch/async-del/pypy/interpreter/baseobjspace.py
==============================================================================
--- pypy/branch/async-del/pypy/interpreter/baseobjspace.py	(original)
+++ pypy/branch/async-del/pypy/interpreter/baseobjspace.py	Thu Jun 26 11:26:11 2008
@@ -1,4 +1,4 @@
-from pypy.interpreter.executioncontext import ExecutionContext
+from pypy.interpreter.executioncontext import ExecutionContext, UserDelAction
 from pypy.interpreter.error import OperationError
 from pypy.interpreter.argument import Arguments, ArgumentsFromValuestack
 from pypy.interpreter.pycompiler import CPythonCompiler, PythonAstCompiler
@@ -137,6 +137,9 @@
             self.setweakref(lifeline.space, None)
             lifeline.clear_all_weakrefs()
 
+    def _call_builtin_destructor(self):
+        pass     # method overridden in typedef.py
+
 
 class Wrappable(W_Root):
     """A subclass of Wrappable is an internal, interpreter-level class
@@ -218,7 +221,8 @@
         import pypy.interpreter.nestedscope     # register *_DEREF bytecodes
 
         self.interned_strings = {}
-        self.pending_actions = []
+        self.user_del_action = UserDelAction(self)
+        self.pending_actions = [self.user_del_action]
         self.setoptions(**kw)
 
 #        if self.config.objspace.logbytecodes:

Modified: pypy/branch/async-del/pypy/interpreter/executioncontext.py
==============================================================================
--- pypy/branch/async-del/pypy/interpreter/executioncontext.py	(original)
+++ pypy/branch/async-del/pypy/interpreter/executioncontext.py	Thu Jun 26 11:26:11 2008
@@ -118,9 +118,9 @@
 
     def _do_bytecode_trace(self, frame):
         if self.ticker < 0:
+            self.ticker = self.space.sys.checkinterval
             Action.perform_actions(self.space.pending_actions)
             Action.perform_actions(self.pending_actions)
-            self.ticker = self.space.sys.checkinterval
         if frame.w_f_trace is None or self.is_tracing:
             return
         code = frame.pycode
@@ -278,3 +278,42 @@
         raise Exception("ExecutionContext instances should not be seen during"
                         " translation.  Now is a good time to inspect the"
                         " traceback and see where this one comes from :-)")
+
+
+class UserDelAction(Action):
+    """An action that invokes all pending app-level __del__() method.
+    This is done as an action instead of immediately when the
+    interp-level __del__() is invoked, because the latter can occur more
+    or less anywhere in the middle of code that might not be happy with
+    random app-level code mutating data structures under its feet.
+    """
+    def __init__(self, space):
+        self.space = space
+        self.dying_objects_w = []
+
+    def register_dying_object(self, w_obj):
+        self.dying_objects_w.append(w_obj)
+        # XXX should force the action to occur as soon as possible.
+        # "space.getexectuion().ticker = 0" is both expensive and not
+        # exactly what we need because it's ok if the action occurs
+        # in any thread
+
+    def perform(self):
+        # Each call to perform() first grabs the self.dying_objects_w
+        # and replaces it with an empty list.  We do this to try to
+        # avoid too deep recursions of the kind of __del__ being called
+        # while in the middle of another __del__ call.
+        pending_w = self.dying_objects_w
+        if len(pending_w) == 0:
+            return     # shortcut
+        self.dying_objects_w = []
+        space = self.space
+        for w_obj in pending_w:
+            try:
+                space.userdel(w_obj)
+            except OperationError, e:
+                e.write_unraisable(space, 'method __del__ of ', w_obj)
+                e.clear(space)   # break up reference cycles
+            # finally, this calls the interp-level destructor for the
+            # cases where there is both an app-level and a built-in __del__.
+            w_obj._call_builtin_destructor()

Modified: pypy/branch/async-del/pypy/interpreter/test/test_typedef.py
==============================================================================
--- pypy/branch/async-del/pypy/interpreter/test/test_typedef.py	(original)
+++ pypy/branch/async-del/pypy/interpreter/test/test_typedef.py	Thu Jun 26 11:26:11 2008
@@ -1,4 +1,5 @@
 from pypy.interpreter import typedef
+from pypy.tool.udir import udir
 
 # this test isn't so much to test that the objspace interface *works*
 # -- it's more to test that it's *there*
@@ -130,3 +131,27 @@
         for cls, set in subclasses.items():
             assert len(set) <= 6, "%s has %d subclasses:\n%r" % (
                 cls, len(set), [subcls.__name__ for subcls in set])
+
+
+class AppTestTypeDef:
+
+    def setup_class(cls):
+        path = udir.join('AppTestTypeDef.txt')
+        path.write('hello world\n')
+        cls.w_path = cls.space.wrap(str(path))
+
+    def test_destructor(self):
+        import gc, os
+        seen = []
+        class MyFile(file):
+            def __del__(self):
+                seen.append(10)
+                seen.append(os.lseek(self.fileno(), 2, 0))
+        f = MyFile(self.path, 'r')
+        fd = f.fileno()
+        seen.append(os.lseek(fd, 5, 0))
+        del f
+        gc.collect(); gc.collect(); gc.collect()
+        lst = seen[:]
+        assert lst == [5, 10, 2]
+        raises(OSError, os.lseek, fd, 7, 0)

Modified: pypy/branch/async-del/pypy/interpreter/typedef.py
==============================================================================
--- pypy/branch/async-del/pypy/interpreter/typedef.py	(original)
+++ pypy/branch/async-del/pypy/interpreter/typedef.py	Thu Jun 26 11:26:11 2008
@@ -9,7 +9,7 @@
     DescrMismatch
 from pypy.interpreter.error import OperationError
 from pypy.tool.sourcetools import compile2, func_with_new_name
-from pypy.rlib.objectmodel import instantiate
+from pypy.rlib.objectmodel import instantiate, we_are_translated
 from pypy.rlib.rarithmetic import intmask
 
 class TypeDef:
@@ -247,12 +247,27 @@
         add(Proto)
 
     if "del" in features:
-        parent_destructor = getattr(supercls, '__del__', None)
         class Proto(object):
+            _del_was_called = False
             def __del__(self):
-                call_user_destructor(self.space, self)
-                if parent_destructor is not None:
-                    parent_destructor(self)
+                # the logic below always resurect the objects, so when
+                # running on top of CPython we must manually ensure that
+                # we do it only once
+                if not we_are_translated():
+                    if self._del_was_called:
+                        return
+                    self._del_was_called = True
+                self.clear_all_weakrefs()
+                self.space.user_del_action.register_dying_object(self)
+        # if the base class needs its own interp-level __del__,
+        # we override the _call_builtin_destructor() method to invoke it
+        # after the app-level destructor.
+        parent_destructor = getattr(supercls, '__del__', None)
+        if parent_destructor is not None:
+            def _call_builtin_destructor(self):
+                parent_destructor(self)
+            Proto._call_builtin_destructor = _call_builtin_destructor
+
         add(Proto)
 
     if "slots" in features:
@@ -323,15 +338,6 @@
     return w_dict
 check_new_dictionary._dont_inline_ = True
 
-def call_user_destructor(space, w_self):
-    w_self.clear_all_weakrefs()
-    try:
-        space.userdel(w_self)
-    except OperationError, e:
-        e.write_unraisable(space, 'method __del__ of ', w_self)
-        e.clear(space)   # break up reference cycles
-call_user_destructor._dont_inline_ = True
-
 # ____________________________________________________________
 
 def make_descr_typecheck_wrapper(func, extraargs=(), cls=None):

Modified: pypy/branch/async-del/pypy/module/gc/interp_gc.py
==============================================================================
--- pypy/branch/async-del/pypy/module/gc/interp_gc.py	(original)
+++ pypy/branch/async-del/pypy/module/gc/interp_gc.py	Thu Jun 26 11:26:11 2008
@@ -5,6 +5,7 @@
 def collect(space):
     "Run a full collection."
     rgc.collect()
+    space.user_del_action.perform()
     return space.wrap(0)
     
 collect.unwrap_spec = [ObjSpace]



More information about the Pypy-commit mailing list