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 <p...@riverbankcomputing.com> wrote: > On Sat, 17 Sep 2011 04:51:42 +0100, Phil Thompson > <p...@riverbankcomputing.com> wrote: >> On Fri, 16 Sep 2011 22:28:24 +0200, Erik Janssens >> <erik.janss...@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 >
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
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 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