[pypy-commit] pypy new-jit-log: merged default
plan_rich
pypy.commits at gmail.com
Tue May 10 05:53:21 EDT 2016
Author: Richard Plangger <planrichi at gmail.com>
Branch: new-jit-log
Changeset: r84348:cfecd970a924
Date: 2016-05-09 13:29 +0200
http://bitbucket.org/pypy/pypy/changeset/cfecd970a924/
Log: merged default
diff too long, truncating to 2000 out of 2152 lines
diff --git a/pypy/doc/discussion/finalizer-order.rst b/pypy/doc/discussion/finalizer-order.rst
--- a/pypy/doc/discussion/finalizer-order.rst
+++ b/pypy/doc/discussion/finalizer-order.rst
@@ -33,26 +33,25 @@
it from a finalizer. A finalizer runs earlier, and in topological
order; care must be taken that the object might still be reachable at
this point if we're clever enough. A destructor on the other hand runs
-last; nothing can be done with the object any more.
+last; nothing can be done with the object any more, and the GC frees it
+immediately.
Destructors
-----------
A destructor is an RPython ``__del__()`` method that is called directly
-by the GC when there is no more reference to an object. Intended for
-objects that just need to free a block of raw memory or close a file.
+by the GC when it is about to free the memory. Intended for objects
+that just need to free an extra block of raw memory.
There are restrictions on the kind of code you can put in ``__del__()``,
including all other functions called by it. These restrictions are
-checked. In particular you cannot access fields containing GC objects;
-and if you call an external C function, it must be a "safe" function
-(e.g. not releasing the GIL; use ``releasegil=False`` in
-``rffi.llexternal()``).
+checked. In particular you cannot access fields containing GC objects.
+Right now you can't call any external C function either.
-If there are several objects with destructors that die during the same
-GC cycle, they are called in a completely random order --- but that
-should not matter because destructors cannot do much anyway.
+Destructors are called precisely when the GC frees the memory of the
+object. As long as the object exists (even in some finalizer queue or
+anywhere), its destructor is not called.
Register_finalizer
@@ -95,10 +94,15 @@
To find the queued items, call ``fin.next_dead()`` repeatedly. It
returns the next queued item, or ``None`` when the queue is empty.
-It is allowed in theory to cumulate several different
+In theory, it would kind of work if you cumulate several different
``FinalizerQueue`` instances for objects of the same class, and
(always in theory) the same ``obj`` could be registered several times
in the same queue, or in several queues. This is not tested though.
+For now the untranslated emulation does not support registering the
+same object several times.
+
+Note that the Boehm garbage collector, used in ``rpython -O0``,
+completely ignores ``register_finalizer()``.
Ordering of finalizers
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -84,3 +84,8 @@
.. branch: cpyext-more-slots
+.. branch: use-gc-del-3
+
+Use the new rgc.FinalizerQueue mechanism to clean up the handling of
+``__del__`` methods. Fixes notably issue #2287. (All RPython
+subclasses of W_Root need to use FinalizerQueue now.)
diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -11,7 +11,7 @@
INT_MIN, INT_MAX, UINT_MAX, USHRT_MAX
from pypy.interpreter.executioncontext import (ExecutionContext, ActionFlag,
- UserDelAction)
+ make_finalizer_queue)
from pypy.interpreter.error import OperationError, new_exception_class, oefmt
from pypy.interpreter.argument import Arguments
from pypy.interpreter.miscutils import ThreadLocals, make_weak_value_dictionary
@@ -28,6 +28,7 @@
"""This is the abstract root class of all wrapped objects that live
in a 'normal' object space like StdObjSpace."""
__slots__ = ('__weakref__',)
+ _must_be_light_finalizer_ = True
user_overridden_class = False
def getdict(self, space):
@@ -136,9 +137,8 @@
pass
def clear_all_weakrefs(self):
- """Call this at the beginning of interp-level __del__() methods
- in subclasses. It ensures that weakrefs (if any) are cleared
- before the object is further destroyed.
+ """Ensures that weakrefs (if any) are cleared now. This is
+ called by UserDelAction before the object is finalized further.
"""
lifeline = self.getweakref()
if lifeline is not None:
@@ -151,25 +151,37 @@
self.delweakref()
lifeline.clear_all_weakrefs()
- __already_enqueued_for_destruction = ()
+ def _finalize_(self):
+ """The RPython-level finalizer.
- 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 call
- callback(self). If that raises OperationError, prints it
- to stderr with the descrname string.
+ By default, it is *not called*. See self.register_finalizer().
+ Be ready to handle the case where the object is only half
+ initialized. Also, in some cases the object might still be
+ visible to app-level after _finalize_() is called (e.g. if
+ there is a __del__ that resurrects).
+ """
- Note that 'callback' will usually need to start with:
- assert isinstance(self, W_SpecificClass)
+ def register_finalizer(self, space):
+ """Register a finalizer for this object, so that
+ self._finalize_() will be called. You must call this method at
+ most once. Be ready to handle in _finalize_() the case where
+ the object is half-initialized, even if you only call
+ self.register_finalizer() at the end of the initialization.
+ This is because there are cases where the finalizer is already
+ registered before: if the user makes an app-level subclass with
+ a __del__. (In that case only, self.register_finalizer() does
+ nothing, because the finalizer is already registered in
+ allocate_instance().)
"""
- # 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 callback in self.__already_enqueued_for_destruction:
- return
- self.__already_enqueued_for_destruction += (callback,)
- space.user_del_action.register_callback(self, callback, descrname)
+ if self.user_overridden_class and self.getclass(space).hasuserdel:
+ # already registered by space.allocate_instance()
+ if not we_are_translated():
+ assert space.finalizer_queue._already_registered(self)
+ else:
+ if not we_are_translated():
+ # does not make sense if _finalize_ is not overridden
+ assert self._finalize_.im_func is not W_Root._finalize_.im_func
+ space.finalizer_queue.register_finalizer(self)
# hooks that the mapdict implementations needs:
def _get_mapdict_map(self):
@@ -389,9 +401,9 @@
self.interned_strings = make_weak_value_dictionary(self, str, W_Root)
self.actionflag = ActionFlag() # changed by the signal module
self.check_signal_action = None # changed by the signal module
- self.user_del_action = UserDelAction(self)
+ make_finalizer_queue(W_Root, self)
self._code_of_sys_exc_info = None
-
+
# can be overridden to a subclass
self.initialize()
@@ -1844,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
@@ -2,7 +2,7 @@
from pypy.interpreter.error import OperationError, get_cleared_operation_error
from rpython.rlib.unroll import unrolling_iterable
from rpython.rlib.objectmodel import specialize
-from rpython.rlib import jit
+from rpython.rlib import jit, rgc
TICK_COUNTER_STEP = 100
@@ -141,6 +141,12 @@
actionflag.action_dispatcher(self, frame) # slow path
bytecode_trace._always_inline_ = True
+ def _run_finalizers_now(self):
+ # Tests only: run the actions now, to ensure that the
+ # finalizable objects are really finalized. Used notably by
+ # pypy.tool.pytest.apptest.
+ self.space.actionflag.action_dispatcher(self, None)
+
def bytecode_only_trace(self, frame):
"""
Like bytecode_trace() but doesn't invoke any other events besides the
@@ -515,75 +521,98 @@
"""
-class UserDelCallback(object):
- def __init__(self, w_obj, callback, descrname):
- self.w_obj = w_obj
- self.callback = callback
- self.descrname = descrname
- self.next = None
-
class UserDelAction(AsyncAction):
"""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
+ WRootFinalizerQueue is triggered, 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):
AsyncAction.__init__(self, space)
- self.dying_objects = None
- self.dying_objects_last = None
- self.finalizers_lock_count = 0
- self.enabled_at_app_level = True
-
- def register_callback(self, w_obj, callback, descrname):
- cb = UserDelCallback(w_obj, callback, descrname)
- if self.dying_objects_last is None:
- self.dying_objects = cb
- else:
- self.dying_objects_last.next = cb
- self.dying_objects_last = cb
- self.fire()
+ 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):
- # 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 = self.dying_objects
- self.dying_objects = None
- self.dying_objects_last = None
+ while True:
+ w_obj = self.space.finalizer_queue.next_dead()
+ if w_obj is None:
+ break
+ self._call_finalizer(w_obj)
+
+ 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. In normal mode, return
+ # False. Use this function from some _finalize_() methods:
+ # if a _finalize_() method would call some user-defined
+ # app-level function, like a weakref callback, then first do
+ # 'if gc.disabled(self): return'. Another attempt at
+ # calling _finalize_() will be made after 'gc.enable()'.
+ # (The exact rule for when to use gc_disabled() or not is 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
+
+ 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
- while pending is not None:
+ 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:
- pending.callback(pending.w_obj)
- except OperationError as e:
- e.write_unraisable(space, pending.descrname, pending.w_obj)
- e.clear(space) # break up reference cycles
- pending = pending.next
- #
- # Note: 'dying_objects' used to be just a regular list instead
- # of a chained list. This was the cause of "leaks" if we have a
- # program that constantly creates new objects with finalizers.
- # Here is why: say 'dying_objects' is a long list, and there
- # are n instances in it. Then we spend some time in this
- # function, possibly triggering more GCs, but keeping the list
- # of length n alive. Then the list is suddenly freed at the
- # end, and we return to the user program. At this point the
- # GC limit is still very high, because just before, there was
- # a list of length n alive. Assume that the program continues
- # to allocate a lot of instances with finalizers. The high GC
- # limit means that it could allocate a lot of instances before
- # reaching it --- possibly more than n. So the whole procedure
- # repeats with higher and higher values of n.
- #
- # This does not occur in the current implementation because
- # there is no list of length n: if n is large, then the GC
- # will run several times while walking the list, but it will
- # see lower and lower memory usage, with no lower bound of n.
+ space.get_and_call_function(w_del, w_obj)
+ except Exception as e:
+ 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(space, e, "finalizer of ", w_obj)
+
+
+def report_error(space, e, where, w_obj):
+ if isinstance(e, OperationError):
+ e.write_unraisable(space, where, w_obj)
+ e.clear(space) # break up reference cycles
+ else:
+ addrstring = w_obj.getaddrstring(space)
+ msg = ("RPython exception %s in %s<%s at 0x%s> ignored\n" % (
+ str(e), where, space.type(w_obj).name, addrstring))
+ space.call_method(space.sys.get('stderr'), 'write',
+ space.wrap(msg))
+
+
+def make_finalizer_queue(W_Root, space):
+ """Make a FinalizerQueue subclass which responds to GC finalizer
+ events by 'firing' the UserDelAction class above. It does not
+ directly fetches the objects to finalize at all; they stay in the
+ GC-managed queue, and will only be fetched by UserDelAction
+ (between bytecodes)."""
+
+ class WRootFinalizerQueue(rgc.FinalizerQueue):
+ Class = W_Root
+
+ def finalizer_trigger(self):
+ space.user_del_action.fire()
+
+ space.user_del_action = UserDelAction(space)
+ space.finalizer_queue = WRootFinalizerQueue()
diff --git a/pypy/interpreter/generator.py b/pypy/interpreter/generator.py
--- a/pypy/interpreter/generator.py
+++ b/pypy/interpreter/generator.py
@@ -1,6 +1,7 @@
from pypy.interpreter.baseobjspace import W_Root
from pypy.interpreter.error import OperationError, oefmt
from pypy.interpreter.pyopcode import LoopBlock
+from pypy.interpreter.pycode import CO_YIELD_INSIDE_TRY
from rpython.rlib import jit
@@ -13,6 +14,8 @@
self.frame = frame # turned into None when frame_finished_execution
self.pycode = frame.pycode
self.running = False
+ if self.pycode.co_flags & CO_YIELD_INSIDE_TRY:
+ self.register_finalizer(self.space)
def descr__repr__(self, space):
if self.pycode is None:
@@ -139,7 +142,6 @@
def descr_close(self):
"""x.close(arg) -> raise GeneratorExit inside generator."""
- assert isinstance(self, GeneratorIterator)
space = self.space
try:
w_retval = self.throw(space.w_GeneratorExit, space.w_None,
@@ -212,25 +214,21 @@
unpack_into = _create_unpack_into()
unpack_into_w = _create_unpack_into()
-
-class GeneratorIteratorWithDel(GeneratorIterator):
-
- 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.
- self.clear_all_weakrefs()
+ def _finalize_(self):
+ # This is only called if the CO_YIELD_INSIDE_TRY flag is set
+ # on the code object. If the frame is still not finished and
+ # finally or except blocks are present at the current
+ # position, then raise a GeneratorExit. Otherwise, there is
+ # no point.
if self.frame is not None:
block = self.frame.lastblock
while block is not None:
if not isinstance(block, LoopBlock):
- self.enqueue_for_destruction(self.space,
- GeneratorIterator.descr_close,
- "interrupting generator of ")
+ self.descr_close()
break
block = block.previous
-
def get_printable_location_genentry(bytecode):
return '%s <generator>' % (bytecode.get_repr(),)
generatorentry_driver = jit.JitDriver(greens=['pycode'],
diff --git a/pypy/interpreter/pyframe.py b/pypy/interpreter/pyframe.py
--- a/pypy/interpreter/pyframe.py
+++ b/pypy/interpreter/pyframe.py
@@ -241,12 +241,8 @@
def run(self):
"""Start this frame's execution."""
if self.getcode().co_flags & pycode.CO_GENERATOR:
- if self.getcode().co_flags & pycode.CO_YIELD_INSIDE_TRY:
- from pypy.interpreter.generator import GeneratorIteratorWithDel
- return self.space.wrap(GeneratorIteratorWithDel(self))
- else:
- from pypy.interpreter.generator import GeneratorIterator
- return self.space.wrap(GeneratorIterator(self))
+ from pypy.interpreter.generator import GeneratorIterator
+ return self.space.wrap(GeneratorIterator(self))
else:
return self.execute_frame()
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
@@ -127,10 +127,7 @@
""" % (slots, methodname, checks[0], checks[1],
checks[2], checks[3]))
subclasses = {}
- for key, subcls in typedef._subclass_cache.items():
- if key[0] is not space.config:
- continue
- cls = key[1]
+ for cls, subcls in typedef._unique_subclass_cache.items():
subclasses.setdefault(cls, {})
prevsubcls = subclasses[cls].setdefault(subcls.__name__, subcls)
assert subcls is prevsubcls
@@ -186,35 +183,20 @@
class W_Level1(W_Root):
def __init__(self, space1):
assert space1 is space
- def __del__(self):
+ self.register_finalizer(space)
+ def _finalize_(self):
space.call_method(w_seen, 'append', space.wrap(1))
- class W_Level2(W_Root):
- def __init__(self, space1):
- assert space1 is space
- 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(
'level1',
__new__ = typedef.generic_new_descr(W_Level1))
- W_Level2.typedef = typedef.TypeDef(
- 'level2',
- __new__ = typedef.generic_new_descr(W_Level2))
#
w_seen = space.newlist([])
W_Level1(space)
gc.collect(); gc.collect()
- assert space.unwrap(w_seen) == [1]
- #
- w_seen = space.newlist([])
- W_Level2(space)
- gc.collect(); gc.collect()
assert space.str_w(space.repr(w_seen)) == "[]" # not called yet
ec = space.getexecutioncontext()
self.space.user_del_action.perform(ec, None)
- assert space.unwrap(w_seen) == [2]
+ assert space.unwrap(w_seen) == [1] # called by user_del_action
#
w_seen = space.newlist([])
self.space.appexec([self.space.gettypeobject(W_Level1.typedef)],
@@ -236,29 +218,17 @@
A4()
""")
gc.collect(); gc.collect()
- assert space.unwrap(w_seen) == [4, 1]
+ assert space.unwrap(w_seen) == [4, 1] # user __del__, and _finalize_
#
w_seen = space.newlist([])
- self.space.appexec([self.space.gettypeobject(W_Level2.typedef)],
+ self.space.appexec([self.space.gettypeobject(W_Level1.typedef)],
"""(level2):
class A5(level2):
pass
A5()
""")
gc.collect(); gc.collect()
- assert space.unwrap(w_seen) == [2]
- #
- w_seen = space.newlist([])
- self.space.appexec([self.space.gettypeobject(W_Level2.typedef),
- w_seen],
- """(level2, seen):
- class A6(level2):
- def __del__(self):
- seen.append(6)
- A6()
- """)
- gc.collect(); gc.collect()
- assert space.unwrap(w_seen) == [6, 2]
+ assert space.unwrap(w_seen) == [1] # _finalize_ only
def test_multiple_inheritance(self):
class W_A(W_Root):
diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py
--- a/pypy/interpreter/typedef.py
+++ b/pypy/interpreter/typedef.py
@@ -24,6 +24,8 @@
self.bases = bases
self.heaptype = False
self.hasdict = '__dict__' in rawdict
+ # no __del__: use an RPython _finalize_() method and register_finalizer
+ assert '__del__' not in rawdict
self.weakrefable = '__weakref__' in rawdict
self.doc = rawdict.pop('__doc__', None)
for base in bases:
@@ -103,26 +105,20 @@
# we need two subclasses of the app-level type, one to add mapdict, and then one
# to add del to not slow down the GC.
-def get_unique_interplevel_subclass(space, cls, needsdel=False):
+def get_unique_interplevel_subclass(space, cls):
"NOT_RPYTHON: initialization-time only"
- if hasattr(cls, '__del__') and getattr(cls, "handle_del_manually", False):
- needsdel = False
assert cls.typedef.acceptable_as_base_class
- key = space, cls, needsdel
try:
- return _subclass_cache[key]
+ return _unique_subclass_cache[cls]
except KeyError:
- # XXX can save a class if cls already has a __del__
- if needsdel:
- cls = get_unique_interplevel_subclass(space, cls, False)
- subcls = _getusercls(space, cls, needsdel)
- assert key not in _subclass_cache
- _subclass_cache[key] = subcls
+ subcls = _getusercls(cls)
+ assert cls not in _unique_subclass_cache
+ _unique_subclass_cache[cls] = subcls
return subcls
get_unique_interplevel_subclass._annspecialcase_ = "specialize:memo"
-_subclass_cache = {}
+_unique_subclass_cache = {}
-def _getusercls(space, cls, wants_del, reallywantdict=False):
+def _getusercls(cls, reallywantdict=False):
from rpython.rlib import objectmodel
from pypy.objspace.std.objectobject import W_ObjectObject
from pypy.module.__builtin__.interp_classobj import W_InstanceObject
@@ -132,11 +128,10 @@
typedef = cls.typedef
name = cls.__name__ + "User"
- mixins_needed = []
if cls is W_ObjectObject or cls is W_InstanceObject:
- mixins_needed.append(_make_storage_mixin_size_n())
+ base_mixin = _make_storage_mixin_size_n()
else:
- mixins_needed.append(MapdictStorageMixin)
+ base_mixin = MapdictStorageMixin
copy_methods = [BaseUserClassMapdict]
if reallywantdict or not typedef.hasdict:
# the type has no dict, mapdict to provide the dict
@@ -147,44 +142,12 @@
# support
copy_methods.append(MapdictWeakrefSupport)
name += "Weakrefable"
- if wants_del:
- # This subclass comes with an app-level __del__. To handle
- # it, we make an RPython-level __del__ method. This
- # RPython-level method is called directly by the GC and it
- # cannot do random things (calling the app-level __del__ would
- # be "random things"). So instead, we just call here
- # enqueue_for_destruction(), and the app-level __del__ will be
- # called later at a safe point (typically between bytecodes).
- # If there is also an inherited RPython-level __del__, it is
- # called afterwards---not immediately! This base
- # RPython-level __del__ is supposed to run only when the
- # object is not reachable any more. NOTE: it doesn't fully
- # work: see issue #2287.
- name += "Del"
- parent_destructor = getattr(cls, '__del__', None)
- def call_parent_del(self):
- assert isinstance(self, subcls)
- parent_destructor(self)
- def call_applevel_del(self):
- assert isinstance(self, subcls)
- space.userdel(self)
- class Proto(object):
- def __del__(self):
- self.clear_all_weakrefs()
- self.enqueue_for_destruction(space, call_applevel_del,
- 'method __del__ of ')
- if parent_destructor is not None:
- self.enqueue_for_destruction(space, call_parent_del,
- 'internal destructor of ')
- mixins_needed.append(Proto)
class subcls(cls):
user_overridden_class = True
- for base in mixins_needed:
- objectmodel.import_from_mixin(base)
+ objectmodel.import_from_mixin(base_mixin)
for copycls in copy_methods:
_copy_methods(copycls, subcls)
- del subcls.base
subcls.__name__ = name
return subcls
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
@@ -44,13 +44,12 @@
self.bases_w = bases
self.w_dict = w_dict
+ def has_user_del(self, space):
+ return self.lookup(space, '__del__') is not None
+
def instantiate(self, space):
cache = space.fromcache(Cache)
- if self.lookup(space, '__del__') is not None:
- w_inst = cache.cls_with_del(space, self)
- else:
- w_inst = cache.cls_without_del(space, self)
- return w_inst
+ return cache.InstanceObjectCls(space, self)
def getdict(self, space):
return self.w_dict
@@ -132,9 +131,9 @@
self.setbases(space, w_value)
return
elif name == "__del__":
- if self.lookup(space, name) is None:
+ if not self.has_user_del(space):
msg = ("a __del__ method added to an existing class will "
- "not be called")
+ "only be called on instances made from now on")
space.warn(space.wrap(msg), space.w_RuntimeWarning)
space.setitem(self.w_dict, w_attr, w_value)
@@ -184,14 +183,11 @@
if hasattr(space, 'is_fake_objspace'):
# hack: with the fake objspace, we don't want to see typedef's
# _getusercls() at all
- self.cls_without_del = W_InstanceObject
- self.cls_with_del = W_InstanceObject
+ self.InstanceObjectCls = W_InstanceObject
return
- self.cls_without_del = _getusercls(
- space, W_InstanceObject, False, reallywantdict=True)
- self.cls_with_del = _getusercls(
- space, W_InstanceObject, True, reallywantdict=True)
+ self.InstanceObjectCls = _getusercls(
+ W_InstanceObject, reallywantdict=True)
def class_descr_call(space, w_self, __args__):
@@ -297,12 +293,15 @@
class W_InstanceObject(W_Root):
def __init__(self, space, w_class):
# note that user_setup is overridden by the typedef.py machinery
+ self.space = space
self.user_setup(space, space.gettypeobject(self.typedef))
assert isinstance(w_class, W_ClassObject)
self.w_class = w_class
+ if w_class.has_user_del(space):
+ space.finalizer_queue.register_finalizer(self)
def user_setup(self, space, w_subtype):
- self.space = space
+ pass
def set_oldstyle_class(self, space, w_class):
if w_class is None or not isinstance(w_class, W_ClassObject):
@@ -368,8 +367,7 @@
self.set_oldstyle_class(space, w_value)
return
if name == '__del__' and w_meth is None:
- cache = space.fromcache(Cache)
- if (not isinstance(self, cache.cls_with_del)
+ if (not self.w_class.has_user_del(space)
and self.getdictvalue(space, '__del__') is None):
msg = ("a __del__ method added to an instance with no "
"__del__ in the class will not be called")
@@ -646,13 +644,14 @@
raise oefmt(space.w_TypeError, "instance has no next() method")
return space.call_function(w_func)
- def descr_del(self, space):
- # Note that this is called from executioncontext.UserDelAction
- # via the space.userdel() method.
+ def _finalize_(self):
+ space = self.space
w_func = self.getdictvalue(space, '__del__')
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):
@@ -729,7 +728,6 @@
__pow__ = interp2app(W_InstanceObject.descr_pow),
__rpow__ = interp2app(W_InstanceObject.descr_rpow),
next = interp2app(W_InstanceObject.descr_next),
- __del__ = interp2app(W_InstanceObject.descr_del),
__exit__ = interp2app(W_InstanceObject.descr_exit),
__dict__ = dict_descr,
**rawdict
diff --git a/pypy/module/_cffi_backend/allocator.py b/pypy/module/_cffi_backend/allocator.py
--- a/pypy/module/_cffi_backend/allocator.py
+++ b/pypy/module/_cffi_backend/allocator.py
@@ -45,14 +45,11 @@
rffi.c_memset(rffi.cast(rffi.VOIDP, ptr), 0,
rffi.cast(rffi.SIZE_T, datasize))
#
- if self.w_free is None:
- # use this class which does not have a __del__, but still
- # keeps alive w_raw_cdata
- res = cdataobj.W_CDataNewNonStdNoFree(space, ptr, ctype, length)
- else:
- res = cdataobj.W_CDataNewNonStdFree(space, ptr, ctype, length)
+ res = cdataobj.W_CDataNewNonStd(space, ptr, ctype, length)
+ res.w_raw_cdata = w_raw_cdata
+ if self.w_free is not None:
res.w_free = self.w_free
- res.w_raw_cdata = w_raw_cdata
+ res.register_finalizer(space)
return res
@unwrap_spec(w_init=WrappedDefault(None))
diff --git a/pypy/module/_cffi_backend/cdataobj.py b/pypy/module/_cffi_backend/cdataobj.py
--- a/pypy/module/_cffi_backend/cdataobj.py
+++ b/pypy/module/_cffi_backend/cdataobj.py
@@ -449,22 +449,11 @@
lltype.free(self._ptr, flavor='raw')
-class W_CDataNewNonStdNoFree(W_CDataNewOwning):
- """Subclass using a non-standard allocator, no free()"""
- _attrs_ = ['w_raw_cdata']
+class W_CDataNewNonStd(W_CDataNewOwning):
+ """Subclass using a non-standard allocator"""
+ _attrs_ = ['w_raw_cdata', 'w_free']
-class W_CDataNewNonStdFree(W_CDataNewNonStdNoFree):
- """Subclass using a non-standard allocator, with a free()"""
- _attrs_ = ['w_free']
-
- def __del__(self):
- self.clear_all_weakrefs()
- self.enqueue_for_destruction(self.space,
- W_CDataNewNonStdFree.call_destructor,
- 'destructor of ')
-
- def call_destructor(self):
- assert isinstance(self, W_CDataNewNonStdFree)
+ def _finalize_(self):
self.space.call_function(self.w_free, self.w_raw_cdata)
@@ -552,14 +541,9 @@
W_CData.__init__(self, space, cdata, ctype)
self.w_original_cdata = w_original_cdata
self.w_destructor = w_destructor
+ self.register_finalizer(space)
- def __del__(self):
- self.clear_all_weakrefs()
- self.enqueue_for_destruction(self.space, W_CDataGCP.call_destructor,
- 'destructor of ')
-
- def call_destructor(self):
- assert isinstance(self, W_CDataGCP)
+ def _finalize_(self):
w_destructor = self.w_destructor
if w_destructor is not None:
self.w_destructor = None
diff --git a/pypy/module/_cffi_backend/cdlopen.py b/pypy/module/_cffi_backend/cdlopen.py
--- a/pypy/module/_cffi_backend/cdlopen.py
+++ b/pypy/module/_cffi_backend/cdlopen.py
@@ -25,10 +25,13 @@
raise wrap_dlopenerror(ffi.space, e, filename)
W_LibObject.__init__(self, ffi, filename)
self.libhandle = handle
+ self.register_finalizer(ffi.space)
- def __del__(self):
- if self.libhandle:
- dlclose(self.libhandle)
+ def _finalize_(self):
+ h = self.libhandle
+ if h != rffi.cast(DLLHANDLE, 0):
+ self.libhandle = rffi.cast(DLLHANDLE, 0)
+ dlclose(h)
def cdlopen_fetch(self, name):
if not self.libhandle:
diff --git a/pypy/module/_cffi_backend/libraryobj.py b/pypy/module/_cffi_backend/libraryobj.py
--- a/pypy/module/_cffi_backend/libraryobj.py
+++ b/pypy/module/_cffi_backend/libraryobj.py
@@ -15,7 +15,6 @@
class W_Library(W_Root):
_immutable_ = True
- handle = rffi.cast(DLLHANDLE, 0)
def __init__(self, space, filename, flags):
self.space = space
@@ -27,8 +26,9 @@
except DLOpenError as e:
raise wrap_dlopenerror(space, e, filename)
self.name = filename
+ self.register_finalizer(space)
- def __del__(self):
+ def _finalize_(self):
h = self.handle
if h != rffi.cast(DLLHANDLE, 0):
self.handle = rffi.cast(DLLHANDLE, 0)
diff --git a/pypy/module/_file/interp_file.py b/pypy/module/_file/interp_file.py
--- a/pypy/module/_file/interp_file.py
+++ b/pypy/module/_file/interp_file.py
@@ -43,22 +43,18 @@
def __init__(self, space):
self.space = space
+ self.register_finalizer(space)
- def __del__(self):
+ def _finalize_(self):
# assume that the file and stream objects are only visible in the
- # thread that runs __del__, so no race condition should be possible
- self.clear_all_weakrefs()
+ # thread that runs _finalize_, so no race condition should be
+ # possible and no locking is done here.
if self.stream is not None:
- self.enqueue_for_destruction(self.space, W_File.destructor,
- 'close() method of ')
-
- def destructor(self):
- assert isinstance(self, W_File)
- try:
- self.direct_close()
- except StreamErrors as e:
- operr = wrap_streamerror(self.space, e, self.w_name)
- raise operr
+ try:
+ self.direct_close()
+ except StreamErrors as e:
+ operr = wrap_streamerror(self.space, e, self.w_name)
+ raise operr
def fdopenstream(self, stream, fd, mode, w_name=None):
self.fd = fd
diff --git a/pypy/module/_hashlib/interp_hashlib.py b/pypy/module/_hashlib/interp_hashlib.py
--- a/pypy/module/_hashlib/interp_hashlib.py
+++ b/pypy/module/_hashlib/interp_hashlib.py
@@ -76,11 +76,14 @@
except:
lltype.free(ctx, flavor='raw')
raise
+ self.register_finalizer(space)
- def __del__(self):
- if self.ctx:
- ropenssl.EVP_MD_CTX_cleanup(self.ctx)
- lltype.free(self.ctx, flavor='raw')
+ def _finalize_(self):
+ ctx = self.ctx
+ if ctx:
+ self.ctx = lltype.nullptr(ropenssl.EVP_MD_CTX.TO)
+ ropenssl.EVP_MD_CTX_cleanup(ctx)
+ lltype.free(ctx, flavor='raw')
def digest_type_by_name(self, space):
digest_type = ropenssl.EVP_get_digestbyname(self.name)
diff --git a/pypy/module/_io/interp_bufferedio.py b/pypy/module/_io/interp_bufferedio.py
--- a/pypy/module/_io/interp_bufferedio.py
+++ b/pypy/module/_io/interp_bufferedio.py
@@ -952,9 +952,15 @@
self.w_writer = None
raise
- def __del__(self):
- self.clear_all_weakrefs()
+ def _finalize_(self):
# Don't call the base __del__: do not close the files!
+ # Usually the _finalize_() method is not called at all because
+ # we set 'needs_to_finalize = False' in this class, so
+ # W_IOBase.__init__() won't call register_finalizer().
+ # However, this method might still be called: if the user
+ # makes an app-level subclass and adds a custom __del__.
+ pass
+ needs_to_finalize = False
# forward to reader
for method in ['read', 'peek', 'read1', 'readinto', 'readable']:
diff --git a/pypy/module/_io/interp_iobase.py b/pypy/module/_io/interp_iobase.py
--- a/pypy/module/_io/interp_iobase.py
+++ b/pypy/module/_io/interp_iobase.py
@@ -59,6 +59,8 @@
self.__IOBase_closed = False
if add_to_autoflusher:
get_autoflusher(space).add(self)
+ if self.needs_to_finalize:
+ self.register_finalizer(space)
def getdict(self, space):
return self.w_dict
@@ -71,13 +73,7 @@
return True
return False
- def __del__(self):
- self.clear_all_weakrefs()
- self.enqueue_for_destruction(self.space, W_IOBase.destructor,
- 'internal __del__ of ')
-
- def destructor(self):
- assert isinstance(self, W_IOBase)
+ def _finalize_(self):
space = self.space
w_closed = space.findattr(self, space.wrap('closed'))
try:
@@ -90,6 +86,7 @@
# equally as bad, and potentially more frequent (because of
# shutdown issues).
pass
+ needs_to_finalize = True
def _CLOSED(self):
# Use this macro whenever you want to check the internal `closed`
diff --git a/pypy/module/_multibytecodec/interp_incremental.py b/pypy/module/_multibytecodec/interp_incremental.py
--- a/pypy/module/_multibytecodec/interp_incremental.py
+++ b/pypy/module/_multibytecodec/interp_incremental.py
@@ -20,8 +20,9 @@
self.codec = codec.codec
self.name = codec.name
self._initialize()
+ self.register_finalizer(space)
- def __del__(self):
+ def _finalize_(self):
self._free()
def reset_w(self):
diff --git a/pypy/module/_multiprocessing/interp_connection.py b/pypy/module/_multiprocessing/interp_connection.py
--- a/pypy/module/_multiprocessing/interp_connection.py
+++ b/pypy/module/_multiprocessing/interp_connection.py
@@ -40,14 +40,17 @@
BUFFER_SIZE = 1024
buffer = lltype.nullptr(rffi.CCHARP.TO)
- def __init__(self, flags):
+ def __init__(self, space, flags):
self.flags = flags
self.buffer = lltype.malloc(rffi.CCHARP.TO, self.BUFFER_SIZE,
flavor='raw')
+ self.register_finalizer(space)
- def __del__(self):
- if self.buffer:
- lltype.free(self.buffer, flavor='raw')
+ def _finalize_(self):
+ buf = self.buffer
+ if buf:
+ self.buffer = lltype.nullptr(rffi.CCHARP.TO)
+ lltype.free(buf, flavor='raw')
try:
self.do_close()
except OSError:
@@ -242,7 +245,7 @@
def __init__(self, space, fd, flags):
if fd == self.INVALID_HANDLE_VALUE or fd < 0:
raise oefmt(space.w_IOError, "invalid handle %d", fd)
- W_BaseConnection.__init__(self, flags)
+ W_BaseConnection.__init__(self, space, flags)
self.fd = fd
@unwrap_spec(fd=int, readable=bool, writable=bool)
@@ -363,8 +366,8 @@
if sys.platform == 'win32':
from rpython.rlib.rwin32 import INVALID_HANDLE_VALUE
- def __init__(self, handle, flags):
- W_BaseConnection.__init__(self, flags)
+ def __init__(self, space, handle, flags):
+ W_BaseConnection.__init__(self, space, flags)
self.handle = handle
@unwrap_spec(readable=bool, writable=bool)
@@ -375,7 +378,7 @@
flags = (readable and READABLE) | (writable and WRITABLE)
self = space.allocate_instance(W_PipeConnection, w_subtype)
- W_PipeConnection.__init__(self, handle, flags)
+ W_PipeConnection.__init__(self, space, handle, flags)
return space.wrap(self)
def descr_repr(self, space):
diff --git a/pypy/module/_multiprocessing/interp_semaphore.py b/pypy/module/_multiprocessing/interp_semaphore.py
--- a/pypy/module/_multiprocessing/interp_semaphore.py
+++ b/pypy/module/_multiprocessing/interp_semaphore.py
@@ -430,11 +430,12 @@
class W_SemLock(W_Root):
- def __init__(self, handle, kind, maxvalue):
+ def __init__(self, space, handle, kind, maxvalue):
self.handle = handle
self.kind = kind
self.count = 0
self.maxvalue = maxvalue
+ self.register_finalizer(space)
def kind_get(self, space):
return space.newint(self.kind)
@@ -508,7 +509,7 @@
@unwrap_spec(kind=int, maxvalue=int)
def rebuild(space, w_cls, w_handle, kind, maxvalue):
self = space.allocate_instance(W_SemLock, w_cls)
- self.__init__(handle_w(space, w_handle), kind, maxvalue)
+ self.__init__(space, handle_w(space, w_handle), kind, maxvalue)
return space.wrap(self)
def enter(self, space):
@@ -517,7 +518,7 @@
def exit(self, space, __args__):
self.release(space)
- def __del__(self):
+ def _finalize_(self):
delete_semaphore(self.handle)
@unwrap_spec(kind=int, value=int, maxvalue=int)
@@ -534,7 +535,7 @@
raise wrap_oserror(space, e)
self = space.allocate_instance(W_SemLock, w_subtype)
- self.__init__(handle, kind, maxvalue)
+ self.__init__(space, handle, kind, maxvalue)
return space.wrap(self)
diff --git a/pypy/module/_pickle_support/maker.py b/pypy/module/_pickle_support/maker.py
--- a/pypy/module/_pickle_support/maker.py
+++ b/pypy/module/_pickle_support/maker.py
@@ -4,7 +4,7 @@
from pypy.interpreter.function import Function, Method
from pypy.interpreter.module import Module
from pypy.interpreter.pytraceback import PyTraceback
-from pypy.interpreter.generator import GeneratorIteratorWithDel
+from pypy.interpreter.generator import GeneratorIterator
from rpython.rlib.objectmodel import instantiate
from pypy.interpreter.gateway import unwrap_spec
from pypy.objspace.std.iterobject import W_SeqIterObject, W_ReverseSeqIterObject
@@ -59,7 +59,7 @@
return space.wrap(tb)
def generator_new(space):
- new_generator = instantiate(GeneratorIteratorWithDel)
+ new_generator = instantiate(GeneratorIterator)
return space.wrap(new_generator)
@unwrap_spec(current=int, remaining=int, step=int)
diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py
--- a/pypy/module/_ssl/interp_ssl.py
+++ b/pypy/module/_ssl/interp_ssl.py
@@ -278,6 +278,8 @@
sock_fd = space.int_w(space.call_method(w_sock, "fileno"))
self.ssl = libssl_SSL_new(w_ctx.ctx) # new ssl struct
+ self.register_finalizer(space)
+
index = compute_unique_id(self)
libssl_SSL_set_app_data(self.ssl, rffi.cast(rffi.VOIDP, index))
SOCKET_STORAGE.set(index, self)
@@ -317,16 +319,15 @@
self.ssl_sock_weakref_w = None
return self
- def __del__(self):
- self.enqueue_for_destruction(self.space, _SSLSocket.destructor,
- '__del__() method of ')
-
- def destructor(self):
- assert isinstance(self, _SSLSocket)
- if self.peer_cert:
- libssl_X509_free(self.peer_cert)
- if self.ssl:
- libssl_SSL_free(self.ssl)
+ def _finalize_(self):
+ peer_cert = self.peer_cert
+ if peer_cert:
+ self.peer_cert = lltype.nullptr(X509.TO)
+ libssl_X509_free(peer_cert)
+ ssl = self.ssl
+ if ssl:
+ self.ssl = lltype.nullptr(SSL.TO)
+ libssl_SSL_free(ssl)
@unwrap_spec(data='bufferstr')
def write(self, space, data):
@@ -1285,6 +1286,7 @@
self = space.allocate_instance(_SSLContext, w_subtype)
self.ctx = ctx
self.check_hostname = False
+ self.register_finalizer(space)
options = SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
if protocol != PY_SSL_VERSION_SSL2:
options |= SSL_OP_NO_SSLv2
@@ -1308,8 +1310,11 @@
return self
- def __del__(self):
- libssl_SSL_CTX_free(self.ctx)
+ def _finalize_(self):
+ ctx = self.ctx
+ if ctx:
+ self.ctx = lltype.nullptr(SSL_CTX.TO)
+ libssl_SSL_CTX_free(ctx)
@unwrap_spec(server_side=int)
def descr_wrap_socket(self, space, w_sock, server_side, w_server_hostname=None, w_ssl_sock=None):
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
@@ -3,7 +3,8 @@
from pypy.interpreter.error import oefmt
from pypy.interpreter.gateway import interp2app, ObjSpace
from pypy.interpreter.typedef import TypeDef
-from rpython.rlib import jit
+from pypy.interpreter.executioncontext import AsyncAction, report_error
+from rpython.rlib import jit, rgc
from rpython.rlib.rshrinklist import AbstractShrinkList
from rpython.rlib.objectmodel import specialize
from rpython.rlib.rweakref import dead_ref
@@ -16,9 +17,12 @@
class WeakrefLifeline(W_Root):
+ typedef = None
+
cached_weakref = None
cached_proxy = None
other_refs_weak = None
+ has_callbacks = False
def __init__(self, space):
self.space = space
@@ -99,31 +103,10 @@
return w_ref
return space.w_None
-
-class WeakrefLifelineWithCallbacks(WeakrefLifeline):
-
- def __init__(self, space, oldlifeline=None):
- self.space = space
- if oldlifeline is not None:
- self.cached_weakref = oldlifeline.cached_weakref
- self.cached_proxy = oldlifeline.cached_proxy
- self.other_refs_weak = oldlifeline.other_refs_weak
-
- def __del__(self):
- """This runs when the interp-level object goes away, and allows
- its lifeline to go away. The purpose of this is to activate the
- callbacks even if there is no __del__ method on the interp-level
- W_Root subclass implementing the object.
- """
- if self.other_refs_weak is None:
- return
- items = self.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:
- w_ref.enqueue_for_destruction(self.space,
- W_WeakrefBase.activate_callback,
- 'weakref callback of ')
+ def enable_callbacks(self):
+ if not self.has_callbacks:
+ self.space.finalizer_queue.register_finalizer(self)
+ self.has_callbacks = True
@jit.dont_look_inside
def make_weakref_with_callback(self, w_subtype, w_obj, w_callable):
@@ -131,6 +114,7 @@
w_ref = space.allocate_instance(W_Weakref, w_subtype)
W_Weakref.__init__(w_ref, space, w_obj, w_callable)
self.append_wref_to(w_ref)
+ self.enable_callbacks()
return w_ref
@jit.dont_look_inside
@@ -141,8 +125,33 @@
else:
w_proxy = W_Proxy(space, w_obj, w_callable)
self.append_wref_to(w_proxy)
+ self.enable_callbacks()
return w_proxy
+ 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)
+
+
# ____________________________________________________________
@@ -163,7 +172,6 @@
self.w_obj_weak = dead_ref
def activate_callback(w_self):
- assert isinstance(w_self, W_WeakrefBase)
w_self.space.call_function(w_self.w_callable, w_self)
def descr__repr__(self, space):
@@ -227,32 +235,16 @@
w_obj.setweakref(space, lifeline)
return lifeline
-def getlifelinewithcallbacks(space, w_obj):
- lifeline = w_obj.getweakref()
- if not isinstance(lifeline, WeakrefLifelineWithCallbacks): # or None
- oldlifeline = lifeline
- lifeline = WeakrefLifelineWithCallbacks(space, oldlifeline)
- w_obj.setweakref(space, lifeline)
- return lifeline
-
-
-def get_or_make_weakref(space, w_subtype, w_obj):
- return getlifeline(space, w_obj).get_or_make_weakref(w_subtype, w_obj)
-
-
-def make_weakref_with_callback(space, w_subtype, w_obj, w_callable):
- lifeline = getlifelinewithcallbacks(space, w_obj)
- return lifeline.make_weakref_with_callback(w_subtype, w_obj, w_callable)
-
def descr__new__weakref(space, w_subtype, w_obj, w_callable=None,
__args__=None):
if __args__.arguments_w:
raise oefmt(space.w_TypeError, "__new__ expected at most 2 arguments")
+ lifeline = getlifeline(space, w_obj)
if space.is_none(w_callable):
- return get_or_make_weakref(space, w_subtype, w_obj)
+ return lifeline.get_or_make_weakref(w_subtype, w_obj)
else:
- return make_weakref_with_callback(space, w_subtype, w_obj, w_callable)
+ return lifeline.make_weakref_with_callback(w_subtype, w_obj, w_callable)
W_Weakref.typedef = TypeDef("weakref",
__doc__ = """A weak reference to an object 'obj'. A 'callback' can be given,
@@ -308,23 +300,15 @@
return space.call_args(w_obj, __args__)
-def get_or_make_proxy(space, w_obj):
- return getlifeline(space, w_obj).get_or_make_proxy(w_obj)
-
-
-def make_proxy_with_callback(space, w_obj, w_callable):
- lifeline = getlifelinewithcallbacks(space, w_obj)
- return lifeline.make_proxy_with_callback(w_obj, w_callable)
-
-
def proxy(space, w_obj, w_callable=None):
"""Create a proxy object that weakly references 'obj'.
'callback', if given, is called with the proxy as an argument when 'obj'
is about to be finalized."""
+ lifeline = getlifeline(space, w_obj)
if space.is_none(w_callable):
- return get_or_make_proxy(space, w_obj)
+ return lifeline.get_or_make_proxy(w_obj)
else:
- return make_proxy_with_callback(space, w_obj, w_callable)
+ return lifeline.make_proxy_with_callback(w_obj, w_callable)
def descr__new__proxy(space, w_subtype, w_obj, w_callable=None):
raise oefmt(space.w_TypeError, "cannot create 'weakproxy' instances")
@@ -345,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/_weakref/test/test_weakref.py b/pypy/module/_weakref/test/test_weakref.py
--- a/pypy/module/_weakref/test/test_weakref.py
+++ b/pypy/module/_weakref/test/test_weakref.py
@@ -1,6 +1,9 @@
class AppTestWeakref(object):
spaceconfig = dict(usemodules=('_weakref',))
-
+
+ def setup_class(cls):
+ cls.w_runappdirect = cls.space.wrap(cls.runappdirect)
+
def test_simple(self):
import _weakref, gc
class A(object):
@@ -287,6 +290,9 @@
assert a1 is None
def test_del_and_callback_and_id(self):
+ if not self.runappdirect:
+ skip("the id() doesn't work correctly in __del__ and "
+ "callbacks before translation")
import gc, weakref
seen_del = []
class A(object):
diff --git a/pypy/module/bz2/interp_bz2.py b/pypy/module/bz2/interp_bz2.py
--- a/pypy/module/bz2/interp_bz2.py
+++ b/pypy/module/bz2/interp_bz2.py
@@ -518,8 +518,14 @@
def __init__(self, space, compresslevel):
self.space = space
self.bzs = lltype.malloc(bz_stream.TO, flavor='raw', zero=True)
- self.running = False
- self._init_bz2comp(compresslevel)
+ try:
+ self.running = False
+ self._init_bz2comp(compresslevel)
+ except:
+ lltype.free(self.bzs, flavor='raw')
+ self.bzs = lltype.nullptr(bz_stream.TO)
+ raise
+ self.register_finalizer(space)
def _init_bz2comp(self, compresslevel):
if compresslevel < 1 or compresslevel > 9:
@@ -532,9 +538,12 @@
self.running = True
- def __del__(self):
- BZ2_bzCompressEnd(self.bzs)
- lltype.free(self.bzs, flavor='raw')
+ def _finalize_(self):
+ bzs = self.bzs
+ if bzs:
+ self.bzs = lltype.nullptr(bz_stream.TO)
+ BZ2_bzCompressEnd(bzs)
+ lltype.free(bzs, flavor='raw')
@unwrap_spec(data='bufferstr')
def compress(self, data):
@@ -621,10 +630,16 @@
self.space = space
self.bzs = lltype.malloc(bz_stream.TO, flavor='raw', zero=True)
- self.running = False
- self.unused_data = ""
+ try:
+ self.running = False
+ self.unused_data = ""
- self._init_bz2decomp()
+ self._init_bz2decomp()
+ except:
+ lltype.free(self.bzs, flavor='raw')
+ self.bzs = lltype.nullptr(bz_stream.TO)
+ raise
+ self.register_finalizer(space)
def _init_bz2decomp(self):
bzerror = BZ2_bzDecompressInit(self.bzs, 0, 0)
@@ -633,9 +648,12 @@
self.running = True
- def __del__(self):
- BZ2_bzDecompressEnd(self.bzs)
- lltype.free(self.bzs, flavor='raw')
+ def _finalize_(self):
+ bzs = self.bzs
+ if bzs:
+ self.bzs = lltype.nullptr(bz_stream.TO)
+ BZ2_bzDecompressEnd(bzs)
+ lltype.free(bzs, flavor='raw')
@unwrap_spec(data='bufferstr')
def decompress(self, data):
diff --git a/pypy/module/bz2/test/support.py b/pypy/module/bz2/test/support.py
--- a/pypy/module/bz2/test/support.py
+++ b/pypy/module/bz2/test/support.py
@@ -10,5 +10,6 @@
#
while tries and ll2ctypes.ALLOCATED:
gc.collect() # to make sure we disallocate buffers
+ self.space.getexecutioncontext()._run_finalizers_now()
tries -= 1
assert not ll2ctypes.ALLOCATED
diff --git a/pypy/module/cppyy/interp_cppyy.py b/pypy/module/cppyy/interp_cppyy.py
--- a/pypy/module/cppyy/interp_cppyy.py
+++ b/pypy/module/cppyy/interp_cppyy.py
@@ -1020,9 +1020,12 @@
class W_CPPInstance(W_Root):
- _attrs_ = ['space', 'cppclass', '_rawobject', 'isref', 'python_owns']
+ _attrs_ = ['space', 'cppclass', '_rawobject', 'isref', 'python_owns',
+ 'finalizer_registered']
_immutable_fields_ = ["cppclass", "isref"]
+ finalizer_registered = False
+
def __init__(self, space, cppclass, rawobject, isref, python_owns):
self.space = space
self.cppclass = cppclass
@@ -1032,6 +1035,12 @@
assert not isref or not python_owns
self.isref = isref
self.python_owns = python_owns
+ self._opt_register_finalizer()
+
+ def _opt_register_finalizer(self):
+ if self.python_owns and not self.finalizer_registered:
+ self.register_finalizer(self.space)
+ self.finalizer_registered = True
def _nullcheck(self):
if not self._rawobject or (self.isref and not self.get_rawobject()):
@@ -1045,6 +1054,7 @@
@unwrap_spec(value=bool)
def fset_python_owns(self, space, value):
self.python_owns = space.is_true(value)
+ self._opt_register_finalizer()
def get_cppthis(self, calling_scope):
return self.cppclass.get_cppthis(self, calling_scope)
@@ -1143,16 +1153,14 @@
(self.cppclass.name, rffi.cast(rffi.ULONG, self.get_rawobject())))
def destruct(self):
- assert isinstance(self, W_CPPInstance)
if self._rawobject and not self.isref:
memory_regulator.unregister(self)
capi.c_destruct(self.space, self.cppclass, self._rawobject)
self._rawobject = capi.C_NULL_OBJECT
- def __del__(self):
+ def _finalize_(self):
if self.python_owns:
- self.enqueue_for_destruction(self.space, W_CPPInstance.destruct,
- '__del__() method of ')
+ self.destruct()
W_CPPInstance.typedef = TypeDef(
'CPPInstance',
diff --git a/pypy/module/cpyext/src/abstract.c b/pypy/module/cpyext/src/abstract.c
--- a/pypy/module/cpyext/src/abstract.c
+++ b/pypy/module/cpyext/src/abstract.c
@@ -326,3 +326,9 @@
return tmp;
}
+/* for binary compatibility with 5.1 */
+PyAPI_FUNC(void) PyPyObject_Del(PyObject *);
+void PyPyObject_Del(PyObject *op)
+{
+ PyObject_FREE(op);
+}
diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py
--- a/pypy/module/cpyext/test/test_typeobject.py
+++ b/pypy/module/cpyext/test/test_typeobject.py
@@ -927,31 +927,62 @@
("fetchFooType", "METH_VARARGS",
"""
PyObject *o;
+ Foo_Type.tp_basicsize = sizeof(FooObject);
Foo_Type.tp_dealloc = &dealloc_foo;
- Foo_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+ Foo_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES
+ | Py_TPFLAGS_BASETYPE;
Foo_Type.tp_new = &new_foo;
Foo_Type.tp_free = &PyObject_Del;
if (PyType_Ready(&Foo_Type) < 0) return NULL;
o = PyObject_New(PyObject, &Foo_Type);
+ init_foo(o);
Py_DECREF(o); /* calls dealloc_foo immediately */
Py_INCREF(&Foo_Type);
return (PyObject *)&Foo_Type;
"""),
+ ("newInstance", "METH_O",
+ """
+ PyTypeObject *tp = (PyTypeObject *)args;
+ PyObject *e = PyTuple_New(0);
+ PyObject *o = tp->tp_new(tp, e, NULL);
+ Py_DECREF(e);
+ return o;
+ """),
("getCounter", "METH_VARARGS",
"""
return PyInt_FromLong(foo_counter);
""")], prologue=
"""
+ typedef struct {
+ PyObject_HEAD
+ int someval[99];
+ } FooObject;
static int foo_counter = 1000;
static void dealloc_foo(PyObject *foo) {
+ int i;
foo_counter += 10;
+ for (i = 0; i < 99; i++)
+ if (((FooObject *)foo)->someval[i] != 1000 + i)
+ foo_counter += 100000; /* error! */
+ Py_TYPE(foo)->tp_free(foo);
+ }
+ static void init_foo(PyObject *o)
+ {
+ int i;
+ if (o->ob_type->tp_basicsize < sizeof(FooObject))
+ abort();
+ for (i = 0; i < 99; i++)
+ ((FooObject *)o)->someval[i] = 1000 + i;
}
static PyObject *new_foo(PyTypeObject *t, PyObject *a, PyObject *k)
{
+ PyObject *o;
foo_counter += 1000;
- return t->tp_alloc(t, 0);
+ o = t->tp_alloc(t, 0);
+ init_foo(o);
+ return o;
}
static PyTypeObject Foo_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
@@ -971,9 +1002,24 @@
#
class Bar(Foo):
pass
+ assert Foo.__new__ is Bar.__new__
Bar(); Bar()
for i in range(10):
if module.getCounter() >= 5050:
break
self.debug_collect()
assert module.getCounter() == 5050
+ #
+ module.newInstance(Foo)
+ for i in range(10):
+ if module.getCounter() >= 6060:
+ break
+ self.debug_collect()
+ assert module.getCounter() == 6060
+ #
+ module.newInstance(Bar)
+ for i in range(10):
+ if module.getCounter() >= 7070:
+ break
+ self.debug_collect()
+ assert module.getCounter() == 7070
diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py
--- a/pypy/module/cpyext/typeobject.py
+++ b/pypy/module/cpyext/typeobject.py
@@ -196,6 +196,10 @@
def update_all_slots(space, w_type, pto):
# XXX fill slots in pto
+ # Not very sure about it, but according to
+ # test_call_tp_dealloc_when_created_from_python, we should not
+ # overwrite slots that are already set: these ones are probably
+ # coming from a parent C type.
typedef = w_type.layout.typedef
for method_name, slot_name, slot_names, slot_func in slotdefs_for_tp_slots:
@@ -223,7 +227,8 @@
# XXX special case wrapper-functions and use a "specific" slot func
if len(slot_names) == 1:
- setattr(pto, slot_names[0], slot_func_helper)
+ if not getattr(pto, slot_names[0]):
+ setattr(pto, slot_names[0], slot_func_helper)
else:
assert len(slot_names) == 2
struct = getattr(pto, slot_names[0])
@@ -240,7 +245,8 @@
struct = lltype.malloc(STRUCT_TYPE, flavor='raw', zero=True)
setattr(pto, slot_names[0], struct)
- setattr(struct, slot_names[1], slot_func_helper)
+ if not getattr(struct, slot_names[1]):
+ setattr(struct, slot_names[1], slot_func_helper)
def add_operators(space, dict_w, pto):
# XXX support PyObject_HashNotImplemented
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/module/micronumpy/ufuncs.py b/pypy/module/micronumpy/ufuncs.py
--- a/pypy/module/micronumpy/ufuncs.py
+++ b/pypy/module/micronumpy/ufuncs.py
@@ -3,7 +3,7 @@
from pypy.interpreter.gateway import interp2app, unwrap_spec, WrappedDefault
from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty
from pypy.interpreter.argument import Arguments
-from rpython.rlib import jit
+from rpython.rlib import jit, rgc
from rpython.rlib.rarithmetic import LONG_BIT, maxint, _get_bitsize
from rpython.tool.sourcetools import func_with_new_name
from rpython.rlib.rawstorage import (
@@ -1534,6 +1534,7 @@
self.steps = alloc_raw_storage(0, track_allocation=False)
self.dims_steps_set = False
+ @rgc.must_be_light_finalizer
def __del__(self):
free_raw_storage(self.dims, track_allocation=False)
free_raw_storage(self.steps, track_allocation=False)
diff --git a/pypy/module/pyexpat/interp_pyexpat.py b/pypy/module/pyexpat/interp_pyexpat.py
--- a/pypy/module/pyexpat/interp_pyexpat.py
+++ b/pypy/module/pyexpat/interp_pyexpat.py
@@ -421,8 +421,11 @@
class W_XMLParserType(W_Root):
+ id = -1
+
def __init__(self, space, parser, w_intern):
self.itself = parser
+ self.register_finalizer(space)
self.w_intern = w_intern
@@ -444,14 +447,17 @@
CallbackData(space, self))
XML_SetUserData(self.itself, rffi.cast(rffi.VOIDP, self.id))
- def __del__(self):
+ def _finalize_(self):
if XML_ParserFree: # careful with CPython interpreter shutdown
- XML_ParserFree(self.itself)
- if global_storage:
+ if self.itself:
+ XML_ParserFree(self.itself)
+ self.itself = lltype.nullptr(XML_Parser.TO)
+ if global_storage and self.id >= 0:
try:
global_storage.free_nonmoving_id(self.id)
except KeyError:
pass # maybe global_storage.clear() was already called
+ self.id = -1
@unwrap_spec(flag=int)
def SetParamEntityParsing(self, space, flag):
diff --git a/pypy/module/pypyjit/test_pypy_c/test_weakref.py b/pypy/module/pypyjit/test_pypy_c/test_weakref.py
--- a/pypy/module/pypyjit/test_pypy_c/test_weakref.py
+++ b/pypy/module/pypyjit/test_pypy_c/test_weakref.py
@@ -28,10 +28,10 @@
p65 = getfield_gc_r(p14, descr=<FieldP .+inst_map \d+>)
guard_value(p65, ConstPtr(ptr45), descr=...)
p66 = getfield_gc_r(p14, descr=<FieldP .+inst__value0 \d+>)
- guard_nonnull_class(p66, ..., descr=...)
+ guard_nonnull(p66, descr=...)
p67 = force_token()
setfield_gc(p0, p67, descr=<FieldP pypy.interpreter.pyframe.PyFrame.vable_token \d+>)
- p68 = call_may_force_r(ConstClass(WeakrefLifelineWithCallbacks.make_weakref_with_callback), p66, ConstPtr(ptr50), p14, ConstPtr(ptr51), descr=<Callr \d rrrr EF=7>)
+ p68 = call_may_force_r(ConstClass(WeakrefLifeline.make_weakref_with_callback), p66, ConstPtr(ptr50), p14, ConstPtr(ptr51), descr=<Callr \d rrrr EF=7>)
guard_not_forced(descr=...)
guard_no_exception(descr=...)
guard_nonnull_class(p68, ..., descr=...)
diff --git a/pypy/module/select/interp_epoll.py b/pypy/module/select/interp_epoll.py
--- a/pypy/module/select/interp_epoll.py
+++ b/pypy/module/select/interp_epoll.py
@@ -80,6 +80,7 @@
class W_Epoll(W_Root):
def __init__(self, space, epfd):
self.epfd = epfd
+ self.register_finalizer(space)
@unwrap_spec(sizehint=int)
def descr__new__(space, w_subtype, sizehint=-1):
@@ -98,7 +99,7 @@
def descr_fromfd(space, w_cls, fd):
return space.wrap(W_Epoll(space, fd))
- def __del__(self):
+ def _finalize_(self):
self.close()
def check_closed(self, space):
diff --git a/pypy/module/select/interp_kqueue.py b/pypy/module/select/interp_kqueue.py
--- a/pypy/module/select/interp_kqueue.py
+++ b/pypy/module/select/interp_kqueue.py
@@ -109,6 +109,7 @@
class W_Kqueue(W_Root):
def __init__(self, space, kqfd):
self.kqfd = kqfd
+ self.register_finalizer(space)
def descr__new__(space, w_subtype):
kqfd = syscall_kqueue()
@@ -120,7 +121,7 @@
def descr_fromfd(space, w_cls, fd):
return space.wrap(W_Kqueue(space, fd))
- def __del__(self):
+ def _finalize_(self):
self.close()
def get_closed(self):
diff --git a/pypy/module/zlib/interp_zlib.py b/pypy/module/zlib/interp_zlib.py
--- a/pypy/module/zlib/interp_zlib.py
+++ b/pypy/module/zlib/interp_zlib.py
@@ -148,8 +148,9 @@
raise zlib_error(space, e.msg)
except ValueError:
raise oefmt(space.w_ValueError, "Invalid initialization option")
+ self.register_finalizer(space)
- def __del__(self):
+ def _finalize_(self):
"""Automatically free the resources used by the stream."""
if self.stream:
rzlib.deflateEnd(self.stream)
@@ -258,8 +259,9 @@
raise zlib_error(space, e.msg)
except ValueError:
raise oefmt(space.w_ValueError, "Invalid initialization option")
+ self.register_finalizer(space)
- def __del__(self):
+ def _finalize_(self):
"""Automatically free the resources used by the stream."""
if self.stream:
rzlib.inflateEnd(self.stream)
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):
diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py
--- a/pypy/objspace/std/objspace.py
+++ b/pypy/objspace/std/objspace.py
@@ -357,11 +357,12 @@
if cls.typedef.applevel_subclasses_base is not None:
cls = cls.typedef.applevel_subclasses_base
#
- subcls = get_unique_interplevel_subclass(
- self, cls, w_subtype.needsdel)
+ subcls = get_unique_interplevel_subclass(self, cls)
instance = instantiate(subcls)
assert isinstance(instance, cls)
instance.user_setup(self, w_subtype)
+ if w_subtype.hasuserdel:
+ self.finalizer_queue.register_finalizer(instance)
else:
raise oefmt(self.w_TypeError,
"%N.__new__(%N): only for the type %N",
diff --git a/pypy/objspace/std/typeobject.py b/pypy/objspace/std/typeobject.py
--- a/pypy/objspace/std/typeobject.py
+++ b/pypy/objspace/std/typeobject.py
@@ -132,7 +132,7 @@
"flag_sequence_bug_compat",
"flag_map_or_seq", # '?' or 'M' or 'S'
"compares_by_identity_status?",
- 'needsdel',
+ 'hasuserdel',
'weakrefable',
'hasdict',
'layout',
@@ -160,7 +160,7 @@
w_self.bases_w = bases_w
w_self.dict_w = dict_w
w_self.hasdict = False
- w_self.needsdel = False
+ w_self.hasuserdel = False
w_self.weakrefable = False
w_self.w_doc = space.w_None
w_self.weak_subclasses = []
@@ -289,7 +289,7 @@
# compute a tuple that fully describes the instance layout
def get_full_instance_layout(w_self):
layout = w_self.layout
- return (layout, w_self.hasdict, w_self.needsdel, w_self.weakrefable)
+ return (layout, w_self.hasdict, w_self.weakrefable)
def compute_default_mro(w_self):
return compute_C3_mro(w_self.space, w_self)
@@ -986,7 +986,7 @@
hasoldstylebase = True
continue
w_self.hasdict = w_self.hasdict or w_base.hasdict
- w_self.needsdel = w_self.needsdel or w_base.needsdel
+ w_self.hasuserdel = w_self.hasuserdel or w_base.hasuserdel
w_self.weakrefable = w_self.weakrefable or w_base.weakrefable
return hasoldstylebase
@@ -1028,7 +1028,7 @@
if wantweakref:
create_weakref_slot(w_self)
if '__del__' in dict_w:
- w_self.needsdel = True
+ w_self.hasuserdel = True
#
if index_next_extra_slot == base_layout.nslots and not force_new_layout:
return base_layout
diff --git a/pypy/tool/pytest/apptest.py b/pypy/tool/pytest/apptest.py
--- a/pypy/tool/pytest/apptest.py
+++ b/pypy/tool/pytest/apptest.py
@@ -7,7 +7,7 @@
# ...unless the -A option ('runappdirect') is passed.
import py
-import sys, textwrap, types
+import sys, textwrap, types, gc
from pypy.interpreter.gateway import app2interp_temp
from pypy.interpreter.error import OperationError
from pypy.interpreter.function import Method
@@ -32,6 +32,7 @@
return traceback
def execute_appex(self, space, target, *args):
+ self.space = space
try:
target(*args)
except OperationError as e:
@@ -64,6 +65,13 @@
code = getattr(func, 'im_func', func).func_code
return "[%s:%s]" % (code.co_filename, code.co_firstlineno)
+ def track_allocations_collect(self):
+ gc.collect()
+ # must also invoke finalizers now; UserDelAction
+ # would not run at all unless invoked explicitly
+ if hasattr(self, 'space'):
+ self.space.getexecutioncontext()._run_finalizers_now()
+
class AppTestMethod(AppTestFunction):
def setup(self):
diff --git a/rpython/annotator/classdesc.py b/rpython/annotator/classdesc.py
--- a/rpython/annotator/classdesc.py
+++ b/rpython/annotator/classdesc.py
@@ -579,6 +579,14 @@
if cls not in FORCE_ATTRIBUTES_INTO_CLASSES:
self.all_enforced_attrs = [] # no attribute allowed
+ if (getattr(cls, '_must_be_light_finalizer_', False) and
+ hasattr(cls, '__del__') and
+ not getattr(cls.__del__, '_must_be_light_finalizer_', False)):
+ raise AnnotatorError(
+ "Class %r is in a class hierarchy with "
+ "_must_be_light_finalizer_ = True: it cannot have a "
+ "finalizer without @rgc.must_be_light_finalizer" % (cls,))
+
def add_source_attribute(self, name, value, mixin=False):
if isinstance(value, property):
# special case for property object
diff --git a/rpython/annotator/test/test_annrpython.py b/rpython/annotator/test/test_annrpython.py
--- a/rpython/annotator/test/test_annrpython.py
+++ b/rpython/annotator/test/test_annrpython.py
@@ -4584,6 +4584,32 @@
e = py.test.raises(Exception, a.build_types, f, [])
assert str(e.value) == "Don't know how to represent Ellipsis"
+ def test_must_be_light_finalizer(self):
+ from rpython.rlib import rgc
+ @rgc.must_be_light_finalizer
+ class A(object):
+ pass
+ class B(A):
+ def __del__(self):
+ pass
+ class C(A):
+ @rgc.must_be_light_finalizer
+ def __del__(self):
+ pass
+ class D(object):
+ def __del__(self):
+ pass
+ def fb():
+ B()
+ def fc():
+ C()
+ def fd():
+ D()
+ a = self.RPythonAnnotator()
+ a.build_types(fc, [])
+ a.build_types(fd, [])
+ py.test.raises(AnnotatorError, a.build_types, fb, [])
+
def g(n):
return [0, 1, 2, n]
diff --git a/rpython/conftest.py b/rpython/conftest.py
--- a/rpython/conftest.py
+++ b/rpython/conftest.py
@@ -82,7 +82,13 @@
return
if (not getattr(item.obj, 'dont_track_allocations', False)
and leakfinder.TRACK_ALLOCATIONS):
- item._pypytest_leaks = leakfinder.stop_tracking_allocations(False)
+ kwds = {}
+ try:
+ kwds['do_collection'] = item.track_allocations_collect
+ except AttributeError:
+ pass
+ item._pypytest_leaks = leakfinder.stop_tracking_allocations(False,
+ **kwds)
else: # stop_tracking_allocations() already called
item._pypytest_leaks = None
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -362,6 +362,16 @@
return func
def must_be_light_finalizer(func):
+ """Mark a __del__ method as being a destructor, calling only a limited
+ set of operations. See pypy/doc/discussion/finalizer-order.rst.
+
+ If you use the same decorator on a class, this class and all its
More information about the pypy-commit
mailing list