Author: Stefan Beyer <[email protected]>
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"
+
[email protected]_api([], api.PyObject)
+def PyPy_Crash1(space):
+ 1/0
+
[email protected]_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
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit