[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