[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