[PyQt] Re: SIP bug with object lifetime
Giovanni Bajo
rasky at develer.com
Mon May 14 11:04:07 BST 2007
On 5/14/2007 11:01 AM, Phil Thompson wrote:
>>>>>> =========================================
>>>>>> from qt import *
>>>>>> import weakref
>>>>>>
>>>>>> app = QApplication([])
>>>>>>
>>>>>> ql = QListView(None)
>>>>>> viewport = ql.viewport()
>>>>>>
>>>>>> o = QObject(viewport)
>>>>>> o.xxx = viewport # bug-trigger!
>>>>>>
>>>>>> destroyed = []
>>>>>> def cb(wr):
>>>>>> destroyed.append(1)
>>>>>> wr = weakref.ref(o, cb)
>>>>>>
>>>>>> del o
>>>>>> del viewport
>>>>>> assert not destroyed, "object destroyed too early #1!"
>>>>>>
>>>>>> import gc
>>>>>> gc.collect()
>>>>>> assert not destroyed, "object destroyed too early #2!"
>>>>>>
>>>>>> del ql
>>>>>> import gc
>>>>>> gc.collect()
>>>>>> assert destroyed, "object never destroyed!"
>>>>>> =========================================
>>>>>> Traceback (most recent call last):
>>>>>> File "pyqtbug19.py", line 25, in ?
>>>>>> assert not destroyed, "object destroyed too early #2!"
>>>>>> AssertionError: object destroyed too early #2!
>>>>>>
>>>>>>
>>>>>> This happens with latest PyQt and SIP official releases (3.17.1 and
>>>>>> 4.6). The line that seems to trigger the bug is the one marked with a
>>>>>> comment.
>>>>> This behaves as I would expect - ie. it's a missing feature rather than
>>>>> a bug.
>>>>>
>>>>> Although ql is the parent of viewport, Python doesn't know that and
>>>>> there is no hidden extra reference to viewport to keep it alive when
>>>>> collect() is run.
>>>> But the problem here is that "o" is collected. o is a QObject whose
>>>> lifetime should be transferred to C++ (given that it has a non-NULL
>>>> parent, right?).
>>> But that parent is owned by Python. When viewpoint (Python) goes,
>>> viewpoint (C++) goes, which takes o (C++), which takes o (Python).
>> Are you saying that "viewport" is owned by Python?
>
> I was but I was just in the process of replying to myself to say I was talking
> rubbish.
>
> Ownership these days covers a couple of things that are actually applied
> separately. One thing is the responsibility to call the C++ dtor, the other
> thing is the associations maintained for the cyclic garbage collector.
>
> When the result of viewport() is wrapped (assuming this is the first call)
> then C++ has the responsibility for calling the dtor. However as far as
> Python is concerned there is no gc association with any other Python object.
>
> "Association" is a parent/child relationship and the parent has an (internally
> maintained) Python reference to the child.
>
> When o is wrapped C++ is responsible for the dtor. A gc association is also
> made with viewport, ie. the viewport Python object has a reference to the o
> Python object. Obviously o is given a reference to viewport via the xxx
> attribute, so creating the cycle.
>
> When o and viewport are del'ed and collect() is run the cycle is broken and
> the o and viewport Python objects garbage collected. The o and viewport C++
> instances are left untouched.
OK, this clears up everything but why both objects manage to survive
even to a gc.collect() call if the "xxx" attribute does not exist.
> If the new /Transfer/ function annotation were to be applied to viewport()
> then a gc association would be established between viewport and ql with the
> ql Python object having a reference to the viewport Python object. This would
> prevent viewport (and also o) from being garbage collected until ql was.
What about queryList()/findChildren()? In my original code, the
C++-allocated instance was accessed through a queryList() call. It's
basically the same (getting a reference to a C++ instance never seen
before by Python), but I guess the annotation won't be enough...
I wonder: can't you just apply it automatically to all cases where you
create a wrapper for an already existing C++ object that was never
wrapped before? How can't that be wrong?
>> (PS: what about a debugging function in sip which tells who owns who?
>> sip.owner(foo) which returns a sip.voidptr() to the owner if it's C++, or
>> None if it's owned by Python. It would make debugging of situations like
>> this a little easier... using weakref/SIGNAL(destroyed) can bring other
>> bugs into the table and makes things more confusing)
>
> Would you want to use the value? Or would something like sip.debug(foo) that
> displayed lots of stuff to stdout be better?
I was thinking it just for debugging purposes, so sip.debug(foo) should
be fine.
--
Giovanni Bajo
More information about the PyQt
mailing list