[prev in list] [next in list] [prev in thread] [next in thread] 

List:       pykde
Subject:    Re: [PyQt] assertion failure in sip
From:       Erik Janssens <Erik.Janssens () conceptive ! be>
Date:       2011-09-20 19:23:21
Message-ID: CAHubDb0FtGnTU_K7=N1BEvkpp69zpDJBvsu8XhtUTPHPpyL-qA () mail ! gmail ! com
[Download RAW message or body]

I've tested it and it still segfaults at the same line.

Adding the GIL lock also causes a deadlock in one of
my pyqt unit tests : test_queued_connection_after_delete

This is a bit strange, because this is the test I wrote
to 'prove' another issue, but I have been unable to
create segfaults with this test.

If there is more debugging stuff I can try, please
let me know

On Sat, Sep 17, 2011 at 2:46 PM, Phil Thompson
<phil@riverbankcomputing.com> wrote:
> On Sat, 17 Sep 2011 04:51:42 +0100, Phil Thompson
> <phil@riverbankcomputing.com> wrote:
>> On Fri, 16 Sep 2011 22:28:24 +0200, Erik Janssens
>> <Erik.Janssens@conceptive.be> wrote:
>>> Hi,
>>>
>>> I'm seeing an assertion failure in sip, stacktrace
>>> attached.
>>>
>>> It happens consistently, but only after long stress
>>> tests of a large application.
>>>
>>> Any suggestions on what might be causing this
>>> failure, so I can try to isolate the case ?
>>
>> It looks like there might be a race condition when the Python object
>> wrapping the QWidget is being garbage collected and the QWidget itself
> is
>> still calling it's virtual methods.
>>
>> This is supposed to be taken care of by the "if (sipSelf == NULL)" test
>> earlier in the function. This test is done without the GIL. The pointer
>> being tested is set in dealloc_QWidget() (with the GIL).
>>
>> Therefore if the Python object is in the process of being garbage
>> collected but before dealloc_QWidget() gets a chance to reset the
> pointer,
>> and then QWidget::changeEvent() gets called then there might be a
> problem.
>>
>> The fix would be to do the test with the GIL but that requires a change
> to
>> the signature of sip_api_is_py_method() to pass a pointer to the pointer
>> being tested rather than the pointer itself.
>>
>> Watch this space...
>
> I've implemented the change in tonight's SIP snapshot (and current Hg).
>
> Let me know if it makes a difference.
>
> Phil
>

["assertion_failure_sip.txt" (text/plain)]

Program terminated with signal 11, Segmentation fault.
#0  0xb56623d6 in sip_api_is_py_method (gil=0xbfa65e08, pymc=0xc4ebde7 "", \
sipSelf=0x1057092c, cname=0x0, mname=0xb6c21297 "changeEvent") at siplib.c:7598 7598	 \
assert(PyTuple_Check(mro)); (gdb) 
(gdb) 
(gdb) 
(gdb) 
(gdb) 
(gdb) bt
#0  0xb56623d6 in sip_api_is_py_method (gil=0xbfa65e08, pymc=0xc4ebde7 "", \
sipSelf=0x1057092c, cname=0x0, mname=0xb6c21297 "changeEvent") at siplib.c:7598 #1  \
0xb6bf762c in sipQWidget::changeEvent (this=0xc4ebdb0, a0=0xc164920) at \
sipQtGuiQWidget.cpp:967 #2  0xb5b3a514 in QWidget::event (this=0xc4ebdb0, \
event=0xc164920) at kernel/qwidget.cpp:8529 #3  0xb6bf6425 in sipQWidget::event \
(this=0xc4ebdb0, a0=0xc164920) at sipQtGuiQWidget.cpp:473 #4  0xb5addb26 in \
QApplicationPrivate::notify_helper (this=0xa41a618, receiver=0xc4ebdb0, e=0xc164920) \
at kernel/qapplication.cpp:4462 #5  0xb5add865 in QApplication::notify \
(this=0xa260fd0, receiver=0xc4ebdb0, e=0xc164920) at kernel/qapplication.cpp:4427 #6  \
0xb6ba6af2 in sipQApplication::notify (this=0xa260fd0, a0=0xc4ebdb0, a1=0xc164920) at \
sipQtGuiQApplication.cpp:312 #7  0xb57eaf9e in QCoreApplication::notifyInternal \
(this=0xa260fd0, receiver=0xc4ebdb0, event=0xc164920) at \
kernel/qcoreapplication.cpp:731 #8  0xb5acfab5 in QCoreApplication::sendEvent \
(receiver=0xc4ebdb0, event=0xc164920) at \
../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:215 #9  0xb57ebf92 \
in QCoreApplicationPrivate::sendPostedEvents (receiver=0x0, event_type=0, \
data=0xa349770) at kernel/qcoreapplication.cpp:1372 #10 0xb57ebc89 in \
QCoreApplication::sendPostedEvents (receiver=0x0, event_type=0) at \
kernel/qcoreapplication.cpp:1265 #11 0xb5b93b7e in QCoreApplication::sendPostedEvents \
() at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:220 #12 \
0xb5b9e90c in QEventDispatcherX11::processEvents (this=0xa34db70, flags=...) at \
kernel/qeventdispatcher_x11.cpp:75 #13 0xb57eb397 in QCoreApplication::processEvents \
(flags=...) at kernel/qcoreapplication.cpp:923 #14 0xb519d577 in \
meth_QCoreApplication_processEvents (sipArgs=0xb748b02c, sipKwds=0x0) at \
sipQtCoreQCoreApplication.cpp:667 #15 0xb7700ed8 in PyCFunction_Call (func=0xeefb84c, \
arg=0xb748b02c, kw=0x0) at Objects/methodobject.c:85


["deadlock.txt" (text/plain)]

Thread 1
--------

(gdb) bt
#0  0xb7722424 in __kernel_vsyscall ()
#1  0xb756c7d5 in sem_wait@@GLIBC_2.1 () from /lib/i386-linux-gnu/libpthread.so.0
#2  0xb76a0840 in PyThread_acquire_lock (lock=0x8cd1bd8, waitflag=1) at \
Python/thread_pthread.h:309 #3  0xb7665418 in PyEval_RestoreThread (tstate=0x87b3050) \
at Python/ceval.c:356 #4  0xb50b2582 in meth_QCoreApplication_processEvents \
(sipArgs=0xb738e02c, sipKwds=0x0) at sipQtCoreQCoreApplication.cpp:668


Thread 2
--------

#0  0xb7722424 in __kernel_vsyscall ()
#1  0xb756c7d5 in sem_wait@@GLIBC_2.1 () from /lib/i386-linux-gnu/libpthread.so.0
#2  0xb76a0840 in PyThread_acquire_lock (lock=0x8cd1bd8, waitflag=1) at \
Python/thread_pthread.h:309 #3  0xb7665418 in PyEval_RestoreThread (tstate=0x9b8a8e8) \
at Python/ceval.c:356 #4  0xb768c770 in PyGILState_Ensure () at Python/pystate.c:609
#5  0xb557730b in sip_api_is_py_method (gil=0xb47a1344, pymc=0x9889544 "", \
sipSelfp=0x9889540, cname=0x0, mname=0xb5130276 "run") at siplib.c:7562 #6  \
0xb4fe01b2 in sipQThread::run (this=0x9889538) at sipQtCoreQThread.cpp:148


["test_qt_bindings.py" (text/x-python)]

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

import unittest
import gc

from PyQt4 import QtGui, QtCore

#
# some helper classes to create all kinds of weird object structures
#

class ReferenceHoldingBox(QtGui.QGroupBox):
    """A group box holding references to the table
    view and the table model"""

    def __init__(self, model, table):
        QtGui.QGroupBox.__init__(self)
        self.model = model
        self.table = table
                 
class TableView( QtGui.QWidget  ):
    """A widget containg both a table and a groupbox that
    holds a reference to both the table and the model of the
    table"""

    def __init__( self, table_model ):
        super(TableView, self).__init__()
        widget_layout = QtGui.QVBoxLayout()
        table = QtGui.QTableView( self )
        table.setModel( table_model )
        widget_layout.addWidget( table )
        widget_layout.addWidget( ReferenceHoldingBox( table_model, self ) )
        self.setLayout( widget_layout )

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 )
     
count_alive = lambda:sum( isinstance(o,CyclicWidget) for o in gc.get_objects() )
alive = lambda initial:count_alive()-initial

class ModelViewRegister(QtCore.QObject):
    
    def __init__(self):
        super(ModelViewRegister, self).__init__()
        self.max_key = 0
        self.model_by_view = dict()
        
    def register_model_view(self, model, view):
        self.max_key += 1
        view.destroyed.connect( self._registered_object_destroyed )
        self.model_by_view[self.max_key] = model
        view.setProperty( 'registered_key', self.max_key )
        
    @QtCore.pyqtSlot(QtCore.QObject)
    def _registered_object_destroyed(self, qobject):
        key, _success = qobject.property('registered_key').toLongLong()
        del self.model_by_view[key]

class TableViewCases(unittest.TestCase):
    """Tests related to table views"""

    def setUp(self):
        from camelot.test import get_application
        self.app = get_application()

    def test_table_view_garbage_collection(self):
        """Create a table view and force its garbage collection, while
        a common reference exists to both the table view and its model.
        
        when doing so without registering the model and the view to the
        ModelViewRegister, this will segfault.
        """            
        register = ModelViewRegister()
        
        for _i in range(100):
            
            class TableModelSubclass(QtGui.QStringListModel):
                pass
    
            model = TableModelSubclass()
            widget = TableView( model )
            register.register_model_view(model, widget)
            gc.collect()

class SignalEmitter(QtCore.QObject):
    
    my_signal = QtCore.pyqtSignal(object)
    
    def start_emitting(self, limit=1000):
        for _i in range(limit):
            o = object()
            self.my_signal.emit(o)

class SignalReceiver(QtCore.QObject):
    
    @QtCore.pyqtSlot(object)
    def my_slot(self, obj):
        print self.sender()

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_custom_garbage_collectory( self ):
        from camelot.view.model_thread.garbage_collector import GarbageCollector
        initial = count_alive()
        collector = GarbageCollector(None, debug=True)
        collector._threshold = [0, 0, 0]
        self.assertFalse( alive(initial) )
        cycle = CyclicWidget()
        self.assertTrue( alive(initial) )
        del cycle
        self.assertTrue( alive(initial) )
        collector._check()
        self.assertFalse( alive(initial) )
        
    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.
        """

        #
        # dont run this test, since it will segfault the
        # interpreter
        #
                    
        initial = count_alive()
        # turn off automatic garbage collection, to be able to trigger it
        # at the 'right' time
        gc.disable()
        #
        # first proof that the wizard is only destructed by the garbage
        # collector
        #
        cycle = CyclicWidget()
        self.assertTrue( alive(initial) )
        del cycle
        self.assertTrue( alive(initial) )
        gc.collect()
        self.assertFalse( alive(initial) )
        #
        # now run the garbage collector in a different thread
        #
        cycle = CyclicWidget()
        del cycle
        self.assertTrue( alive(initial) )

        class GarbageCollectingThread(QtCore.QThread):
            
            def run(thread):
                self.assertTrue( alive(initial) )
                # assertian failure here, and core dump
                gc.collect()
                self.assertFalse( alive(initial) )
                    
        thread = GarbageCollectingThread()
        thread.start()
        thread.wait()
        
class SignalSlotCase( unittest.TestCase ):
    
    def setUp(self):
        self.app = QtGui.QApplication.instance()
        if self.app == None:
            self.app = QtGui.QApplication([])
        #from camelot.test import get_application
        #self.app = get_application()

    def test_queued_connection_after_delete(self):
        """Connect emitter and receiver in a different thread with a
        queued connection.  Emitter emits a signal and then deletes
        itself before the receiver its slot is called.
        
        this corrupts the program.
        """
        import random
        import time
        receiver = SignalReceiver()
        #threads = []
        for i in range(1000):
            

            class EmittingThread(QtCore.QThread):
                
                def __init__( self ):
                    QtCore.QThread.__init__( self )
                    self.emitter = SignalEmitter()
                    
                def connect( self, receiver ):
                    self.emitter.my_signal[object].connect( receiver.my_slot, \
QtCore.Qt.QueuedConnection )  
                def run(self): 
                    self.emitter.start_emitting( 1 )
                    #time.sleep( 0.01 / random.randint(1, 100) )
                    for i in range( random.randint(1000,100000) ):
                        pass
                    self.emitter = None
                    
            thread = EmittingThread()
            thread.connect( receiver )
            thread.start()
            self.app.processEvents()
            #3threads.append( thread )
            thread.wait()
            #del thread
            
        #for thread in threads:
        #    thread.wait()
            
            
        
        
    def test_multiple_threads_emit_and_connect(self):
        """Emit a signal containing a python object and at the
        same time connect to it.
        
        this used to deadlock in pyqt.
        """
            
        emitter = SignalEmitter()
        
        class ReceivingThread(QtCore.QThread):
            
            def run(self):
                receivers = []
                for _i in range(100):
                    receiver = SignalReceiver()
                    emitter.my_signal.connect( receiver.my_slot )
                    receivers.append(receiver)
                    
        thread = ReceivingThread()
        thread.start()
        emitter.start_emitting()
        thread.wait()
        
    def test_received_signals(self):
        """See what happens when an object that has
        been deleted receives signals"""

        class SignalReceiver(QtGui.QWidget):
    
            def __init__(self, parent):
                super(SignalReceiver, self).__init__(parent)
                receiver_child = QtGui.QWidget(self)
                receiver_child.setObjectName('child')
                
            @QtCore.pyqtSlot(object)
            def my_slot(self, obj):
                child = self.findChild(QtCore.QObject, 'child')
                print child.objectName()
        
        class ReceiverParent(QtGui.QTabWidget):
            
            def __init__(self):
                super(ReceiverParent, self).__init__()
                receiver = SignalReceiver(parent=self)
                receiver.setObjectName('receiver')
                self.addTab(receiver, 'receiver')
                
            def get_receiver(self):
                return self.findChild(QtCore.QObject, 'receiver')
        
        receiver_parent = ReceiverParent()
                
        class EmittingThread(QtCore.QThread):
            
            my_signal = QtCore.pyqtSignal(object)
            started = False
            move_on = False
            
            def run(self):
                for i in range(10):
                    self.my_signal.emit( i )
                    self.started = True
                    while not self.move_on:
                        pass
                                    
        thread = EmittingThread()
        thread.my_signal.connect( receiver_parent.get_receiver().my_slot, \
QtCore.Qt.QueuedConnection )  #del receiver_parent
        thread.start()
        while thread.started == False:
            thread.wait(1)
            self.app.processEvents()
        receiver_parent.widget(0).deleteLater()
        receiver_parent.removeTab(0)
        gc.collect()
        thread.move_on = True
        thread.wait()
        self.app.processEvents()



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

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic