Skip to content

Commit 7f40f0e

Browse files
committed
More progress on Qt6.9 thread bug workaround.
1 parent 8558a98 commit 7f40f0e

7 files changed

Lines changed: 78 additions & 22 deletions

File tree

Common/Cpp/Concurrency/Backends/Thread_Qt.tpp

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <QCoreApplication>
88
#include <QThread>
99
#include "Common/Cpp/Containers/Pimpl.tpp"
10+
#include "Common/Cpp/Concurrency/Qt6.9ThreadBugWorkaround.h"
1011
#include "Common/Cpp/Concurrency/Thread.h"
1112

1213
//#include <iostream>
@@ -22,7 +23,7 @@ namespace PokemonAutomation{
2223
struct Thread::Data : public QThread{
2324
Data(std::function<void()>&& function)
2425
: m_function(std::move(function))
25-
, m_finished(false)
26+
// , m_finished(false)
2627
{
2728
start();
2829
}
@@ -37,13 +38,34 @@ struct Thread::Data : public QThread{
3738
// What the fuck?!?! This hangs on Qt6.9 even after run() returns!
3839
wait();
3940

41+
#if 0
42+
#ifndef PA_ENABLE_QT_ADOPTION_WORKAROUND
43+
wait();
44+
#else
45+
while (m_finished.load(std::memory_order_acquire));
46+
if (wait(1000)){
47+
return;
48+
}
49+
std::cout << "QThread did not quit in a timely manner... It is likely hung..." << std::endl;
50+
abort();
51+
#endif
52+
#endif
53+
54+
//
55+
// On further investigation, wait() hangs because the Qt6.9 thread
56+
// adoption bug affects not just std::thread, but its own as well.
57+
//
58+
// When this QThread ends, the handle adopts one of the video backend
59+
// threads which do not join. Thus it hangs here.
60+
//
61+
4062
// cout << "finished: " << this << endl;
4163
}
4264

4365
virtual void run() override{
4466
// cout << "starting: " << this << endl;
4567
m_function();
46-
m_finished.store(true, std::memory_order_acquire);
68+
// m_finished.store(true, std::memory_order_acquire);
4769
// cout << "exiting: " << this << endl;
4870
}
4971

@@ -83,6 +105,14 @@ void Thread::join(){
83105
return;
84106
}
85107

108+
#ifdef PA_ENABLE_QT_ADOPTION_WORKAROUND
109+
// Exit the thread.
110+
m_data->quit();
111+
112+
// But instead of waiting and joining (which will hang), we leak it intentionally.
113+
m_data.release();
114+
#endif
115+
86116
// Clear the Pimpl data, marking this Thread as joined/empty
87117
// After this, operator bool() returns false and join() becomes no-op
88118
m_data.clear();

Common/Cpp/Containers/Pimpl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ class Pimpl{
3636
template <class... Args>
3737
void reset(Args&&... args);
3838

39+
Type* release(){
40+
Type* ret = m_ptr;
41+
m_ptr = nullptr;
42+
return ret;
43+
}
44+
3945

4046
public:
4147
operator bool() const{ return m_ptr != nullptr; }

Common/Qt/QtThreadPool.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*
55
*/
66

7+
#include "Common/Cpp/Concurrency/Qt6.9ThreadBugWorkaround.h"
78
#include "Common/Cpp/Concurrency/SpinPause.h"
89
#include "QtThreadPool.h"
910

@@ -203,6 +204,12 @@ QtEventThreadPool::~QtEventThreadPool(){
203204
stop();
204205
}
205206
void QtEventThreadPool::stop(){
207+
#ifdef PA_ENABLE_QT_ADOPTION_WORKAROUND
208+
// Leak all the threads because they can't be joined without hanging.
209+
for (std::unique_ptr<QtEventThread>& thread : m_threads){
210+
thread.release();
211+
}
212+
#endif
206213
m_threads.clear();
207214
m_available_threads.clear();
208215
}
@@ -244,8 +251,10 @@ QtEventThread& QtEventThreadPool::get_thread(){
244251
std::lock_guard<Mutex> lg(m_lock);
245252
if (m_available_threads.empty()){
246253
m_available_threads.reserve(m_threads.size() + 1);
247-
auto& new_thread = m_threads.emplace_back();
248-
m_available_threads.emplace_back(&new_thread);
254+
QtEventThread* new_thread = m_threads.emplace_back(
255+
std::make_unique<QtEventThread>()
256+
).get();
257+
m_available_threads.emplace_back(new_thread);
249258
}
250259
QtEventThread* ret = m_available_threads.back();
251260
// cout << "QtEventThreadPool: " << m_threads.size() << ", using = " << ret << endl;

Common/Qt/QtThreadPool.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class QtEventThreadPool : public QObject{
114114

115115
Mutex m_lock;
116116

117-
std::deque<QtEventThread> m_threads;
117+
std::vector<std::unique_ptr<QtEventThread>> m_threads;
118118
std::vector<QtEventThread*> m_available_threads;
119119
};
120120

SerialPrograms/Source/CommonFramework/AudioPipeline/IO/AudioFileLoader.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424

2525
namespace PokemonAutomation{
2626

27-
AudioFileLoader::AudioFileLoader(QObject* parent, const std::string& filename, const QAudioFormat& audioFormat):
27+
AudioFileLoader::AudioFileLoader(
28+
QObject* parent,
29+
const std::string& filename,
30+
const QAudioFormat& audioFormat
31+
):
2832
QObject(parent), m_filename(filename), m_audioFormat(audioFormat) {}
2933

3034
AudioFileLoader::~AudioFileLoader(){

SerialPrograms/Source/CommonFramework/AudioPipeline/IO/AudioFileLoader.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ class AudioFileLoader: public QObject{
4343
Q_OBJECT
4444

4545
public:
46-
AudioFileLoader(QObject* parent, const std::string& filename, const QAudioFormat& audioFormat);
46+
AudioFileLoader(
47+
QObject* parent,
48+
const std::string& filename,
49+
const QAudioFormat& audioFormat
50+
);
4751
virtual ~AudioFileLoader();
4852

4953
// Start loading and decoding audio samples.
@@ -126,7 +130,12 @@ class AudioDecoderWorker: public QObject{
126130
Q_OBJECT
127131

128132
public:
129-
AudioDecoderWorker(QObject* parent, const std::string& filename, const QAudioFormat& audioFormat, std::vector<char>& decodedBuffer);
133+
AudioDecoderWorker(
134+
QObject* parent,
135+
const std::string& filename,
136+
const QAudioFormat& audioFormat,
137+
std::vector<char>& decodedBuffer
138+
);
130139
virtual ~AudioDecoderWorker();
131140

132141
void start();

SerialPrograms/Source/CommonFramework/Main.cpp

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,18 @@ int main(int argc, char *argv[]){
234234
global_watchdog().stop();
235235
static_cast<FileWindowLogger&>(global_logger_raw()).stop();
236236

237-
// When we actually migrate to Qt 6.9+, we may need to move the exit(0)
238-
// call here since joining *any* threads may hang.
237+
//
238+
// Workaround Qt 6.9 thread-adoption bug on Windows.
239+
// https://github.com/PokemonAutomation/Arduino-Source/issues/570
240+
// https://bugreports.qt.io/browse/QTBUG-131892
241+
//
242+
// Joining threads on Qt 6.9+ Windows will hang!
243+
//
244+
//#ifdef PA_ENABLE_QT_ADOPTION_WORKAROUND
245+
// cout << "Qt6.9 bug workaround: abort() to avoid thread join hangs." << endl;
246+
// abort();
247+
// exit(ret);
248+
//#endif
239249

240250
// Force stop the thread pools.
241251
// This is where all the threads in the program are joined.
@@ -250,17 +260,5 @@ int main(int argc, char *argv[]){
250260

251261
cout << "Exiting main()..." << endl;
252262

253-
254-
//
255-
// Workaround Qt 6.9 thread-adoption bug on Windows.
256-
// https://github.com/PokemonAutomation/Arduino-Source/issues/570
257-
// https://bugreports.qt.io/browse/QTBUG-131892
258-
//
259-
// Program will hang after main() without this!
260-
//
261-
#ifdef PA_ENABLE_QT_ADOPTION_WORKAROUND
262-
exit(ret);
263-
#endif
264-
265263
return ret;
266264
}

0 commit comments

Comments
 (0)