Overriding QThread.run and calling the default implementation

Giuseppe Corbelli corbelligiuseppe at mesdan.it
Tue Jul 6 09:40:28 BST 2021

On 7/4/21 1:50 AM, Maurizio Berti wrote:
> Il giorno dom 4 lug 2021 alle ore 01:36 Jeremy Katz <jkatz at volexity.com 
> <mailto:jkatz at volexity.com>> ha scritto:
>     On Fedora 32 using Python 3.8.6, the attached test works for me with
>     PyQt/Qt 5.15.2 and fails to service either event loop with 5.12.10.
>     [...] 
>     The nearly identical C++ version works with both Qt 5.15.3 and 5.12.10.
>     This seems to be an issue within PyQt that has been resolved in newer
>     versions.
> Thank you so much for your input.
> Unfortunately I cannot test it with a newer version (I seriously need to 
> update my base system), but I'm trusting your tests.
> It could be interesting to know the whole background about this, though 
> (Phil, if you can, I'd be glad!).
> My low-level programming knowledge is very limited, so, to my eyes, a 
> simple override of run() that calls the default method shouldn't change 
> anything.
> But I know that we're talking about a binding (PyQt) *and* a wrapper 
> (QThread), so, many things I'd take for "granted" could happen under the 
> hood.

I don't remember where and how this was fixed but this does NOT work 
with python 3.5.4 - PyQt 5.10.1 - Qt 5.11.2

First let's note that there are a few threads running by default. I 
added a setObjectName() to the worker thread before run() so that it's 
easily spottable in GDB.

* Worker thread is running QEventLoop::exec()
* QDBusConnection, assume started automatically by Qt (QEventLoop::exec())
* QXcbEventQueue (xcb_wait_for_event()) defined in the Qt XCB platform 
plugin, assume started automatically by Qt
* 4 threads python:disk$0 to python:disk$4 waiting on a futex [1] 
started by QXcbWindow::create() (why? did not dig the details)
* Main Python thread

At any rate the default QThread::run() executes the event loop, which in 
turn can be based on QEventDispatcherGlib or QThreadPipe (underlying it 
may be using eventfd(7) or pipe(2)).
GLIB (if compiled in) may be turned off at runtime setting QT_NO_GLIB 
env var.
Using either event loop the main thread hangs the same way while 
handling a Qt event when GIL is acquired. Precisely:

#0  futex_abstimed_wait_cancelable (private=0, abstime=0x7fffffffcca0, 
clockid=-13296, expected=0, futex_word=0x7ffff7fb5828 <gil_cond+40>) at 
#1  __pthread_cond_wait_common (abstime=0x7fffffffcca0, clockid=-13296, 
mutex=0x7ffff7fb57c0 <gil_mutex>, cond=0x7ffff7fb5800 <gil_cond>) at 
#2  __pthread_cond_timedwait (cond=cond at entry=0x7ffff7fb5800 <gil_cond>, 
mutex=mutex at entry=0x7ffff7fb57c0 <gil_mutex>, 
abstime=abstime at entry=0x7fffffffcca0) at pthread_cond_wait.c:656
#3  0x00007ffff7e0f9e3 in PyCOND_TIMEDWAIT (cond=0x7ffff7fb5800 
<gil_cond>, mut=0x7ffff7fb57c0 <gil_mutex>, us=<optimized out>) at 
#4  take_gil (tstate=tstate at entry=0x55555555a8d0) at Python/ceval_gil.h:224
#5  0x00007ffff7e0fdeb in PyEval_RestoreThread 
(tstate=tstate at entry=0x55555555a8d0) at Python/ceval.c:450
#6  0x00007ffff7e3da07 in PyGILState_Ensure () at Python/pystate.c:823
#7  0x00007ffff46925e9 in sip_api_is_py_method (gil=0x7fffffffcde4, 
pymc=0x5555558e10dd "", sipSelf=0x7ffff281d288, cname=0x0, 
mname=0x7ffff44f6af9 <sipStrings_QtWidgets+58297> "leaveEvent") at 
#8  0x00007ffff43ecdca in sipQPushButton::leaveEvent 
(this=0x5555558e1090, a0=0x7fffffffd030) at sipQtWidgetsQPushButton.cpp:732

So it looks like a SIP/PyQt bug in handling GIL locks.

[1] https://en.wikipedia.org/wiki/Futex
Giuseppe Corbelli

More information about the PyQt mailing list