Author: Stefan Beyer <h...@sbeyer.at>
Branch: cpyext-gc-cycle
Changeset: r95606:9e5001a6604b
Date: 2018-07-05 15:54 +0200
http://bitbucket.org/pypy/pypy/changeset/9e5001a6604b/

Log:    Implemented pyobj_list for rawrefcount (to be used in cpyext tests)
        Added own cpyext test file for GC-related tests

diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -1300,7 +1300,8 @@
                 ctypes.c_void_p)
 
     # initialize the pyobj_list for the gc
-    space.fromcache(State).C._PyPy_InitPyObjList()
+    pyobj_list = space.fromcache(State).C._PyPy_InitPyObjList()
+    rawrefcount._init_pyobj_list(pyobj_list)
 
     # we need to call this *after* the init code above, because it might
     # indirectly call some functions which are attached to pypyAPI (e.g., we
diff --git a/pypy/module/cpyext/state.py b/pypy/module/cpyext/state.py
--- a/pypy/module/cpyext/state.py
+++ b/pypy/module/cpyext/state.py
@@ -59,28 +59,23 @@
     def setup_rawrefcount(self):
         space = self.space
         if not self.space.config.translating:
-            from pypy.module.cpyext.api import PyGC_HeadPtr
             def dealloc_trigger():
-                from pypy.module.cpyext.pyobject import PyObject, decref
+                from pypy.module.cpyext.pyobject import PyObject, decref, cts
                 print 'dealloc_trigger...'
                 while True:
                     ob = rawrefcount.next_dead(PyObject)
                     if not ob:
                         break
-                    print 'deallocating PyObject', ob
+                    pto = ob.c_ob_type
+                    name = rffi.charp2str(cts.cast('char*', pto.c_tp_name))
+                    print 'deallocating PyObject', ob, 'of type', name
                     decref(space, ob)
                 print 'dealloc_trigger DONE'
                 return "RETRY"
             def tp_traverse(obj_addr, callback, args):
                 # TODO: implement
                 pass
-            # Warning: This list ist different than the list actually used
-            # by the extension modules (see _PyPy_InitPyObjList).
-            pyobj_list = lltype.malloc(PyGC_HeadPtr.TO,
-                                       flavor='raw', immortal=True, zero=True)
-            pyobj_list.c_gc_next = rffi.cast(rffi.VOIDP, pyobj_list);
-            pyobj_list.c_gc_next = rffi.cast(rffi.VOIDP, pyobj_list);
-            rawrefcount.init(dealloc_trigger, tp_traverse, pyobj_list)
+            rawrefcount.init(dealloc_trigger, tp_traverse)
         else:
             if space.config.translation.gc == "boehm":
                 action = BoehmPyObjDeallocAction(space)
diff --git a/pypy/module/cpyext/test/test_cpyext_gc.py 
b/pypy/module/cpyext/test/test_cpyext_gc.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/cpyext/test/test_cpyext_gc.py
@@ -0,0 +1,801 @@
+import sys
+import weakref
+
+import pytest
+
+from pypy.tool.cpyext.extbuild import (
+    SystemCompilationInfo, HERE, get_sys_info_app)
+from pypy.interpreter.gateway import unwrap_spec, interp2app
+from rpython.rtyper.lltypesystem import lltype, ll2ctypes
+from pypy.module.cpyext import api
+from pypy.module.cpyext.state import State
+from rpython.tool.identity_dict import identity_dict
+from rpython.tool import leakfinder
+from rpython.rlib import rawrefcount
+from rpython.tool.udir import udir
+
+only_pypy ="config.option.runappdirect and '__pypy__' not in 
sys.builtin_module_names"
+
+@api.cpython_api([], api.PyObject)
+def PyPy_Crash1(space):
+    1/0
+
+@api.cpython_api([], lltype.Signed, error=-1)
+def PyPy_Crash2(space):
+    1/0
+
+class SpaceCompiler(SystemCompilationInfo):
+    """Extension compiler for regular (untranslated PyPy) mode"""
+    def __init__(self, space, *args, **kwargs):
+        self.space = space
+        SystemCompilationInfo.__init__(self, *args, **kwargs)
+
+    def load_module(self, mod, name):
+        space = self.space
+        api.load_extension_module(space, mod, name)
+        return space.getitem(
+            space.sys.get('modules'), space.wrap(name))
+
+
+def get_cpyext_info(space):
+    from pypy.module.imp.importing import get_so_extension
+    state = space.fromcache(State)
+    api_library = state.api_lib
+    if sys.platform == 'win32':
+        libraries = [api_library]
+        # '%s' undefined; assuming extern returning int
+        compile_extra = ["/we4013"]
+        # prevent linking with PythonXX.lib
+        w_maj, w_min = space.fixedview(space.sys.get('version_info'), 5)[:2]
+        link_extra = ["/NODEFAULTLIB:Python%d%d.lib" %
+            (space.int_w(w_maj), space.int_w(w_min))]
+    else:
+        libraries = []
+        if sys.platform.startswith('linux'):
+            compile_extra = [
+                "-Werror", "-g", "-O0", "-Wp,-U_FORTIFY_SOURCE", "-fPIC"]
+            link_extra = ["-g"]
+        else:
+            compile_extra = link_extra = None
+    return SpaceCompiler(space,
+        builddir_base=udir,
+        include_extra=api.include_dirs,
+        compile_extra=compile_extra,
+        link_extra=link_extra,
+        extra_libs=libraries,
+        ext=get_so_extension(space))
+
+
+def freeze_refcnts(self):
+    rawrefcount._dont_free_any_more()
+    return #ZZZ
+    state = self.space.fromcache(RefcountState)
+    self.frozen_refcounts = {}
+    for w_obj, obj in state.py_objects_w2r.iteritems():
+        self.frozen_refcounts[w_obj] = obj.c_ob_refcnt
+    #state.print_refcounts()
+    self.frozen_ll2callocations = set(ll2ctypes.ALLOCATED.values())
+
+class LeakCheckingTest(object):
+    """Base class for all cpyext tests."""
+    spaceconfig = dict(usemodules=['cpyext', 'thread', 'struct', 'array',
+                                   'itertools', 'time', 'binascii',
+                                   'micronumpy', 'mmap'
+                                   ])
+
+    enable_leak_checking = True
+
+    @staticmethod
+    def cleanup_references(space):
+        return #ZZZ
+        state = space.fromcache(RefcountState)
+
+        import gc; gc.collect()
+        # Clear all lifelines, objects won't resurrect
+        for w_obj, obj in state.lifeline_dict._dict.items():
+            if w_obj not in state.py_objects_w2r:
+                state.lifeline_dict.set(w_obj, None)
+            del obj
+        import gc; gc.collect()
+
+
+        for w_obj in state.non_heaptypes_w:
+            w_obj.c_ob_refcnt -= 1
+        state.non_heaptypes_w[:] = []
+        state.reset_borrowed_references()
+
+    def check_and_print_leaks(self):
+        rawrefcount._collect()
+        # check for sane refcnts
+        import gc
+
+        if 1:  #ZZZ  not self.enable_leak_checking:
+            leakfinder.stop_tracking_allocations(check=False)
+            return False
+
+        leaking = False
+        state = self.space.fromcache(RefcountState)
+        gc.collect()
+        lost_objects_w = identity_dict()
+        lost_objects_w.update((key, None) for key in 
self.frozen_refcounts.keys())
+
+        for w_obj, obj in state.py_objects_w2r.iteritems():
+            base_refcnt = self.frozen_refcounts.get(w_obj)
+            delta = obj.c_ob_refcnt
+            if base_refcnt is not None:
+                delta -= base_refcnt
+                lost_objects_w.pop(w_obj)
+            if delta != 0:
+                leaking = True
+                print >>sys.stderr, "Leaking %r: %i references" % (w_obj, 
delta)
+                try:
+                    weakref.ref(w_obj)
+                except TypeError:
+                    lifeline = None
+                else:
+                    lifeline = state.lifeline_dict.get(w_obj)
+                if lifeline is not None:
+                    refcnt = lifeline.pyo.c_ob_refcnt
+                    if refcnt > 0:
+                        print >>sys.stderr, "\tThe object also held by C code."
+                    else:
+                        referrers_repr = []
+                        for o in gc.get_referrers(w_obj):
+                            try:
+                                repr_str = repr(o)
+                            except TypeError as e:
+                                repr_str = "%s (type of o is %s)" % (str(e), 
type(o))
+                            referrers_repr.append(repr_str)
+                        referrers = ", ".join(referrers_repr)
+                        print >>sys.stderr, "\tThe object is referenced by 
these objects:", \
+                                referrers
+        for w_obj in lost_objects_w:
+            print >>sys.stderr, "Lost object %r" % (w_obj, )
+            leaking = True
+        # the actual low-level leak checking is done by pypy.tool.leakfinder,
+        # enabled automatically by pypy.conftest.
+        return leaking
+
+class AppTestApi(LeakCheckingTest):
+    def setup_class(cls):
+        from rpython.rlib.clibffi import get_libc_name
+        if cls.runappdirect:
+            cls.libc = get_libc_name()
+        else:
+            cls.w_libc = cls.space.wrap(get_libc_name())
+
+    def setup_method(self, meth):
+        if not self.runappdirect:
+            freeze_refcnts(self)
+
+    def teardown_method(self, meth):
+        if self.runappdirect:
+            return
+        self.space.getexecutioncontext().cleanup_cpyext_state()
+        self.cleanup_references(self.space)
+        # XXX: like AppTestCpythonExtensionBase.teardown_method:
+        # find out how to disable check_and_print_leaks() if the
+        # test failed
+        assert not self.check_and_print_leaks(), (
+            "Test leaks or loses object(s).  You should also check if "
+            "the test actually passed in the first place; if it failed "
+            "it is likely to reach this place.")
+
+
+def _unwrap_include_dirs(space, w_include_dirs):
+    if w_include_dirs is None:
+        return None
+    else:
+        return [space.str_w(s) for s in space.listview(w_include_dirs)]
+
+def debug_collect(space):
+    rawrefcount._collect()
+
+class AppTestCpythonExtensionBase(LeakCheckingTest):
+
+    def setup_class(cls):
+        space = cls.space
+        cls.w_here = space.wrap(str(HERE))
+        cls.w_udir = space.wrap(str(udir))
+        cls.w_runappdirect = space.wrap(cls.runappdirect)
+        if not cls.runappdirect:
+            cls.sys_info = get_cpyext_info(space)
+            space.getbuiltinmodule("cpyext")
+            # 'import os' to warm up reference counts
+            w_import = space.builtin.getdictvalue(space, '__import__')
+            space.call_function(w_import, space.wrap("os"))
+            #state = cls.space.fromcache(RefcountState) ZZZ
+            #state.non_heaptypes_w[:] = []
+            cls.w_debug_collect = space.wrap(interp2app(debug_collect))
+        else:
+            def w_import_module(self, name, init=None, body='', filename=None,
+                    include_dirs=None, PY_SSIZE_T_CLEAN=False):
+                from extbuild import get_sys_info_app
+                sys_info = get_sys_info_app(self.udir)
+                return sys_info.import_module(
+                    name, init=init, body=body, filename=filename,
+                    include_dirs=include_dirs,
+                    PY_SSIZE_T_CLEAN=PY_SSIZE_T_CLEAN)
+            cls.w_import_module = w_import_module
+
+            def w_import_extension(self, modname, functions, prologue="",
+                include_dirs=None, more_init="", PY_SSIZE_T_CLEAN=False):
+                from extbuild import get_sys_info_app
+                sys_info = get_sys_info_app(self.udir)
+                return sys_info.import_extension(
+                    modname, functions, prologue=prologue,
+                    include_dirs=include_dirs, more_init=more_init,
+                    PY_SSIZE_T_CLEAN=PY_SSIZE_T_CLEAN)
+            cls.w_import_extension = w_import_extension
+
+            def w_compile_module(self, name,
+                    source_files=None, source_strings=None):
+                from extbuild import get_sys_info_app
+                sys_info = get_sys_info_app(self.udir)
+                return sys_info.compile_extension_module(name,
+                    source_files=source_files, source_strings=source_strings)
+            cls.w_compile_module = w_compile_module
+
+            def w_load_module(self, mod, name):
+                from extbuild import get_sys_info_app
+                sys_info = get_sys_info_app(self.udir)
+                return sys_info.load_module(mod, name)
+            cls.w_load_module = w_load_module
+
+            def w_debug_collect(self):
+                import gc
+                gc.collect()
+                gc.collect()
+                gc.collect()
+            cls.w_debug_collect = w_debug_collect
+
+
+    def record_imported_module(self, name):
+        """
+        Record a module imported in a test so that it can be cleaned up in
+        teardown before the check for leaks is done.
+
+        name gives the name of the module in the space's sys.modules.
+        """
+        self.imported_module_names.append(name)
+
+    def setup_method(self, func):
+        if self.runappdirect:
+            return
+
+        @unwrap_spec(name='text')
+        def compile_module(space, name,
+                           w_source_files=None,
+                           w_source_strings=None):
+            """
+            Build an extension module linked against the cpyext api library.
+            """
+            if not space.is_none(w_source_files):
+                source_files = space.listview_bytes(w_source_files)
+            else:
+                source_files = None
+            if not space.is_none(w_source_strings):
+                source_strings = space.listview_bytes(w_source_strings)
+            else:
+                source_strings = None
+            pydname = self.sys_info.compile_extension_module(
+                name,
+                source_files=source_files,
+                source_strings=source_strings)
+
+            # hackish, but tests calling compile_module() always end up
+            # importing the result
+            self.record_imported_module(name)
+
+            return space.wrap(pydname)
+
+        @unwrap_spec(name='text', init='text_or_none', body='text',
+                     filename='fsencode_or_none', PY_SSIZE_T_CLEAN=bool)
+        def import_module(space, name, init=None, body='',
+                          filename=None, w_include_dirs=None,
+                          PY_SSIZE_T_CLEAN=False):
+            include_dirs = _unwrap_include_dirs(space, w_include_dirs)
+            w_result = self.sys_info.import_module(
+                name, init, body, filename, include_dirs, PY_SSIZE_T_CLEAN)
+            self.record_imported_module(name)
+            return w_result
+
+
+        @unwrap_spec(mod='text', name='text')
+        def load_module(space, mod, name):
+            return self.sys_info.load_module(mod, name)
+
+        @unwrap_spec(modname='text', prologue='text',
+                             more_init='text', PY_SSIZE_T_CLEAN=bool)
+        def import_extension(space, modname, w_functions, prologue="",
+                             w_include_dirs=None, more_init="", 
PY_SSIZE_T_CLEAN=False):
+            functions = space.unwrap(w_functions)
+            include_dirs = _unwrap_include_dirs(space, w_include_dirs)
+            w_result = self.sys_info.import_extension(
+                modname, functions, prologue, include_dirs, more_init,
+                PY_SSIZE_T_CLEAN)
+            self.record_imported_module(modname)
+            return w_result
+
+        # A list of modules which the test caused to be imported (in
+        # self.space).  These will be cleaned up automatically in teardown.
+        self.imported_module_names = []
+
+        wrap = self.space.wrap
+        self.w_compile_module = wrap(interp2app(compile_module))
+        self.w_load_module = wrap(interp2app(load_module))
+        self.w_import_module = wrap(interp2app(import_module))
+        self.w_import_extension = wrap(interp2app(import_extension))
+
+        # create the file lock before we count allocations
+        self.space.call_method(self.space.sys.get("stdout"), "flush")
+
+        freeze_refcnts(self)
+        #self.check_and_print_leaks()
+
+    def unimport_module(self, name):
+        """
+        Remove the named module from the space's sys.modules.
+        """
+        w_modules = self.space.sys.get('modules')
+        w_name = self.space.wrap(name)
+        self.space.delitem(w_modules, w_name)
+
+    def teardown_method(self, func):
+        if self.runappdirect:
+            return
+        for name in self.imported_module_names:
+            self.unimport_module(name)
+        self.space.getexecutioncontext().cleanup_cpyext_state()
+        self.cleanup_references(self.space)
+        # XXX: find out how to disable check_and_print_leaks() if the
+        # test failed...
+        assert not self.check_and_print_leaks(), (
+            "Test leaks or loses object(s).  You should also check if "
+            "the test actually passed in the first place; if it failed "
+            "it is likely to reach this place.")
+
+def collect(space):
+    import gc
+    rawrefcount._collect()
+    gc.collect(2)
+
+def print_pyobj_list(space):
+    rawrefcount._print_pyobj_list()
+
+class AppTestCpythonExtensionCycleGC(AppTestCpythonExtensionBase):
+
+    def setup_method(self, func):
+        if self.runappdirect:
+            return
+
+        @unwrap_spec(methods='text')
+        def import_cycle_module(space, methods):
+            init = """
+            if (Py_IsInitialized()) {
+                PyObject* m;
+                if (PyType_Ready(&CycleType) < 0)
+                    return;
+                m = Py_InitModule("cycle", module_methods);
+                if (m == NULL)
+                    return;
+                Py_INCREF(&CycleType);
+                PyModule_AddObject(m, "Cycle", (PyObject *)&CycleType);
+            }
+            """
+            body = """
+            #include <Python.h>
+            #include "structmember.h"
+            typedef struct {
+                PyObject_HEAD
+                PyObject *next;
+                PyObject *val;
+            } Cycle;
+            static PyTypeObject CycleType;
+            static int Cycle_traverse(Cycle *self, visitproc visit, void *arg)
+            {
+                int vret;
+                if (self->next) {
+                    vret = visit(self->next, arg);
+                    if (vret != 0)
+                        return vret;
+                }
+                if (self->val) {
+                    vret = visit(self->val, arg);
+                    if (vret != 0)
+                        return vret;
+                }
+                return 0;
+            }
+            static int Cycle_clear(Cycle *self)
+            {
+                PyObject *tmp;
+                tmp = self->next;
+                self->next = NULL;
+                Py_XDECREF(tmp);
+                tmp = self->val;
+                self->val = NULL;
+                Py_XDECREF(tmp);
+                return 0;
+            }
+            static void Cycle_dealloc(Cycle* self)
+            {
+                Cycle_clear(self);
+                Py_TYPE(self)->tp_free((PyObject*)self);
+            }
+            static PyObject* Cycle_new(PyTypeObject *type, PyObject *args,
+                                       PyObject *kwds)
+            {
+                Cycle *self;
+                self = (Cycle *)type->tp_alloc(type, 0);
+                if (self != NULL) {
+                    self->next = PyString_FromString("");
+                    if (self->next == NULL) {
+                        Py_DECREF(self);
+                        return NULL;
+                    }
+                }
+                PyObject_GC_Track(self);
+                return (PyObject *)self;
+            }
+            static int Cycle_init(Cycle *self, PyObject *args, PyObject *kwds)
+            {
+                PyObject *next=NULL, *tmp;
+                static char *kwlist[] = {"next", NULL};
+                if (! PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist,
+                                                  &next))
+                    return -1;
+                if (next) {
+                    tmp = self->next;
+                    Py_INCREF(next);
+                    self->next = next;
+                    Py_XDECREF(tmp);
+                }
+                return 0;
+            }
+            static PyMemberDef Cycle_members[] = {
+                {"next", T_OBJECT_EX, offsetof(Cycle, next), 0, "next"},
+                {"val", T_OBJECT_EX, offsetof(Cycle, val), 0, "val"},
+                {NULL}  /* Sentinel */
+            };
+            static PyMethodDef Cycle_methods[] = {
+                {NULL}  /* Sentinel */
+            };
+            static PyTypeObject CycleType = {
+                PyVarObject_HEAD_INIT(NULL, 0)
+                "Cycle.Cycle",             /* tp_name */
+                sizeof(Cycle),             /* tp_basicsize */
+                0,                         /* tp_itemsize */
+                (destructor)Cycle_dealloc, /* tp_dealloc */
+                0,                         /* tp_print */
+                0,                         /* tp_getattr */
+                0,                         /* tp_setattr */
+                0,                         /* tp_compare */
+                0,                         /* tp_repr */
+                0,                         /* tp_as_number */
+                0,                         /* tp_as_sequence */
+                0,                         /* tp_as_mapping */
+                0,                         /* tp_hash */
+                0,                         /* tp_call */
+                0,                         /* tp_str */
+                0,                         /* tp_getattro */
+                0,                         /* tp_setattro */
+                0,                         /* tp_as_buffer */
+                Py_TPFLAGS_DEFAULT |
+                    Py_TPFLAGS_BASETYPE |
+                    Py_TPFLAGS_HAVE_GC,    /* tp_flags */
+                "Cycle objects",           /* tp_doc */
+                (traverseproc)Cycle_traverse,   /* tp_traverse */
+                (inquiry)Cycle_clear,           /* tp_clear */
+                0,                         /* tp_richcompare */
+                0,                         /* tp_weaklistoffset */
+                0,                         /* tp_iter */
+                0,                         /* tp_iternext */
+                Cycle_methods,             /* tp_methods */
+                Cycle_members,             /* tp_members */
+                0,                         /* tp_getset */
+                0,                         /* tp_base */
+                0,                         /* tp_dict */
+                0,                         /* tp_descr_get */
+                0,                         /* tp_descr_set */
+                0,                         /* tp_dictoffset */
+                (initproc)Cycle_init,      /* tp_init */
+                0,                         /* tp_alloc */
+                Cycle_new,                 /* tp_new */
+            };
+            """
+            w_result = self.sys_info.import_module("cycle", init,
+                                                   body + methods,
+                                                   None, None, False)
+            return w_result
+
+        self.imported_module_names = []
+
+        wrap = self.space.wrap
+        self.w_import_cycle_module = wrap(interp2app(import_cycle_module))
+        self.w_collect = wrap(interp2app(collect))
+        self.w_print_pyobj_list = wrap(interp2app(print_pyobj_list))
+
+    # def test_free_self_reference_cycle_child_pypyobj(self):
+    #     cycle = self.import_cycle_module("""
+    #         static Cycle *c;
+    #         static PyObject * Cycle_cc(Cycle *self, PyObject *val)
+    #         {
+    #             c = PyObject_GC_New(Cycle, &CycleType);
+    #             if (c == NULL)
+    #                 return NULL;
+    #             Py_INCREF(val);
+    #             c->val = val;                // set value
+    #             Py_INCREF(c);
+    #             c->next = (PyObject *)c;     // create self reference
+    #             Py_INCREF(Py_None);
+    #             return Py_None;
+    #         }
+    #         static PyObject * Cycle_cd(Cycle *self)
+    #         {
+    #             Py_DECREF(c);                // throw cycle away
+    #             Py_INCREF(Py_None);
+    #             return Py_None;
+    #         }
+    #         static PyMethodDef module_methods[] = {
+    #             {"createCycle", (PyCFunction)Cycle_cc, METH_OLDARGS, ""},
+    #             {"discardCycle", (PyCFunction)Cycle_cd, METH_NOARGS, ""},
+    #             {NULL}  /* Sentinel */
+    #         };
+    #         """)
+    #
+    #     class Example(object):
+    #         del_called = -1
+    #
+    #         def __init__(self, val):
+    #             self.val = val
+    #             Example.del_called = 0
+    #
+    #         def __del__(self):
+    #             Example.del_called = self.val
+    #
+    #     # don't keep any reference in pypy
+    #     cycle.createCycle(Example(42))
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     cycle.discardCycle()
+    #     self.collect()
+    #     assert Example.del_called == 42
+    #
+    #     # keep a temporary reference in pypy
+    #     e = Example(43)
+    #     cycle.createCycle(e)
+    #     cycle.discardCycle()
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     e = None
+    #     self.collect()
+    #     assert Example.del_called == 43
+    #
+    #     # keep a reference in pypy, free afterwards
+    #     e = Example(44)
+    #     cycle.createCycle(e)
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     e = None
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     cycle.discardCycle()
+    #     self.collect()
+    #     assert Example.del_called == 44
+    #
+    # def test_free_self_reference_cycle_parent_pypyobj(self):
+    #     # create and return a second object which references the cycle, 
because
+    #     # otherwise we will end up with a cycle that spans across cpy/pypy,
+    #     # which we don't want to test here
+    #     cycle = self.import_cycle_module("""
+    #         static PyObject * Cycle_cc(Cycle *self, PyObject *val)
+    #         {
+    #             Cycle *c = PyObject_GC_New(Cycle, &CycleType);
+    #             if (c == NULL)
+    #                 return NULL;
+    #             Cycle *c2 = PyObject_GC_New(Cycle, &CycleType);
+    #             if (c2 == NULL)
+    #                 return NULL;
+    #             Py_INCREF(val);
+    #             c2->val = val;                // set value
+    #             Py_INCREF(c2);
+    #             c2->next = (PyObject *)c2;    // create self reference
+    #             c->next = (PyObject *)c2;
+    #             return (PyObject *)c;         // return other object
+    #         }
+    #         static PyMethodDef module_methods[] = {
+    #             {"createCycle", (PyCFunction)Cycle_cc, METH_OLDARGS, ""},
+    #             {NULL}  /* Sentinel */
+    #         };
+    #         """)
+    #
+    #     class Example(object):
+    #         del_called = -1
+    #
+    #         def __init__(self, val):
+    #             self.val = val
+    #             Example.del_called = 0
+    #
+    #         def __del__(self):
+    #             Example.del_called = self.val
+    #
+    #     c = cycle.createCycle(Example(42))
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     c = None
+    #     self.collect()
+    #     assert Example.del_called == 42
+    #
+    # def test_free_simple_cycle_child_pypyobj(self):
+    #     cycle = self.import_cycle_module("""
+    #         static Cycle *c;
+    #         static PyObject * Cycle_cc(Cycle *self, PyObject *val)
+    #         {
+    #             c = PyObject_GC_New(Cycle, &CycleType);
+    #             if (c == NULL)
+    #                 return NULL;
+    #             Cycle *c2 = PyObject_GC_New(Cycle, &CycleType);
+    #             if (c2 == NULL)
+    #                 return NULL;
+    #             Py_INCREF(val);
+    #             c->val = val;                // set value
+    #             c->next = (PyObject *)c2;
+    #             Py_INCREF(c);
+    #             c2->next = (PyObject *)c;    // simple cycle across two 
objects
+    #             Py_INCREF(Py_None);
+    #             return Py_None;
+    #         }
+    #         static PyObject * Cycle_cd(Cycle *self)
+    #         {
+    #             Py_DECREF(c);                // throw cycle away
+    #             Py_INCREF(Py_None);
+    #             return Py_None;
+    #         }
+    #         static PyMethodDef module_methods[] = {
+    #             {"createCycle", (PyCFunction)Cycle_cc, METH_OLDARGS, ""},
+    #             {"discardCycle", (PyCFunction)Cycle_cd, METH_NOARGS, ""},
+    #             {NULL}  /* Sentinel */
+    #         };
+    #         """)
+    #
+    #     class Example(object):
+    #         del_called = -1
+    #
+    #         def __init__(self, val):
+    #             self.val = val
+    #             Example.del_called = 0
+    #
+    #         def __del__(self):
+    #             Example.del_called = self.val
+    #
+    #     # don't keep any reference in pypy
+    #     cycle.createCycle(Example(42))
+    #     self.collect()
+    #     cycle.discardCycle()
+    #     assert Example.del_called == 0
+    #     self.collect()
+    #     assert Example.del_called == 42
+    #
+    #     # keep a temporary reference in pypy
+    #     e = Example(43)
+    #     cycle.createCycle(e)
+    #     cycle.discardCycle()
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     e = None
+    #     self.collect()
+    #     assert Example.del_called == 43
+    #
+    #     # keep a reference in pypy, free afterwards
+    #     e = Example(44)
+    #     cycle.createCycle(e)
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     e = None
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     cycle.discardCycle()
+    #     self.collect()
+    #     assert Example.del_called == 44
+    #
+    #
+    # def test_free_complex_cycle_child_pypyobj(self):
+    #     cycle = self.import_cycle_module("""
+    #         static PyObject * Cycle_cc(Cycle *self, PyObject *val)
+    #         {
+    #             Cycle *c = PyObject_GC_New(Cycle, &CycleType);
+    #             if (c == NULL)
+    #                 return NULL;
+    #             Cycle *c2 = PyObject_GC_New(Cycle, &CycleType);
+    #             if (c2 == NULL)
+    #                 return NULL;
+    #             Cycle *c3 = PyObject_GC_New(Cycle, &CycleType);
+    #             if (c3 == NULL)
+    #                 return NULL;
+    #             Py_INCREF(val);
+    #             c->val = val;                // set value
+    #             Py_INCREF(val);
+    #             c3->val = val;                // set value
+    #             Py_INCREF(c2);
+    #             c->next = (PyObject *)c2;
+    #             Py_INCREF(c);
+    #             c2->next = (PyObject *)c;    // inner cycle
+    #             Py_INCREF(c3);
+    #             c2->val = (PyObject *)c3;
+    #             Py_INCREF(c);
+    #             c3->next = (PyObject *)c;     // outer cycle
+    #             Py_DECREF(c);
+    #             Py_DECREF(c2);
+    #             Py_DECREF(c3);               // throw all objects away
+    #             Py_INCREF(Py_None);
+    #             return Py_None;
+    #         }
+    #         static PyMethodDef module_methods[] = {
+    #             {"createCycle", (PyCFunction)Cycle_cc, METH_OLDARGS, ""},
+    #             {NULL}  /* Sentinel */
+    #         };
+    #         """)
+    #
+    #     class Example(object):
+    #         del_called = -1
+    #
+    #         def __init__(self, val):
+    #             self.val = val
+    #             Example.del_called = 0
+    #
+    #         def __del__(self):
+    #             Example.del_called = self.val
+    #
+    #     # don't keep any reference in pypy
+    #     cycle.createCycle(Example(42))
+    #     assert Example.del_called == 0
+    #     self.collect()
+    #     assert Example.del_called == 42
+    #
+    #     # keep a temporary reference in pypy
+    #     e = Example(43)
+    #     cycle.createCycle(e)
+    #     e = None
+    #     assert Example.del_called == 0
+    #     self.collect()
+    #     assert Example.del_called == 43
+    #
+    #     # keep a reference in pypy, free afterwards
+    #     e = Example(44)
+    #     cycle.createCycle(e)
+    #     self.collect()
+    #     assert Example.del_called == 0
+    #     e = None
+    #     self.collect()
+    #     assert Example.del_called == 44
+
+    def test_objects_in_global_list(self):
+        cycle = self.import_cycle_module("""
+            static PyObject * Cycle_Create(Cycle *self, PyObject *val)
+            {
+                Cycle *c = PyObject_GC_New(Cycle, &CycleType);
+                if (c == NULL)
+                    return NULL;
+                c->next = val;
+                return (PyObject *)c;
+            }
+            static PyMethodDef module_methods[] = {
+                {"create", (PyCFunction)Cycle_Create, METH_OLDARGS, ""},
+                {NULL}  /* Sentinel */
+            };
+            """)
+
+        class Example(object):
+            def __init__(self, val):
+                self.val = val
+
+        c = cycle.create(Example(41))
+
+        self.print_pyobj_list()
+        c = cycle.create(Example(42))
+        self.print_pyobj_list()
+
+        # TODO: fix rawrefcount, so that the Cycle objects are properly added
+        #       to the ALLOCATED list of leakfinder or alternatively not freed
+        #       by collect
diff --git a/rpython/rlib/rawrefcount.py b/rpython/rlib/rawrefcount.py
--- a/rpython/rlib/rawrefcount.py
+++ b/rpython/rlib/rawrefcount.py
@@ -19,6 +19,11 @@
                           ('c_ob_refcnt', lltype.Signed),
                           ('c_ob_pypy_link', lltype.Signed))
 PYOBJ_HDR_PTR = lltype.Ptr(PYOBJ_HDR)
+PYOBJ_GC_HDR = lltype.Struct('PyGC_Head',
+                             ('c_gc_next', rffi.VOIDP),
+                             ('c_gc_prev', rffi.VOIDP),
+                             ('c_gc_refs', lltype.Signed))
+PYOBJ_GC_HDR_PTR = lltype.Ptr(PYOBJ_GC_HDR)
 RAWREFCOUNT_DEALLOC_TRIGGER = lltype.Ptr(lltype.FuncType([], lltype.Void))
 VISIT_FUNCTYPE = lltype.Ptr(lltype.FuncType([PYOBJ_HDR_PTR, rffi.VOIDP],
                                             rffi.INT_real))
@@ -41,16 +46,22 @@
     """
     global _p_list, _o_list, _adr2pypy, _pypy2ob, _pypy2ob_rev
     global _d_list, _dealloc_trigger_callback, _tp_traverse, _pygclist
+    global _pyobj_list
     _p_list = []
     _o_list = []
     _adr2pypy = [None]
     _pypy2ob = {}
     _pypy2ob_rev = {}
     _d_list = []
-    _d_marker = None
     _dealloc_trigger_callback = dealloc_trigger_callback
     _tp_traverse = tp_traverse
-    _pygclist = pyobj_list
+    if pyobj_list is not None:
+        _init_pyobj_list(pyobj_list)
+
+@not_rpython
+def _init_pyobj_list(pyobj_list):
+    global _pyobj_list
+    _pyobj_list = rffi.cast(PYOBJ_GC_HDR_PTR, pyobj_list)
 
 # def init_traverse(traverse_cpy_call):
 #     global _traverse_cpy_call
@@ -214,6 +225,17 @@
         _keepalive_forever.add(to_obj(object, ob))
     del _d_list[:]
 
+@not_rpython
+def _print_pyobj_list():
+    "for tests only"
+    # TODO: change to get_pyobj_list, that returns a list of PyObjects
+    global _pyobj_list
+    print "_print_pyobj_list start!"
+    curr = rffi.cast(PYOBJ_GC_HDR_PTR, _pyobj_list.c_gc_next)
+    while curr != _pyobj_list:
+        print "_print_pyobj_list: ", curr
+        curr = rffi.cast(PYOBJ_GC_HDR_PTR, curr.c_gc_next)
+
 # ____________________________________________________________
 
 
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to