Author: Armin Rigo <[email protected]>
Branch: use-gc-del-3
Changeset: r84259:88006cd809de
Date: 2016-05-06 12:10 +0200
http://bitbucket.org/pypy/pypy/changeset/88006cd809de/

Log:    Restore semblance of sanity to gc.disable()/gc.enable(): now they
        don't usually prevent running finalizers, but they will prevent some
        explicitly-defined ones to run:

        * all user __del__
        * weakref callbacks

        They don't prevent RPython-level things like closing files, and
        also, by default, they don't prevent calls in other situations, like
        cffi's 'ffi.gc()'.

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -1856,7 +1856,6 @@
     ('get',             'get',       3, ['__get__']),
     ('set',             'set',       3, ['__set__']),
     ('delete',          'delete',    2, ['__delete__']),
-    ('userdel',         'del',       1, ['__del__']),
 ]
 
 ObjSpace.BuiltinModuleTable = [
diff --git a/pypy/interpreter/executioncontext.py 
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -533,32 +533,56 @@
         AsyncAction.__init__(self, space)
         self.finalizers_lock_count = 0        # see pypy/module/gc
         self.enabled_at_app_level = True      # see pypy/module/gc
+        self.pending_with_disabled_del = None
 
     def perform(self, executioncontext, frame):
-        if self.finalizers_lock_count > 0:
-            return
         self._run_finalizers()
 
+    @jit.dont_look_inside
     def _run_finalizers(self):
         while True:
             w_obj = self.space.finalizer_queue.next_dead()
             if w_obj is None:
                 break
+            self._call_finalizer(w_obj)
 
-            # Before calling the finalizers, clear the weakrefs, if any.
-            w_obj.clear_all_weakrefs()
+    def gc_disabled(self, w_obj):
+        # If we're running in 'gc.disable()' mode, record w_obj in the
+        # "call me later" list and return True.  Use this function
+        # from _finalize_() methods that would call app-level some
+        # things that we consider shouldn't be called in gc.disable().
+        # (The exact definition is of course a bit vague, but most
+        # importantly this includes all user-level __del__().)
+        pdd = self.pending_with_disabled_del
+        if pdd is None:
+            return False
+        else:
+            pdd.append(w_obj)
+            return True
 
-            # Look up and call the app-level __del__, if any.
+    def _call_finalizer(self, w_obj):
+        # Before calling the finalizers, clear the weakrefs, if any.
+        w_obj.clear_all_weakrefs()
+
+        # Look up and call the app-level __del__, if any.
+        space = self.space
+        if w_obj.typedef is None:
+            w_del = None       # obscure case: for WeakrefLifeline
+        else:
+            w_del = space.lookup(w_obj, '__del__')
+        if w_del is not None:
+            if self.gc_disabled(w_obj):
+                return
             try:
-                self.space.userdel(w_obj)
+                space.get_and_call_function(w_del, w_obj)
             except Exception as e:
-                report_error(self.space, e, "method __del__ of ", w_obj)
+                report_error(space, e, "method __del__ of ", w_obj)
 
-            # Call the RPython-level _finalize_() method.
-            try:
-                w_obj._finalize_()
-            except Exception as e:
-                report_error(self.space, e, "finalizer of ", w_obj)
+        # Call the RPython-level _finalize_() method.
+        try:
+            w_obj._finalize_()
+        except Exception as e:
+            report_error(space, e, "finalizer of ", w_obj)
 
 
 def report_error(space, e, where, w_obj):
diff --git a/pypy/module/__builtin__/interp_classobj.py 
b/pypy/module/__builtin__/interp_classobj.py
--- a/pypy/module/__builtin__/interp_classobj.py
+++ b/pypy/module/__builtin__/interp_classobj.py
@@ -650,6 +650,8 @@
         if w_func is None:
             w_func = self.getattr_from_class(space, '__del__')
         if w_func is not None:
+            if self.space.user_del_action.gc_disabled(self):
+                return
             space.call_function(w_func)
 
     def descr_exit(self, space, w_type, w_value, w_tb):
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
@@ -17,6 +17,8 @@
 
 
 class WeakrefLifeline(W_Root):
+    typedef = None
+
     cached_weakref  = None
     cached_proxy    = None
     other_refs_weak = None
@@ -103,8 +105,7 @@
 
     def enable_callbacks(self):
         if not self.has_callbacks:
-            fq = self.space.fromcache(Cache).fq
-            fq.register_finalizer(self)
+            self.register_finalizer(self.space)
             self.has_callbacks = True
 
     @jit.dont_look_inside
@@ -127,39 +128,28 @@
         self.enable_callbacks()
         return w_proxy
 
-
-class WeakrefCallbackAction(AsyncAction):
-    """An action that runs when a W_Root object goes away, and allows
-    its lifeline to go away.  It activates all the callbacks of all
-    the dying lifelines.
-    """
-
-    def perform(self, executioncontext, frame):
-        fq = self.space.fromcache(Cache).fq
-        while True:
-            lifeline = fq.next_dead()
-            if lifeline is None:
-                break
-            if lifeline.other_refs_weak is None:
-                continue  # should never be the case, but better safe than 
sorry
-            items = lifeline.other_refs_weak.items()
-            for i in range(len(items)-1, -1, -1):
-                w_ref = items[i]()
-                if w_ref is not None and w_ref.w_callable is not None:
-                    try:
-                        w_ref.activate_callback()
-                    except Exception as e:
-                        report_error(self.space, e,
-                                     "weakref callback ", w_ref.w_callable)
-
-class Cache:
-    def __init__(self, space):
-        class WeakrefFinalizerQueue(rgc.FinalizerQueue):
-            Class = WeakrefLifeline
-            def finalizer_trigger(self):
-                space.weakref_callback_action.fire()
-        space.weakref_callback_action = WeakrefCallbackAction(space)
-        self.fq = WeakrefFinalizerQueue()
+    def _finalize_(self):
+        """This is called at the end, if enable_callbacks() was invoked.
+        It activates the callbacks.
+        """
+        if self.other_refs_weak is None:
+            return
+        #
+        # If this is set, then we're in the 'gc.disable()' mode.  In that
+        # case, don't invoke the callbacks now.
+        if self.space.user_del_action.gc_disabled(self):
+            return
+        #
+        items = self.other_refs_weak.items()
+        self.other_refs_weak = None
+        for i in range(len(items)-1, -1, -1):
+            w_ref = items[i]()
+            if w_ref is not None and w_ref.w_callable is not None:
+                try:
+                    w_ref.activate_callback()
+                except Exception as e:
+                    report_error(self.space, e,
+                                 "weakref callback ", w_ref.w_callable)
 
 
 # ____________________________________________________________
@@ -339,7 +329,7 @@
 
 proxy_typedef_dict = {}
 callable_proxy_typedef_dict = {}
-special_ops = {'repr': True, 'userdel': True, 'hash': True}
+special_ops = {'repr': True, 'hash': True}
 
 for opname, _, arity, special_methods in ObjSpace.MethodTable:
     if opname in special_ops or not special_methods:
diff --git a/pypy/module/gc/interp_gc.py b/pypy/module/gc/interp_gc.py
--- a/pypy/module/gc/interp_gc.py
+++ b/pypy/module/gc/interp_gc.py
@@ -38,13 +38,23 @@
     return space.newbool(space.user_del_action.enabled_at_app_level)
 
 def enable_finalizers(space):
-    if space.user_del_action.finalizers_lock_count == 0:
+    uda = space.user_del_action
+    if uda.finalizers_lock_count == 0:
         raise oefmt(space.w_ValueError, "finalizers are already enabled")
-    space.user_del_action.finalizers_lock_count -= 1
-    space.user_del_action.fire()
+    uda.finalizers_lock_count -= 1
+    if uda.finalizers_lock_count == 0:
+        pending = uda.pending_with_disabled_del
+        uda.pending_with_disabled_del = None
+        if pending is not None:
+            for i in range(len(pending)):
+                uda._call_finalizer(pending[i])
+                pending[i] = None   # clear the list as we progress
 
 def disable_finalizers(space):
-    space.user_del_action.finalizers_lock_count += 1
+    uda = space.user_del_action
+    uda.finalizers_lock_count += 1
+    if uda.pending_with_disabled_del is None:
+        uda.pending_with_disabled_del = []
 
 # ____________________________________________________________
 
diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py
--- a/pypy/objspace/descroperation.py
+++ b/pypy/objspace/descroperation.py
@@ -440,11 +440,6 @@
             raise oefmt(space.w_TypeError,
                         "__hash__() should return an int or long")
 
-    def userdel(space, w_obj):
-        w_del = space.lookup(w_obj, '__del__')
-        if w_del is not None:
-            space.get_and_call_function(w_del, w_obj)
-
     def cmp(space, w_v, w_w):
 
         if space.is_w(w_v, w_w):
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to