[PyQt] Re: SIP bug with object lifetime
Giovanni Bajo
rasky at develer.com
Mon May 14 09:33:41 BST 2007
On 14/05/2007 9.33, 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?
If that was the case, it would be almost impossible to use the .viewport()
method without causing damage (you would have to store a reference to the
Python object). I'm pretty sure that can't be the case... I verified with this
example:
===================================================
from qt import *
import weakref
app = QApplication([])
ql = QListView(None)
viewport = ql.viewport()
def cbpy(wr):
print "Python destroyed!"
def cbcxx():
print "C++ destroyed!"
QObject.connect(viewport, SIGNAL("destroyed()"), cbcxx)
wr = weakref.ref(viewport, cbpy)
if 0:
import sip
sip.transferback(viewport)
del viewport
app.processEvents()
print "exiting"
===================================================
Without the call to transferback, I get:
Python destroyed!
exiting
C++ destroyed!
which means that viewport is owned by C++, since the Python reference didn't
bring down the C++ reference. If I change the ownership to Python using the
explicit transferback, I get this:
Python destroyed!
C++ destroyed!
segfault
which looks correct, since the C++ code probably crashes as soon as the first
messages are dispatched, because it expects the viewport to be alive.
(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)
>> And why is this behaviour triggered *only* when I add the "xxx" attribute
>> to o? If I don't do that, everything looks right.
>
> That confuses me a little bit too. It may be a case of timing - objects are
> not guaranteed to be garbage collected immediately their reference count
> reaches 0.
I'll note that, without the "xxx" attribute, the whole example works as
expected: even after the explicit gc.collect() call (which is guaranteed to
collect *everything*), neither "o" nor "viewport" are collected, which is what
I was expecting in the first place.
--
Giovanni Bajo
More information about the PyQt
mailing list