[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