Hello Phil,

I believe to have found a subtle bug in PyQt in
combination with the Python garbage collector.

Here is what I think that happens :

- A QObject is constructed in one thread,
  and this QObject construction contains a cyclic
  dependency.

- this construction will not be deleted with
  reference counting, but only when the garbage
  collector is triggered

- now, if the garbage collector starts collecting
  in a different thread then the one that the 
  QObject was constructed in, the QObject gets
  destroyed in that different thread

- when a QObject is destroyed, it sends events to
  its parent object.  these events are then send in
  the wrong thread

- this leads to corruption and/or segmentation faults

To see this happening, please run the attached test 
case with a development build of Qt (because then,
you see the assertion failure).

I have observed this with :

- Qt 4.7.2
- PyQt 4.8.3
- sip 4.12.1

PySide suffers from the same behavior.

Thank you and best regards,

Erik


"""Test the behaviour of the qt bindings in various circumstances.
"""

import unittest

from PyQt4 import QtGui, QtCore

class GarbageCollectionCase( unittest.TestCase ):
    
    def setUp(self):
        self.application = QtGui.QApplication.instance()
        if not self.application:
            import sys
            self.application = QtGui.QApplication(sys.argv)
        
    def test_cyclic_dependency( self ):
        """Create 2 widgets with a cyclic dependency, so that they can
        only be removed by the garbage collector, and then invoke the
        garbage collector in a different thread.
        """
        import gc
        
        class CyclicChildWidget(QtGui.QWidget):
            
            def __init__( self, parent ):
                super( CyclicChildWidget, self ).__init__( parent )
                self._parent = parent
                
        class CyclicWidget(QtGui.QWidget):
            
            def __init__( self ):
                super( CyclicWidget, self ).__init__()
                CyclicChildWidget( self )
                    
        # turn off automatic garbage collection, to be able to trigger it
        # at the 'right' time
        gc.disable()
        alive = lambda :sum( isinstance(o,CyclicWidget) for o in gc.get_objects() )
        #
        # first proof that the wizard is only destructed by the garbage
        # collector
        #
        cycle = CyclicWidget()
        self.assertTrue( alive() )
        del cycle
        self.assertTrue( alive() )
        gc.collect()
        self.assertFalse( alive() )
        #
        # now run the garbage collector in a different thread
        #
        cycle = CyclicWidget()
        del cycle
        self.assertTrue( alive() )

        class GarbageCollectingThread(QtCore.QThread):
            
            def run(thread):
                self.assertTrue( alive() )
                # assertian failure here, and core dump
                gc.collect()
                self.assertFalse( alive() )
                    
        thread = GarbageCollectingThread()
        thread.start()
        thread.wait()
#0  0x00821416 in __kernel_vsyscall ()
#1  0x00e72941 in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#2  0x00e75e42 in abort () at abort.c:92
#3  0x03c2c997 in qt_message_output (msgType=QtFatalMsg, buf=0x96ea6f8 "ASSERT 
failure in QCoreApplication::sendEvent: \"Cannot send events to objects owned 
by a different thread. Current thread 97870f0. Receiver '' (of type 'QWidget') 
was created in thread 9545fa8\", file "...) at global/qglobal.cpp:2282
#4  0x03c2cb8d in qt_message (msgType=QtFatalMsg, msg=0x3dd75f4 "ASSERT failure 
in %s: \"%s\", file %s, line %d", ap=0x2d22494 
"\304[\343\003\360\245n\t\017[\343\003]\001") at global/qglobal.cpp:2328
#5  0x03c2cef3 in qFatal (msg=0x3dd75f4 "ASSERT failure in %s: \"%s\", file %s, 
line %d") at global/qglobal.cpp:2511
#6  0x03c2c53a in qt_assert_x (where=0x3e35bc4 "QCoreApplication::sendEvent", 
what=0x96ea5f0 "Cannot send events to objects owned by a different thread. 
Current thread 97870f0. Receiver '' (of type 'QWidget') was created in thread 
9545fa8", file=0x3e35b0f "kernel/qcoreapplication.cpp", line=349) at 
global/qglobal.cpp:2035
#7  0x03d5542d in QCoreApplicationPrivate::checkReceiverThread (this=0x963e810, 
receiver=0x96dfe08) at kernel/qcoreapplication.cpp:349
#8  0x0514e96c in QApplication::notify (this=0x9548db8, receiver=0x96dfe08, 
e=0x2d22914) at kernel/qapplication.cpp:3754
#9  0x01635f02 in sipQApplication::notify (this=0x9548db8, a0=0x96dfe08, 
a1=0x2d22914) at sipQtGuiQApplication.cpp:317
#10 0x03d56178 in QCoreApplication::notifyInternal (this=0x9548db8, 
receiver=0x96dfe08, event=0x2d22914) at kernel/qcoreapplication.cpp:731
#11 0x05140e3d in QCoreApplication::sendEvent (receiver=0x96dfe08, 
event=0x2d22914) at 
../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:215
#12 0x051a781e in QWidget::~QWidget (this=0x96dfe08, __in_chrg=<value optimized 
out>) at kernel/qwidget.cpp:1647
#13 0x01686d84 in sipQWidget::~sipQWidget (this=0x96dfe08, __in_chrg=<value 
optimized out>) at sipQtGuiQWidget.cpp:345
#14 0x03d6f226 in QObjectPrivate::deleteChildren (this=0x96ad5e8) at 
kernel/qobject.cpp:1955
#15 0x051a7788 in QWidget::~QWidget (this=0x96ad598, __in_chrg=<value optimized 
out>) at kernel/qwidget.cpp:1631
#16 0x01686d84 in sipQWidget::~sipQWidget (this=0x96ad598, __in_chrg=<value 
optimized out>) at sipQtGuiQWidget.cpp:345
#17 0x0169e40e in release_QWidget (sipCppV=0x96ad598, sipState=6) at 
sipQtGuiQWidget.cpp:9307
#18 0x0169e4ca in dealloc_QWidget (sipSelf=0xb70edf5c) at 
sipQtGuiQWidget.cpp:9325
#19 0x0094a30f in forgetObject (sw=0xb70edf5c) at siplib.c:10029
#20 0x00949b30 in sipWrapper_dealloc (self=0xb70edf5c) at siplib.c:9580
#21 0x00bd555a in subtype_dealloc (self=0xb70edf5c) at Objects/typeobject.c:1005
#22 0x00bb6734 in dict_dealloc (mp=0xb709746c) at Objects/dictobject.c:985
#23 0x009494d1 in sipSimpleWrapper_clear (self=0xb70f8d1c) at siplib.c:9150
#24 0x00949a4c in sipWrapper_clear (self=0xb70f8d1c) at siplib.c:9531
#25 0x00bd58f7 in subtype_clear (self=0xb70f8d1c) at Objects/typeobject.c:874
#26 0x00c56ad6 in delete_garbage (generation=<value optimized out>) at 
Modules/gcmodule.c:769
#27 collect (generation=<value optimized out>) at Modules/gcmodule.c:930
#28 0x00c574af in gc_collect (self=0x0, args=0xb76d402c, kws=0x0) at 
Modules/gcmodule.c:1067
#29 0x00bbabb8 in PyCFunction_Call (func=0xb70939ec, arg=0xb76d402c, kw=0x0) at 
Objects/methodobject.c:85
#30 0x00c1f901 in call_function (f=0x96e1dc4, throwflag=0) at 
Python/ceval.c:4012
#31 PyEval_EvalFrameEx (f=0x96e1dc4, throwflag=0) at Python/ceval.c:2665
#32 0x00c21270 in PyEval_EvalCodeEx (co=0xb711c218, globals=0xb71162d4, 
locals=0x0, args=0xb7101558, argcount=1, kws=0x0, kwcount=0, defs=0x0, 
defcount=0, closure=0xb711daa4) at Python/ceval.c:3252
#33 0x00ba0f17 in function_call (func=0xb70951b4, arg=0xb710154c, kw=0x0) at 
Objects/funcobject.c:526
#34 0x00b7159c in PyObject_Call (func=0xb70951b4, arg=0xb710154c, kw=0x0) at 
Objects/abstract.c:2529
#35 0x00b83484 in instancemethod_call (func=0xb70ef734, arg=0xb710154c, kw=0x0) 
at Objects/classobject.c:2578
#36 0x00b7159c in PyObject_Call (func=0xb70ef734, arg=0xb76d402c, kw=0x0) at 
Objects/abstract.c:2529
#37 0x00c19b94 in PyEval_CallObjectWithKeywords (func=0xb70ef734, 
arg=0xb76d402c, kw=0x0) at Python/ceval.c:3881
#38 0x0093efc0 in sip_api_call_method (isErr=0x0, method=0xb70ef734, 
fmt=0x721031b "") at siplib.c:1742
#39 0x0706df2a in sipVH_QtCore_11 (sipGILState=PyGILState_UNLOCKED, 
sipMethod=0xb70ef734) at sipQtCorecmodule.cpp:4376
#40 0x070b8664 in sipQThread::run (this=0x97870f0) at sipQtCoreQThread.cpp:165
#41 0x03c39fef in QThreadPrivate::start (arg=0x97870f0) at 
thread/qthread_unix.cpp:320
#42 0x00fc1cc9 in start_thread (arg=0x2d23b70) at pthread_create.c:304
#43 0x00f1869e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:130

_______________________________________________
PyQt mailing list    PyQt@riverbankcomputing.com
http://www.riverbankcomputing.com/mailman/listinfo/pyqt

Reply via email to