You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Qt 6.9.0 hangs when stopping a program during inference. #570
There's something fucked up with Qt 6.9.0 that I can't explain. If you try to stop a program while an inference is running, there's a good chance it will hang.
Reproduce steps:
Compile latest on Qt 6.9.0.
Run the Watt Farmer with no program open.
Wait until the program appears to get stuck trying to enter game.
Click "Stop Program". The program will never stop and just hangs on "Stopping program".
When I try to follow the wait chain, it doesn't make sense. The thread that it's waiting on is our thread, but the debugger is showing it as a Qt rendering thread.
When I put logs to track our thread that it was waiting on, it ends properly, but then the thread-id somehow gets (immediately) reused or reassigned to a Qt rendering thread that doesn't end. So when we try to join our thread, it's actually waiting for that Qt rendering thread forever.
It does not reproduce with an earlier build with Qt 6.8.3.
I'm going to rebuild latest with Qt 6.8.3 and see if it happens there.
Yup, no issue on Qt 6.8.3. I'll rebuild the latest release candidate with Qt 6.8.3.
I'm not satisfied with "just waiting for a newer Qt." The nature of this bug is really concerning as the thread ID gets immediately reassigned. This suggests that it is happening at the OS-level. But why does the Qt version matter? And why does it only happen with the specific thread being joined and the Qt rendering thread? The program makes a lot of other threads, yet it's only this pair that gets "merged".
This will not block v0.52.x release since I'm rolling it back to Qt 6.8.3. But it's very concerning nonetheless.
Call any Qt signal/slot from an std::thread. (this was never supposed to work anyway and we no longer do this)
Call QVideoFrame::toImage() from an std::thread. (is it using signals/slots internally?)
Use std::condition_variable on a QThread.
will cause at least of the following:
std::thread::join() hangs because the thread gets adopted by a different Qt task.
QThread::wait() hangs (unknown reason).
Process hangs after returning from main(). (requiring Task Manager to kill)
While #1 and #3 can be worked around by detaching instead of joining and exit() respectively, #2 cannot be worked around. There also seems to be global Qt state that gets corrupted upon any of the 3 triggers.
It is not feasible to completely change everything to Qt's threading library because:
Qt's threading library is shit and unnatural C++.
We do not control the threads from external libraries like DPP, ONNX, etc...
We are trying to minimize and isolate our Qt usage. Not increase it.
While it's not impossible to wrap every single C++ async function with an intermediate API that is backed by either C++ or Qt, there is a lot of stuff that would need to be wrapped. std::thread, std::mutex, std::condition_variable. And it would ruin any sort of idiomatic C++ async coding which we prefer.
I'm really hoping it works now - as ugly as it is. But seeing as how I've been wrong about this about 10 times now, I'm probably wrong again. lol
As for why the workaround works, we'll need to start with the problem. The problem is that in Qt 6.9+, when a thread ends, it can (but not always) get adopted by another thread handle. This seems to be some sort of attempt at thread pooling on Qt's part.
The bug seems to be in that adoption logic where a thread handle who's thread has ended will randomly adopt a non-Qt thread.
So if thread A joins its own thread, after the thread ends, the handle (incorrectly) adopts thread B. So thread.join() begins waiting for thread B to finish. But thread B is unrelated and thus it may never return.
Initially I only observed this issue when mixing std::thread and QThread - the former lacking the initialization to properly enter Qt's centralized thread management. But after I refactored the entire codebase to wrap threads (and their associated mutexes and condition variables) and pointed it all to Qt's versions (thus no more mixing), I was still seeing hangs in QThread's own join(). And it turns out that even though I made all of our threads into "compliance", Qt's video stack itself calls into AMD's drivers which spawn a ton of threads outside of Qt. These video rendering threads (which are not Qt threads) are not in compliance, and end up getting adoped by QThread handles causing them to hang on join.
The issue only seems to happen when a thread ends since only when a thread ends can it trigger the adoption mechanism. So the solution is to simply never let any thread end. Hence the full refactor of the entire codebase to use only thread pools.
But this wasn't quite enough. When a program exits, it destructs everything (even if you call exit(0)). And it's illegal to destruct a QThread without joining its thread. (whereas std::thread has a detach() function). Forcing QThread's to end doesn't work since it hangs in the destructor. So the final solution was to simply leak all QThreads so they don't try to join their threads.
Activity
Mysticial commentedon Apr 25, 2025
Maybe be related to this: https://bugreports.qt.io/browse/QTBUG-131892?filter=27174
pifopi commentedon Jul 25, 2025
Still broken with Qt 6.9.1
Mysticial commentedon Oct 12, 2025
This appears to be fixed in Qt 6.9.3 and 6.10.0. So the Qt bug I linked is not it.
Mysticial commentedon Oct 20, 2025
Nope, reproduced on Qt 6.10.0. Goddammit.
Mysticial commentedon Nov 7, 2025
8ff4171
Mysticial commentedon Nov 7, 2025
This work-around is not sufficient. It just found other places to hang:
QAudioSource::stop()main()exits.This is pretty hopeless.
Mysticial commentedon Feb 1, 2026
Latest attempts to work-around this have failed:
It seems that any of the following:
std::thread. (this was never supposed to work anyway and we no longer do this)QVideoFrame::toImage()from anstd::thread. (is it using signals/slots internally?)std::condition_variableon aQThread.will cause at least of the following:
std::thread::join()hangs because the thread gets adopted by a different Qt task.QThread::wait()hangs (unknown reason).main(). (requiring Task Manager to kill)While #1 and #3 can be worked around by detaching instead of joining and
exit()respectively, #2 cannot be worked around. There also seems to be global Qt state that gets corrupted upon any of the 3 triggers.It is not feasible to completely change everything to Qt's threading library because:
While it's not impossible to wrap every single C++ async function with an intermediate API that is backed by either C++ or Qt, there is a lot of stuff that would need to be wrapped.
std::thread,std::mutex,std::condition_variable. And it would ruin any sort of idiomatic C++ async coding which we prefer.Mysticial commentedon Feb 14, 2026
Pulling from here: https://discord.com/channels/695809740428673034/1462210406616531259/1471708678502613022
I'm really hoping it works now - as ugly as it is. But seeing as how I've been wrong about this about 10 times now, I'm probably wrong again. lol