[PyKDE] Using thread and event filter crashes Python

Phil Thompson phil at riverbankcomputing.co.uk
Wed Jul 27 13:29:34 BST 2005


On Monday 11 July 2005 4:50 pm, Yann Cointepas wrote:
> On Sunday 10 July 2005 19:44, you wrote:
> > With current snapshots I don't get the double delete and get a SIGKILL
> > rather than a seg fault.
> >
> > The above code definately breaks Qt's rules - you must must create
> > QWidgets in the main GUI thread. I need another example that
> > demonstrate's the problem but conforms to Qt's rules.
> >
> > Phil
>
> I thought it was legal to create widgets in any thread between
> qt.qApp.lock() and qt.qApp.lock(). I used this to make a smaller example.
>
> The error is still here with legal code. I joined a modification of the
> program that uses a queue of functions which are called from the main GUI
> thread via a QTimer. It produces exactly the same error as the previous
> sample code but uses Qt only from the main thread.
>
> I noticed a different behavior for two different python/sip versions. With
> Fedora core 2 versions (Python 2.3.3/sip 3.10.1/Qt 3.3.3), I have the
> following output:
>
> !threadedFunction! <Thread(Thread-1, started)>
> !createWidget! <_MainThread(MainThread, started)>
> !init TestWidget! <_MainThread(MainThread, started)>
> !del TestWidget! <_MainThread(MainThread, started)>
> python: Objects/classobject.c:631: instance_dealloc: Assertion
> `g->gc.gc_refs != (-2)' failed. Abort
>
>
> With my own Python/sip/PyQt compiled from sources
> (Python 2.4.1/sip 4.2.1/qt 3.3.3), the result is:
>
> !threadedFunction! <Thread(Thread-1, started)>
> !createWidget! <_MainThread(MainThread, started)>
> !init TestWidget! <_MainThread(MainThread, started)>
> !del TestWidget! <_MainThread(MainThread, started)>
> !del TestWidget! <_MainThread(MainThread, started)>
> Segmentation fault
>
> #--------------------------------------------------------------------------
>-- import sys, threading
> from qt import *
>
> class EventFilter( QObject ):
>   def eventFilter( self, o, e ):
>     return False
>
> class TestWidget( QWidget ):
>   def __init__( self ):
>     print '!init TestWidget!', threading.currentThread()
>     QWidget.__init__( self )
>
>   def __del__( self ):
>     print '!del TestWidget!', threading.currentThread()
>
>
> class MainThreadActions( QObject ):
>   '''Queue of functions that are called from the main thread
>   via a QTimer. Functions and arguments can be pushed in
>   the queue (from any thread) with the push method.'''
>   def __init__( self, parent = None ):
>     QObject.__init__( self, parent )
>     self.lock = threading.RLock()
>     self.actions = []
>     self.timer = QTimer( self )
>     self.connect( self.timer, SIGNAL( 'timeout()' ), self.__doAction )
>     self.timer.start( 100, 0 )
>
>   def push( self, function, *args, **kwargs ):
>     self.lock.acquire()
>     try:
>       self.actions.append( ( function, args, kwargs ) )
>     finally:
>       self.lock.release()
>
>   def __doAction( self ):
>     self.lock.acquire()
>     try:
>       actions = self.actions
>       self.actions = []
>     finally:
>       self.lock.release()
>     for ( function, args, kwargs ) in actions:
>       if kwargs is None or len( kwargs ) == 0:
>         apply( function, args )
>       else:
>         apply( function, args, kwargs )
>
>
> def threadedFunction():
>   '''This function is *not* called from the main GUI thread'''
>   print '!threadedFunction!', threading.currentThread()
>   mainThread.push( createWidget )
>
>
> def createWidget():
>   '''This function is called from the main GUI thread'''
>   print '!createWidget!', threading.currentThread()
>   w = TestWidget()
>   w.show()
>   del w
>
>
> app = QApplication( sys.argv )
>
> theEventFilter = EventFilter()
> qApp.installEventFilter( theEventFilter )
>
> mainThread = MainThreadActions()
>
> mainWindow = QLabel( 'main', None )
> mainWindow.show()
> app.setMainWidget( mainWindow )
>
> t = threading.Thread( target=threadedFunction )
> t.start()
>
> app.exec_loop()

Apologies for the delay in replying. The fix should be in tonight's SIP 
snapshot. This proved to be rather subtle...

It's nothing to do with threading - you can remove all the thread related code 
in your example and still get a crash.

When your TestWidget gets garbage collected the underlying C++ dtor is called 
which causes a QHideEvent to be generated. When calling the Python 
eventFilter() method with the TestWidget object as an argument, the object's 
reference count gets incremented again, and then decremented back to 0 (when 
eventFilter() returns) causing it to be garbage collected for a second time - 
resulting in the crash.

The fix is to remove the TestWidget object from SIP's internal map of Python 
objects and C++ pointers before the QWidget dtor is called so that passing it 
as an argument to eventFilter() causes it to be re-wrapped as a new Python 
object.

Phil




More information about the PyQt mailing list