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
_______________________________________________
PyQt mailing list    [email protected]
http://www.riverbankcomputing.com/mailman/listinfo/pyqt

Reply via email to