You need to be careful what you do in a __dealloc__() method. By the time your __dealloc__() method is called, the object may already have been partially destroyed and may not be in a valid state as far as Python is concerned, so you should avoid invoking any Python operations which might touch the object. In particular, don’t call any other methods of the object or do anything which might cause the object to be resurrected. It’s best if you stick to just deallocating C data.But this did not give me the crucial hint: Currently, any references to Python objects may be invalid at the time dealloc is called. This is because Cython generates a tp_clear function for each extension type that calls Py_CLEAR for each object reference.
http://docs.cython.org/src/userguide/special_methods.html?highlight=__dealloc__#finalization-method-dealloc
cdef noclear class Slots:Exempt a single attribute:
cdef Context context
cdef class Slots:This is probably enough of explanation for the Cython experts, but I would still like to explain what happened in my case and a work around for illustration purposes and in the hope, that this will help somebody else at some time.
cdef noclear Context context
from fakep11 import *But the following code will crash when the garbage collector runs:
def works():
ctx = Context()
my_slots = ctx.slots()
while my_slots:
assert ctx.usage_counter == 1
my_slots.pop()
assert ctx.usage_counter == 0
def crashes():This is because a and b refer to each other, creating a cycle, and both refer to a slot, pulling the Slots instance into the object graph of the cycle. For this reason, tp_clear will be called for slots before tp_dealloc is called, leaving tp_dealloc with invalid data.
ctx = Context()
slots = ctx.slots()
a = {"slot": slots[0]}
b = {"slot": slots[1], "cycle": a}
a["cycle"] = b
diff -r f2afc4865bbc fakep11.pyxThat way I have full control over the reference counting and can make sure that context is a valid reference when __dealloc__ is called.
--- a/fakep11.pyx Sat Apr 20 23:43:00 2013 +0200
+++ b/fakep11.pyx Sun Apr 21 23:50:10 2013 +0200
@@ -1,5 +1,10 @@
cimport cython
+cdef extern from "Python.h":
+ ctypedef struct PyObject
+ void Py_INCREF(PyObject *)
+ void Py_DECREF(PyObject *)
+
cdef extern from "fakep11c.h":
ctypedef struct fakep11_context:
@@ -35,17 +40,19 @@
@cython.internal
cdef class Slots:
- cdef Context context
+ cdef PyObject *context
cdef unsigned int nslots
cdef fakep11_slot *slots
def __cinit__(self, Context context):
- self.context = context
+ self.context = <PyObject*> context
+ Py_INCREF(self.context)
self.slots = fakep11_enumerate(context.imp, &self.nslots)
def __dealloc__(self):
print "Slots dealloc"
- fakep11_release_all(self.context.imp, self.slots, self.nslots)
+ fakep11_release_all((<Context>self.context).imp, self.slots, self.nslots)
+ Py_DECREF(self.context)
def as_list(self):
l = [None] * self.nslots
Please find my fake libp11 wrapper example code attached, as well
as a small patch for the documentation documenting the current
status.
-- DYNAmore Gesellschaft fuer Ingenieurdienstleistungen mbH Torsten Landschoff Office Dresden Tel: +49-(0)351-4519587 Fax: +49-(0)351-4519561 mailto:torsten.landschoff@dynamore.de http://www.dynamore.de DYNAmore Gesellschaft für FEM Ingenieurdienstleistungen mbH Registration court: Stuttgart, HRB 733694 Managing director: Prof. Dr. Karl Schweizerhof, Dipl.-Math. Ulrich Franz