Deadlock in QThreadPool destructor

Phil Thompson phil at riverbankcomputing.com
Tue Feb 1 10:36:10 GMT 2022


On 31/01/2022 11:49, Ales Erjavec wrote:
> Hi,
> 
> A deadlock can happen in QThreadPool destructor if any QRunnables are
> still running/pending and attempt to acquire the GIL.
> 
> ```
> from PyQt5.QtCore import QThreadPool, QRunnable
> 
> 
> class Runnable(QRunnable):
>     def run(self):
>         print("in run")
> 
> 
> def test_one():
>     pool = QThreadPool(maxThreadCount=1)
>     pool.start(Runnable())
>     pool.start(Runnable())
>     pool.start(Runnable())
>     pool.start(Runnable())
>     del pool # should invoke ~QThreadPool here
> 
> i = 1
> while True:
>     print("Run ", i, flush=True)
>     test_one()  # this mostly just executes once and deadlocks.
>     i += 1
> ```
> 
> Relevant extract from program sample
> ```
> Call graph:
>     2721 Thread_2615382   DispatchQueue_1: com.apple.main-thread  
> (serial)
>     + 2721 start  (in libdyld.dylib) + 1  [0x7fff6c6d83d5]
>     +   2721 main  (in Python) + 53  [0x10d7b6f65]
>     +     2721 Py_Main  (in Python) + 3554  [0x10d8dcb12]
>     +       2721 PyRun_SimpleFileExFlags  (in Python) + 882  
> [0x10d8c2bf2]
>     +         2721 PyRun_FileExFlags  (in Python) + 209  [0x10d8c3381]
>     +           2721 PyEval_EvalCode  (in Python) + 100  [0x10d88d244]
>     +             2721 _PyEval_EvalCodeWithName  (in Python) + 2447
> [0x10d8981af]
>     +               2721 _PyEval_EvalFrameDefault  (in Python) + 27559
>  [0x10d893eb7]
>     +                 2721 call_function  (in Python) + 401  
> [0x10d897721]
>     +                   2721 fast_function  (in Python) + 381  
> [0x10d898a3d]
>     +                     2721 _PyEval_EvalFrameDefault  (in Python) +
> 15504  [0x10d890fa0]
>     +                       2721 subtype_dealloc  (in Python) + 1049
> [0x10d827b99]
>     +                         2721 sipWrapper_dealloc  (in
> sip.cpython-36m-darwin.so) + 37  [0x10e7ba765]
>     +                           2721 forgetObject  (in
> sip.cpython-36m-darwin.so) + 182  [0x10e7bab96]
>     +                             2721
> sipQThreadPool::~sipQThreadPool()  (in QtCore.abi3.so) + 44
> [0x10de40d4c]
>     +                               2721 QThreadPool::~QThreadPool()
> (in QtCore) + 34  [0x10e12e4b2]
>     +                                 2721
> QThreadPoolPrivate::waitForDone(int)  (in QtCore) + 179  [0x10e12df73]
>     +                                   2721
> QWaitCondition::wait(QMutex*, QDeadlineTimer)  (in QtCore) + 93
> [0x10e13080d]
>     +                                     2721
> QWaitConditionPrivate::wait(QDeadlineTimer)  (in QtCore) + 59
> [0x10e1308cb]
>     +                                       2721 _pthread_cond_wait
> (in libsystem_pthread.dylib) + 722  [0x7fff6c8cf56e]
>     +                                         2721 __psynch_cvwait
> (in libsystem_kernel.dylib) + 10  [0x7fff6c810866]
>     2721 Thread_2615392: Thread (pooled)
>       2721 thread_start  (in libsystem_pthread.dylib) + 13  
> [0x7fff6c8cb40d]
>         2721 _pthread_start  (in libsystem_pthread.dylib) + 66  
> [0x7fff6c8cf249]
>           2721 _pthread_body  (in libsystem_pthread.dylib) + 126
> [0x7fff6c8cc2eb]
>             2721 QThreadPrivate::start(void*)  (in QtCore) + 329  
> [0x10e128a79]
>               2721 QThreadPoolThread::run()  (in QtCore) + 124  
> [0x10e12cf4c]
>                 2721 sipQRunnable::run()  (in QtCore.abi3.so) + 51
> [0x10de549a3]
>                   2721 sip_api_is_py_method_12_8  (in
> sip.cpython-36m-darwin.so) + 68  [0x10e7bfa74]
>                     2721 PyGILState_Ensure  (in Python) + 109  
> [0x10d8c257d]
>                       2721 PyEval_RestoreThread  (in Python) + 41  
> [0x10d88ce99]
>                         2721 take_gil  (in Python) + 235  [0x10d88c8db]
>                           2716 _pthread_cond_wait  (in
> libsystem_pthread.dylib) + 722  [0x7fff6c8cf56e]
>                           ! 2656 __psynch_cvwait  (in
> libsystem_kernel.dylib) + 10,12  [0x7fff6c810866,0x7fff6c810868]
>                           ! 60 cerror_nocancel  (in
> libsystem_kernel.dylib) + 20,0,...
> [0x7fff6c80d467,0x7fff6c80d453,...]
>                           2 _pthread_cond_wait  (in
> libsystem_pthread.dylib) + 453  [0x7fff6c8cf461]
>                           ! 2 __gettimeofday  (in
> libsystem_kernel.dylib) + 17  [0x7fff6c810e32]
>                           2 _pthread_cond_wait  (in
> libsystem_pthread.dylib) + 726,731  [0x7fff6c8cf572,0x7fff6c8cf577]
>                           1 _pthread_cond_wait  (in
> libsystem_pthread.dylib) + 381  [0x7fff6c8cf419]
>                             1 _pthread_mutex_droplock  (in
> libsystem_pthread.dylib) + 494  [0x7fff6c8cd500]
> ```
> 
> The same seems to happen in PyQt5 and PyQt6
> 
> Granted that this is a bad code (it should explicitly wait on the pool
> and/or all the runnable tasks), but the ~QThreadPool does document
> that it will wait for all tasks to complete and should therefore
> release the GIL.

Fixed in both.

Thanks,
Phil


More information about the PyQt mailing list