[pypy-commit] pypy refactor-wrapped-del: Fix and simplify handling of destructors, both interp-level and

arigo noreply at buildbot.pypy.org
Tue Jul 12 09:40:05 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
Branch: refactor-wrapped-del
Changeset: r45495:3a6efd180057
Date: 2011-07-11 22:55 +0200
http://bitbucket.org/pypy/pypy/changeset/3a6efd180057/

Log:	Fix and simplify handling of destructors, both interp-level and app-
	level. No more magic meta-programming. Instead, we can (but don't
	have to) call self.enqueue_for_destruction(), passing it an explicit
	callback, and we can enqueue several callbacks for the same object.
	Calling the app-level destructor is just one such callback, added by
	typedef.py.

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -149,26 +149,22 @@
             self.delweakref()
             lifeline.clear_all_weakrefs()
 
-    __already_enqueued_for_destruction = False
+    __already_enqueued_for_destruction = ()
 
-    def _enqueue_for_destruction(self, space, call_user_del=True):
+    def enqueue_for_destruction(self, space, callback, descrname):
         """Put the object in the destructor queue of the space.
-        At a later, safe point in time, UserDelAction will use
-        space.userdel() to call the object's app-level __del__ method.
+        At a later, safe point in time, UserDelAction will call
+        callback(self).  If that raises OperationError, prints it
+        to stderr with the descrname string.
         """
         # this function always resurect the object, so when
         # running on top of CPython we must manually ensure that
         # we enqueue it only once
         if not we_are_translated():
-            if self.__already_enqueued_for_destruction:
+            if callback in self.__already_enqueued_for_destruction:
                 return
-            self.__already_enqueued_for_destruction = True
-        self.clear_all_weakrefs()
-        if call_user_del:
-            space.user_del_action.register_dying_object(self)
-
-    def _call_builtin_destructor(self):
-        pass     # method overridden by builtin_destructor() in typedef.py
+            self.__already_enqueued_for_destruction += (callback,)
+        space.user_del_action.register_callback(self, callback, descrname)
 
     # hooks that the mapdict implementations needs:
     def _get_mapdict_map(self):
diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -484,44 +484,31 @@
 
     def __init__(self, space):
         AsyncAction.__init__(self, space)
-        self.dying_objects_w = []
-        self.weakrefs_w = []
+        self.dying_objects = []
         self.finalizers_lock_count = 0
 
-    def register_dying_object(self, w_obj):
-        self.dying_objects_w.append(w_obj)
-        self.fire()
-
-    def register_weakref_callback(self, w_ref):
-        self.weakrefs_w.append(w_ref)
+    def register_callback(self, w_obj, callback, descrname):
+        self.dying_objects.append((w_obj, callback, descrname))
         self.fire()
 
     def perform(self, executioncontext, frame):
         if self.finalizers_lock_count > 0:
             return
-        # Each call to perform() first grabs the self.dying_objects_w
+        # Each call to perform() first grabs the self.dying_objects
         # 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
-        self.dying_objects_w = []
+        pending = self.dying_objects
+        self.dying_objects = []
         space = self.space
-        for i in range(len(pending_w)):
-            w_obj = pending_w[i]
-            pending_w[i] = None
+        for i in range(len(pending)):
+            w_obj, callback, descrname = pending[i]
+            pending[i] = (None, None, None)
             try:
-                space.userdel(w_obj)
+                callback(w_obj)
             except OperationError, e:
-                e.write_unraisable(space, 'method __del__ of ', w_obj)
+                e.write_unraisable(space, descrname, 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()
-        pending_w = self.weakrefs_w
-        self.weakrefs_w = []
-        for i in range(len(pending_w)):
-            w_ref = pending_w[i]
-            w_ref.activate_callback()
 
 class FrameTraceAction(AsyncAction):
     """An action that calls the local trace functions (w_f_trace)."""
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -141,22 +141,16 @@
         code_name = self.pycode.co_name
         return space.wrap(code_name)
 
-    def descr__del__(self):        
-        """
-        applevel __del__, which is called at a safe point after the
-        interp-level __del__ enqueued the object for destruction
-        """
-        self.descr_close()
-
     def __del__(self):
         # Only bother enqueuing self to raise an exception if the frame is
         # still not finished and finally or except blocks are present.
-        must_call_close = False
+        self.clear_all_weakrefs()
         if self.frame is not None:
             block = self.frame.lastblock
             while block is not None:
                 if not isinstance(block, LoopBlock):
-                    must_call_close = True
+                    self.enqueue_for_destruction(self.space,
+                                                 GeneratorIterator.descr_close,
+                                                 "interrupting generator of ")
                     break
                 block = block.previous
-        self._enqueue_for_destruction(self.space, must_call_close)
diff --git a/pypy/interpreter/test/test_typedef.py b/pypy/interpreter/test/test_typedef.py
--- a/pypy/interpreter/test/test_typedef.py
+++ b/pypy/interpreter/test/test_typedef.py
@@ -184,11 +184,12 @@
     def test_destructor(self):
         space = self.space
         class W_Level1(Wrappable):
-            space = self.space
             def __del__(self):
                 space.call_method(w_seen, 'append', space.wrap(1))
-        class W_Level2(W_Level1):
-            typedef.builtin_destructor(locals(), 'destructormeth', W_Level1)
+        class W_Level2(Wrappable):
+            def __del__(self):
+                self.enqueue_for_destruction(space, W_Level2.destructormeth,
+                                             'FOO ')
             def destructormeth(self):
                 space.call_method(w_seen, 'append', space.wrap(2))
         W_Level1.typedef = typedef.TypeDef(
@@ -201,7 +202,7 @@
         w_seen = space.newlist([])
         W_Level1()
         gc.collect(); gc.collect()
-        assert space.str_w(space.repr(w_seen)) == "[1]"
+        assert space.unwrap(w_seen) == [1]
         #
         w_seen = space.newlist([])
         W_Level2()
@@ -209,7 +210,7 @@
         assert space.str_w(space.repr(w_seen)) == "[]"  # not called yet
         ec = space.getexecutioncontext()
         self.space.user_del_action.perform(ec, None)
-        assert space.str_w(space.repr(w_seen)) == "[2, 1]"
+        assert space.unwrap(w_seen) == [2]
         #
         w_seen = space.newlist([])
         self.space.appexec([self.space.gettypeobject(W_Level1.typedef)],
@@ -219,7 +220,7 @@
             A3()
         """)
         gc.collect(); gc.collect()
-        assert space.str_w(space.repr(w_seen)) == "[1]"
+        assert space.unwrap(w_seen) == [1]
         #
         w_seen = space.newlist([])
         self.space.appexec([self.space.gettypeobject(W_Level1.typedef),
@@ -231,7 +232,7 @@
             A4()
         """)
         gc.collect(); gc.collect()
-        assert space.str_w(space.repr(w_seen)) == "[4, 1]"
+        assert space.unwrap(w_seen) == [4, 1]
         #
         w_seen = space.newlist([])
         self.space.appexec([self.space.gettypeobject(W_Level2.typedef)],
@@ -241,7 +242,7 @@
             A5()
         """)
         gc.collect(); gc.collect()
-        assert space.str_w(space.repr(w_seen)) == "[2, 1]"
+        assert space.unwrap(w_seen) == [2]
         #
         w_seen = space.newlist([])
         self.space.appexec([self.space.gettypeobject(W_Level2.typedef),
@@ -253,7 +254,7 @@
             A6()
         """)
         gc.collect(); gc.collect()
-        assert space.str_w(space.repr(w_seen)) == "[6, 2, 1]"
+        assert space.unwrap(w_seen) == [6, 2]
 
 
 class AppTestTypeDef:
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -233,8 +233,15 @@
         add(Proto)
 
     if "del" in features:
+        parent_destructor = getattr(supercls, '__del__', None)
         class Proto(object):
-            builtin_destructor(locals(), None, supercls)
+            def __del__(self):
+                self.clear_all_weakrefs()
+                self.enqueue_for_destruction(self.space, call_applevel_del,
+                                             'method __del__ of ')
+                if parent_destructor is not None:
+                    self.enqueue_for_destruction(self.space, parent_destructor,
+                                                 'internal destructor of ')
         add(Proto)
 
     if "slots" in features:
@@ -286,46 +293,8 @@
     return w_dict
 check_new_dictionary._dont_inline_ = True
 
-
-def builtin_destructor(loc, methodname, baseclass):
-    # Destructor logic:
-    #  * if we have none, then the class has no __del__.
-    #  * if we have a "simple" interp-level one, it is just written as a
-    #    __del__.  ("simple" destructors may not do too much, e.g. they must
-    #    never call further Python code; this is checked at translation-time)
-    #  * if we have a "complex" interp-level destructor, then we define it in
-    #    a method foo(), followed by
-    #        ``builtin_destructor(locals(), "foo", W_Base)''.
-    #  * if we have an app-level destructor, then typedef.py will also
-    #    call builtin_destructor().
-    # In the last two cases, the __del__ just calls _enqueue_for_destruction()
-    # and executioncontext.UserDelAction does the real job.
-
-    assert '_builtin_destructor_already_called_' not in loc
-    assert '__del__' not in loc
-    assert '_call_builtin_destructor' not in loc
-
-    if hasattr(baseclass, '_builtin_destructor_already_called_'):
-        # builtin_destructor() was already called on the parent
-        # class, so we don't need to install the __del__ method nor
-        # call the __del__ method from _call_builtin_destructor()
-        # (because the parent _call_builtin_destructor() will do it)
-        parent_del = None
-    else:
-        def __del__(self):
-            self._enqueue_for_destruction(self.space)
-        loc['__del__'] = __del__
-        loc['_builtin_destructor_already_called_'] = True
-        parent_del = getattr(baseclass, '__del__', None)
-
-    if methodname is not None or parent_del is not None:
-        def _call_builtin_destructor(self):
-            if methodname is not None:
-                getattr(self, methodname)()
-            if parent_del is not None:
-                parent_del(self)
-            baseclass._call_builtin_destructor(self)
-        loc['_call_builtin_destructor'] = _call_builtin_destructor
+def call_applevel_del(self):
+    self.space.userdel(self)
 
 # ____________________________________________________________
 
@@ -894,8 +863,6 @@
                             descrmismatch='close'),
     __iter__   = interp2app(GeneratorIterator.descr__iter__,
                             descrmismatch='__iter__'),
-    __del__    = interp2app(GeneratorIterator.descr__del__,
-                            descrmismatch='__del__'),
     gi_running = interp_attrproperty('running', cls=GeneratorIterator),
     gi_frame   = GetSetProperty(GeneratorIterator.descr_gi_frame),
     gi_code    = GetSetProperty(GeneratorIterator.descr_gi_code),
diff --git a/pypy/module/_weakref/interp__weakref.py b/pypy/module/_weakref/interp__weakref.py
--- a/pypy/module/_weakref/interp__weakref.py
+++ b/pypy/module/_weakref/interp__weakref.py
@@ -23,8 +23,10 @@
         """
         for i in range(len(self.refs_weak) - 1, -1, -1):
             w_ref = self.refs_weak[i]()
-            if w_ref is not None:
-                self.space.user_del_action.register_weakref_callback(w_ref)
+            if w_ref is not None and w_ref.w_callable is not None:
+                w_ref.enqueue_for_destruction(self.space,
+                                              W_WeakrefBase.activate_callback,
+                                              'weakref callback of ')
 
     def clear_all_weakrefs(self):
         """Clear all weakrefs.  This is called when an app-level object has
@@ -118,11 +120,7 @@
         self.w_obj_weak = dead_ref
 
     def activate_callback(w_self):
-        if not w_self.w_callable is None:
-            try:
-                w_self.space.call_function(w_self.w_callable, w_self)
-            except OperationError, e:
-                e.write_unraisable(w_self.space, 'weakref callback ', w_self.w_callable)
+        w_self.space.call_function(w_self.w_callable, w_self)
 
     def descr__repr__(self, space):
         w_obj = self.dereference()


More information about the pypy-commit mailing list