Author: Armin Rigo <[email protected]>
Branch: gc-del
Changeset: r62010:84eb06294ac8
Date: 2013-03-04 16:35 +0100
http://bitbucket.org/pypy/pypy/changeset/84eb06294ac8/
Log: More in-progress.
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
@@ -39,7 +39,7 @@
by the GC when the last reference to the object goes away, like in
CPython. However (like "lightweight finalizers" used to be), all
``__del__()`` methods must only contain simple enough code, and this
- is checked.
+ is checked. This is now called "destructors".
* For any more advanced usage --- in particular for any app-level object
with a __del__ --- we don't use the RPython-level ``__del__()``
@@ -54,13 +54,12 @@
last; nothing can be done with the object any more.
-Lightweight finalizers
-----------------------
+Destructors
+-----------
-A lightweight finalizer is an RPython ``__del__()`` method that is
-called directly by the GC when there is no more reference to an object
-(including from other objects with finalizers). Intended for objects
-that just need to free a block of raw memory or close a file.
+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.
There are restrictions on the kind of code you can put in ``__del__()``,
including all other functions called by it. These restrictions are
@@ -68,11 +67,16 @@
and if you call an external C function, it must be a "safe" function.
(XXX check/implement this)
+If there are several objects with destructors, they are called in a
+random order --- but that should be fine because destructors cannot
+do much anyway.
+
Register_finalizer
------------------
-The interface is made with PyPy in mind, but should be generally useful.
+The interface for full finalizers is made with PyPy in mind, but should
+be generally useful.
``rgc.register_finalizer(obj.finalizer_method)``
@@ -82,9 +86,9 @@
picks a topological ordering (breaking cycles randomly) and enqueues
the objects and their registered finalizer functions in that order.
Finally, when the major collection is done, it calls
- ``rgc.progress_through_finalizer_queue()`` once, unless there is
+ ``rgc.progress_through_finalizer_queue()`` once (unless there is
already a call to ``rgc.progress_through_finalizer_queue()`` in
- progress.
+ progress).
It is only allowed to register one finalizer per object,
but we can cumulate one register_finalizer() and a __del__(). It is
@@ -99,15 +103,16 @@
function remains at the front of the queue, and will be called again
by the next call to ``progress_through_finalizer_queue()``.
-The idea is that the finalizer functions in PyPy either do their clean-up
-immediately (for the case where they are not lightweight finalizers, but
-don't require synchronization), or are postponed to be executed at the
-end of the current bytecode by the interpreter. This is done by writing
-such functions with this logic::
+The idea is that the finalizer functions in PyPy either do their
+clean-up immediately (for the case where destructors are not
+appropriate, but they still don't require complete synchronization with
+the Python code), or are postponed to be executed at the end of the
+current bytecode by the interpreter. This is done by writing such
+functions with this logic::
def finalize(self):
ec = self.space.getexecutioncontext()
- if not ec.running_finalizers:
+ if not ec.running_finalizers_between_bytecodes:
ec.schedule_later_call_to_progress_through_finalizer_queue()
raise FinalizeLater
else:
diff --git a/rpython/memory/gc/base.py b/rpython/memory/gc/base.py
--- a/rpython/memory/gc/base.py
+++ b/rpython/memory/gc/base.py
@@ -62,8 +62,7 @@
def set_query_functions(self, is_varsize, has_gcptr_in_varsize,
is_gcarrayofgcptr,
- getfinalizer,
- getlightfinalizer,
+ getdestructor,
offsets_to_gc_pointers,
fixed_size, varsize_item_sizes,
varsize_offset_to_variable_part,
@@ -75,8 +74,7 @@
has_custom_trace,
get_custom_trace,
fast_path_tracing):
- self.getfinalizer = getfinalizer
- self.getlightfinalizer = getlightfinalizer
+ self.getdestructor = getdestructor
self.is_varsize = is_varsize
self.has_gcptr_in_varsize = has_gcptr_in_varsize
self.is_gcarrayofgcptr = is_gcarrayofgcptr
@@ -138,10 +136,9 @@
# to malloc_varsize(), but always uses malloc_varsize_clear()
size = self.fixed_size(typeid)
- needs_finalizer = bool(self.getfinalizer(typeid))
- finalizer_is_light = bool(self.getlightfinalizer(typeid))
+ has_destructor = bool(self.getdestructor(typeid))
contains_weakptr = self.weakpointer_offset(typeid) >= 0
- assert not (needs_finalizer and contains_weakptr)
+ assert not (has_destructor and contains_weakptr)
if self.is_varsize(typeid):
assert not contains_weakptr
assert not needs_finalizer
@@ -158,8 +155,7 @@
malloc_fixedsize = self.malloc_fixedsize_clear
else:
malloc_fixedsize = self.malloc_fixedsize
- ref = malloc_fixedsize(typeid, size, needs_finalizer,
- finalizer_is_light,
+ ref = malloc_fixedsize(typeid, size, has_destructor,
contains_weakptr)
# lots of cast and reverse-cast around...
return llmemory.cast_ptr_to_adr(ref)
diff --git a/rpython/memory/gc/minimark.py b/rpython/memory/gc/minimark.py
--- a/rpython/memory/gc/minimark.py
+++ b/rpython/memory/gc/minimark.py
@@ -104,9 +104,8 @@
# translation and is statically recorded.
GCFLAG_HAS_SHADOW = first_gcflag << 3
-# The following flag is set temporarily on some objects during a major
-# collection. See pypy/doc/discussion/finalizer-order.txt
-GCFLAG_FINALIZATION_ORDERING = first_gcflag << 4
+# The following flag is set when we call rgc.register_finalizer().
+GCFLAG_HAS_FINALIZER= first_gcflag << 4
# This flag is reserved for RPython.
GCFLAG_EXTRA = first_gcflag << 5
@@ -294,10 +293,9 @@
self.old_rawmalloced_objects = self.AddressStack()
self.rawmalloced_total_size = r_uint(0)
#
- # A list of all objects with finalizers (these are never young).
- self.objects_with_finalizers = self.AddressDeque()
- self.young_objects_with_light_finalizers = self.AddressStack()
- self.old_objects_with_light_finalizers = self.AddressStack()
+ # Two lists of all objects with destructors
+ self.young_objects_with_destructors = self.AddressStack()
+ self.old_objects_with_destructors = self.AddressStack()
#
# Two lists of the objects with weakrefs. No weakref can be an
# old object weakly pointing to a young object: indeed, weakrefs
@@ -467,25 +465,16 @@
def malloc_fixedsize_clear(self, typeid, size,
- needs_finalizer=False,
- is_finalizer_light=False,
+ has_destructor=False,
contains_weakptr=False):
size_gc_header = self.gcheaderbuilder.size_gc_header
totalsize = size_gc_header + size
rawtotalsize = raw_malloc_usage(totalsize)
#
- # If the object needs a finalizer, ask for a rawmalloc.
- # The following check should be constant-folded.
- if needs_finalizer and not is_finalizer_light:
- ll_assert(not contains_weakptr,
- "'needs_finalizer' and 'contains_weakptr' both specified")
- obj = self.external_malloc(typeid, 0, can_make_young=False)
- self.objects_with_finalizers.append(obj)
- #
# If totalsize is greater than nonlarge_max (which should never be
# the case in practice), ask for a rawmalloc. The following check
# should be constant-folded.
- elif rawtotalsize > self.nonlarge_max:
+ if rawtotalsize > self.nonlarge_max:
ll_assert(not contains_weakptr,
"'contains_weakptr' specified for a large object")
obj = self.external_malloc(typeid, 0)
@@ -507,11 +496,12 @@
# Build the object.
llarena.arena_reserve(result, totalsize)
obj = result + size_gc_header
- if is_finalizer_light:
- self.young_objects_with_light_finalizers.append(obj)
self.init_gc_object(result, typeid, flags=0)
#
- # If it is a weakref, record it (check constant-folded).
+ # If it is a weakref or has a destructor, record it
+ # (checks constant-folded).
+ if has_destructor:
+ self.young_objects_with_destructors.append(obj)
if contains_weakptr:
self.young_objects_with_weakrefs.append(obj)
#
@@ -876,7 +866,7 @@
"""
assert self.is_in_nursery(obj)
tid = self.header(obj).tid
- result = (tid & GCFLAG_FINALIZATION_ORDERING != 0)
+ result = (tid & GCFLAG_NO_HEAP_PTRS != 0)
if result:
ll_assert(tid == -42, "bogus header for young obj")
else:
@@ -929,9 +919,6 @@
# the GCFLAG_VISITED should not be set between collections
ll_assert(self.header(obj).tid & GCFLAG_VISITED == 0,
"unexpected GCFLAG_VISITED")
- # the GCFLAG_FINALIZATION_ORDERING should not be set between coll.
- ll_assert(self.header(obj).tid & GCFLAG_FINALIZATION_ORDERING == 0,
- "unexpected GCFLAG_FINALIZATION_ORDERING")
# the GCFLAG_CARDS_SET should not be set between collections
ll_assert(self.header(obj).tid & GCFLAG_CARDS_SET == 0,
"unexpected GCFLAG_CARDS_SET")
@@ -1251,8 +1238,8 @@
# weakrefs' targets.
if self.young_objects_with_weakrefs.non_empty():
self.invalidate_young_weakrefs()
- if self.young_objects_with_light_finalizers.non_empty():
- self.deal_with_young_objects_with_finalizers()
+ if self.young_objects_with_destructors.non_empty():
+ self.deal_with_young_objects_with_destructors()
#
# Clear this mapping.
if self.nursery_objects_shadows.length() > 0:
@@ -1569,16 +1556,18 @@
# with a finalizer and all objects reachable from there (and also
# moves some objects from 'objects_with_finalizers' to
# 'run_finalizers').
- if self.objects_with_finalizers.non_empty():
- self.deal_with_objects_with_finalizers()
+ #if self.objects_with_finalizers.non_empty():
+ # self.deal_with_objects_with_finalizers()
#
self.objects_to_trace.delete()
#
# Weakref support: clear the weak pointers to dying objects
if self.old_objects_with_weakrefs.non_empty():
self.invalidate_old_weakrefs()
- if self.old_objects_with_light_finalizers.non_empty():
- self.deal_with_old_objects_with_finalizers()
+ #
+ # Destructor support
+ if self.old_objects_with_destructors.non_empty():
+ self.deal_with_old_objects_with_destructors()
#
# Walk all rawmalloced objects and free the ones that don't
@@ -1834,24 +1823,23 @@
self.extra_threshold += diff
- # ----------
- # Finalizers
+ # -----------
+ # Destructors
- def deal_with_young_objects_with_finalizers(self):
- """ This is a much simpler version of dealing with finalizers
- and an optimization - we can reasonably assume that those finalizers
- don't do anything fancy and *just* call them. Among other things
- they won't resurrect objects
+ def deal_with_young_objects_with_destructors(self):
+ """We can reasonably assume that destructors don't do anything
+ fancy and *just* call them. Among other things they won't
+ resurrect objects
"""
- while self.young_objects_with_light_finalizers.non_empty():
- obj = self.young_objects_with_light_finalizers.pop()
+ while self.young_objects_with_destructors.non_empty():
+ obj = self.young_objects_with_destructors.pop()
if not self.is_forwarded(obj):
- finalizer = self.getlightfinalizer(self.get_type_id(obj))
- ll_assert(bool(finalizer), "no light finalizer found")
- finalizer(obj, llmemory.NULL)
+ destructor = self.getdestructor(self.get_type_id(obj))
+ ll_assert(bool(destructor), "destructor missing")
+ destructor(obj, llmemory.NULL)
else:
obj = self.get_forwarding_address(obj)
- self.old_objects_with_light_finalizers.append(obj)
+ self.old_objects_with_destructors.append(obj)
def deal_with_old_objects_with_finalizers(self):
""" This is a much simpler version of dealing with finalizers
@@ -1874,6 +1862,7 @@
self.old_objects_with_light_finalizers = new_objects
def deal_with_objects_with_finalizers(self):
+ XXXXXXXXX
# Walk over list of objects with finalizers.
# If it is not surviving, add it to the list of to-be-called
# finalizers and make it survive, to make the finalizer runnable.
diff --git a/rpython/memory/gctypelayout.py b/rpython/memory/gctypelayout.py
--- a/rpython/memory/gctypelayout.py
+++ b/rpython/memory/gctypelayout.py
@@ -22,15 +22,15 @@
# custom tracer (CT), it enumerates the addresses that contain GCREFs.
# It is called with the object as first argument, and the previous
# returned address (or NULL the first time) as the second argument.
- FINALIZER_OR_CT_FUNC = lltype.FuncType([llmemory.Address,
+ DESTRUCTOR_OR_CT_FUNC = lltype.FuncType([llmemory.Address,
llmemory.Address],
llmemory.Address)
- FINALIZER_OR_CT = lltype.Ptr(FINALIZER_OR_CT_FUNC)
+ DESTRUCTOR_OR_CT = lltype.Ptr(DESTRUCTOR_OR_CT_FUNC)
# structure describing the layout of a typeid
TYPE_INFO = lltype.Struct("type_info",
("infobits", lltype.Signed), # combination of the T_xxx consts
- ("finalizer_or_customtrace", FINALIZER_OR_CT),
+ ("destructor_or_customtrace", DESTRUCTOR_OR_CT),
("fixedsize", lltype.Signed),
("ofstoptrs", lltype.Ptr(OFFSETS_TO_GC_PTR)),
hints={'immutable': True},
@@ -77,19 +77,12 @@
infobits = self.get(typeid).infobits
return (infobits & T_IS_GCARRAY_OF_GCPTR) != 0
- def q_finalizer(self, typeid):
+ def q_getdestructor(self, typeid):
typeinfo = self.get(typeid)
- if typeinfo.infobits & T_HAS_FINALIZER:
- return typeinfo.finalizer_or_customtrace
+ if typeinfo.infobits & T_HAS_DESTRUCTOR:
+ return typeinfo.destructor_or_customtrace
else:
- return lltype.nullptr(GCData.FINALIZER_OR_CT_FUNC)
-
- def q_light_finalizer(self, typeid):
- typeinfo = self.get(typeid)
- if typeinfo.infobits & T_HAS_LIGHTWEIGHT_FINALIZER:
- return typeinfo.finalizer_or_customtrace
- else:
- return lltype.nullptr(GCData.FINALIZER_OR_CT_FUNC)
+ return lltype.nullptr(GCData.DESTRUCTOR_OR_CT_FUNC)
def q_offsets_to_gc_pointers(self, typeid):
return self.get(typeid).ofstoptrs
@@ -131,7 +124,7 @@
ll_assert(self.q_has_custom_trace(typeid),
"T_HAS_CUSTOM_TRACE missing")
typeinfo = self.get(typeid)
- return typeinfo.finalizer_or_customtrace
+ return typeinfo.destructor_or_customtrace
def q_fast_path_tracing(self, typeid):
# return True if none of the flags T_HAS_GCPTR_IN_VARSIZE,
@@ -147,8 +140,7 @@
self.q_is_varsize,
self.q_has_gcptr_in_varsize,
self.q_is_gcarrayofgcptr,
- self.q_finalizer,
- self.q_light_finalizer,
+ self.q_getdestructor,
self.q_offsets_to_gc_pointers,
self.q_fixed_size,
self.q_varsize_item_sizes,
@@ -170,9 +162,8 @@
T_IS_GCARRAY_OF_GCPTR = 0x040000
T_IS_WEAKREF = 0x080000
T_IS_RPYTHON_INSTANCE = 0x100000 # the type is a subclass of OBJECT
-T_HAS_FINALIZER = 0x200000
+T_HAS_DESTRUCTOR = 0x200000
T_HAS_CUSTOM_TRACE = 0x400000
-T_HAS_LIGHTWEIGHT_FINALIZER = 0x800000
T_KEY_MASK = intmask(0xFF000000)
T_KEY_VALUE = intmask(0x5A000000) # bug detection only
@@ -199,11 +190,9 @@
kind_and_fptr = builder.special_funcptr_for_type(TYPE)
if kind_and_fptr is not None:
kind, fptr = kind_and_fptr
- info.finalizer_or_customtrace = fptr
- if kind == "finalizer":
- infobits |= T_HAS_FINALIZER
- elif kind == 'light_finalizer':
- infobits |= T_HAS_FINALIZER | T_HAS_LIGHTWEIGHT_FINALIZER
+ info.destructor_or_customtrace = fptr
+ if kind == "destructor":
+ infobits |= T_HAS_DESTRUCTOR
elif kind == "custom_trace":
infobits |= T_HAS_CUSTOM_TRACE
else:
diff --git a/rpython/memory/gcwrapper.py b/rpython/memory/gcwrapper.py
--- a/rpython/memory/gcwrapper.py
+++ b/rpython/memory/gcwrapper.py
@@ -225,7 +225,7 @@
raise RuntimeError(
"a finalizer raised an exception, shouldn't happen")
return llmemory.NULL
- return llhelper(gctypelayout.GCData.FINALIZER_OR_CT, ll_finalizer)
+ return llhelper(gctypelayout.GCData.DESTRUCTOR_OR_CT, ll_finalizer)
def make_custom_trace_funcptr_for_type(self, TYPE):
from rpython.memory.gctransform.support import get_rtti
diff --git a/rpython/memory/test/snippet.py b/rpython/memory/test/snippet.py
--- a/rpython/memory/test/snippet.py
+++ b/rpython/memory/test/snippet.py
@@ -115,8 +115,8 @@
return f
- def test_full_finalizer_order(self):
- res = self.run('full_finalizer_order')
+ def test_finalizer_order(self):
+ res = self.run('finalizer_order')
if res != "ok":
i, summary, msg = res.split('\n')
i = int(i)
diff --git a/rpython/memory/test/test_gc.py b/rpython/memory/test/test_gc.py
--- a/rpython/memory/test/test_gc.py
+++ b/rpython/memory/test/test_gc.py
@@ -153,7 +153,7 @@
res = self.interpret(f, [5])
assert res == 6
- def test_finalizer(self):
+ def test_finalizer_simple(self):
class B(object):
pass
b = B()
@@ -303,7 +303,7 @@
res = self.interpret(f, [])
assert res
- def test_weakref_to_object_with_finalizer(self):
+ def test_weakref_to_object_with_destructor(self):
import weakref
class A(object):
count = 0
@@ -323,6 +323,32 @@
res = self.interpret(f, [])
assert res
+ def test_weakref_to_object_with_finalizer(self):
+ import weakref
+ class A(object):
+ count = 0
+ a = A()
+ class B(object):
+ def __init__(self, ref):
+ rgc.register_finalizer(self.finalizer)
+ def finalizer(self):
+ # when the finalizer is called, the weakref to myself is
+ # still valid in RPython
+ a.count += 100 + (self.ref() is None)
+ def g():
+ b = B()
+ ref = weakref.ref(b)
+ b.ref = ref
+ return b
+ def f():
+ ref = g()
+ llop.gc__collect(lltype.Void)
+ llop.gc__collect(lltype.Void)
+ result = a.count + 10 * (ref() is None)
+ return result
+ res = self.interpret(f, [])
+ assert res == 110
+
def test_bug_1(self):
import weakref
class B(object):
@@ -348,8 +374,10 @@
count = 0
a = A()
class B(object):
- def __del__(self):
- # when __del__ is called, the weakref to c should be dead
+ def __init__(self, ref):
+ rgc.register_finalizer(self.finalizer)
+ def finalizer(self):
+ # when the finalizer is called, the weakref to c should be dead
if self.ref() is None:
a.count += 10 # ok
else:
@@ -371,40 +399,14 @@
res = self.interpret(f, [])
assert res == 11
- def test_weakref_to_object_with_finalizer_ordering(self):
- import weakref
- class A(object):
- count = 0
- a = A()
- class B(object):
- def __del__(self):
- # when __del__ is called, the weakref to myself is still valid
- # in RPython (at least with most GCs; this test might be
- # skipped for specific GCs)
- if self.ref() is self:
- a.count += 10 # ok
- else:
- a.count = 666 # not ok
- def g():
- b = B()
- ref = weakref.ref(b)
- b.ref = ref
- return ref
- def f():
- ref = g()
- llop.gc__collect(lltype.Void)
- llop.gc__collect(lltype.Void)
- result = a.count + (ref() is None)
- return result
- res = self.interpret(f, [])
- assert res == 11
-
def test_weakref_bug_1(self):
import weakref
class A(object):
pass
class B(object):
- def __del__(self):
+ def __init__(self, ref):
+ rgc.register_finalizer(self.finalizer)
+ def finalizer(self):
self.wref().x += 1
def g(a):
b = B()
@@ -453,7 +455,8 @@
def __init__(self):
self.id = b.nextid
b.nextid += 1
- def __del__(self):
+ rgc.register_finalizer(self.finalizer)
+ def finalizer(self):
b.num_deleted += 1
b.all.append(D(b.num_deleted))
class D(object):
@@ -486,14 +489,16 @@
def __init__(self):
self.id = b.nextid
b.nextid += 1
- def __del__(self):
+ rgc.register_finalizer(self.finalizer)
+ def finalizer(self):
+ b.num_deleted += 1
+ self.do_finalizer()
+ def do_finalizer(self):
llop.gc__collect(lltype.Void)
- b.num_deleted += 1
C()
C()
class C(A):
- def __del__(self):
- b.num_deleted += 1
+ def do_finalizer(self):
b.num_deleted_c += 1
def f(x, y):
persistent_a1 = A()
@@ -873,7 +878,8 @@
def __init__(self):
self.id = b.nextid
b.nextid += 1
- def __del__(self):
+ rgc.register_finalizer(self.finalizer)
+ def finalizer(self):
b.num_deleted += 1
def f(x):
a = A()
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -333,7 +333,8 @@
func(obj)
except FinalizeLater:
_finalizer_queue.appendleft((obj, func))
- break
+ return False # interrupted
+ return True # completed
class RegisterFinalizerEntry(ExtRegistryEntry):
_about_ = register_finalizer
@@ -342,8 +343,10 @@
from rpython.annotator import model as annmodel
from rpython.annotator.description import MethodDesc
assert (isinstance(s_method, annmodel.SomePBC) and
- s_method.getKind() is MethodDesc and
- len(s_method.descriptions) == 1)
+ s_method.getKind() is MethodDesc)
+ if len(s_method.descriptions) != 1:
+ raise Exception("rgc.register_finalizer(method): the method must "
+ "not be overridden. Add an indirection if needed")
#
[methoddesc] = s_method.descriptions
key = (register_finalizer, methoddesc.funcdesc, methoddesc.name)
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit