如何使用 Qt Network 防止关机竞争条件 类
How to prevent shutdown race conditions with Qt Network classes
我们有一个 OS X C++ 应用程序,它使用提供简单 HTTP 服务器接口的 Qt 5.5。它本质上类似于 Qt (http://doc.qt.io/qt-5/qtnetwork-fortuneserver-example.html) 提供的 Fortune Server 示例,但我们看到的是应用程序偶尔会在关闭时出现段错误,堆栈跟踪如下(线程 6 崩溃):
Thread 0:: Dispatch queue: com.apple.main-thread
0 libsystem_kernel.dylib 0x00007fff8deb4fca __open + 10
Thread 1:: Dispatch queue: com.apple.libdispatch-manager
0 libsystem_kernel.dylib 0x00007fff8deb6232 kevent64 + 10
1 libdispatch.dylib 0x00007fff90f0426e _dispatch_mgr_thread + 52
Thread 2:
0 libsystem_kernel.dylib 0x00007fff8deb594a __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x00007fff887833dd start_wqthread + 13
Thread 3:
0 libsystem_kernel.dylib 0x00007fff8deb594a __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x00007fff887833dd start_wqthread + 13
Thread 4:
0 libsystem_kernel.dylib 0x00007fff8deb594a __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x00007fff887833dd start_wqthread + 13
Thread 5:: Dispatch queue: com.apple.NSXPCConnection.m-user.com.apple.airportd
0 libsystem_platform.dylib 0x00007fff8923378d _os_lock_handoff_lock + 23
1 libobjc.A.dylib 0x00007fff83258906 objc_object::sidetable_clearDeallocating() + 64
2 libobjc.A.dylib 0x00007fff8323e651 objc_destructInstance + 145
3 libobjc.A.dylib 0x00007fff8323e595 object_dispose + 22
4 com.apple.CoreFoundation 0x00007fff84eea448 -[__NSArrayM dealloc] + 376
5 libobjc.A.dylib 0x00007fff8325889c objc_object::sidetable_release(bool) + 236
6 com.apple.Foundation 0x00007fff85747909 -[_NSXPCInterfaceMethodInfo dealloc] + 63
7 libobjc.A.dylib 0x00007fff8325889c objc_object::sidetable_release(bool) + 236
8 com.apple.CoreFoundation 0x00007fff84ed5db0 CFRelease + 304
9 com.apple.CoreFoundation 0x00007fff84ee5b92 __CFBasicHashDrain + 498
10 com.apple.CoreFoundation 0x00007fff84ed5e8e CFRelease + 526
11 com.apple.Foundation 0x00007fff8578dd7a -[NSXPCInterface dealloc] + 28
12 libobjc.A.dylib 0x00007fff8325889c objc_object::sidetable_release(bool) + 236
13 com.apple.Foundation 0x00007fff8578df0c -[NSXPCConnection dealloc] + 281
14 libobjc.A.dylib 0x00007fff8325889c objc_object::sidetable_release(bool) + 236
15 libsystem_blocks.dylib 0x00007fff8d3166e5 _Block_release + 196
16 libdispatch.dylib 0x00007fff90effe73 _dispatch_client_callout + 8
17 libdispatch.dylib 0x00007fff90f035cd _dispatch_queue_drain + 1100
18 libdispatch.dylib 0x00007fff90f03030 _dispatch_queue_invoke + 202
19 libdispatch.dylib 0x00007fff90f02bef _dispatch_root_queue_drain + 463
20 libdispatch.dylib 0x00007fff90f02a1c _dispatch_worker_thread3 + 91
21 libsystem_pthread.dylib 0x00007fff88785a9d _pthread_wqthread + 729
22 libsystem_pthread.dylib 0x00007fff887833dd start_wqthread + 13
Thread 6 Crashed:: Qt bearer thread
0 org.qt-project.QtNetwork 0x0000000100a541cb QNetworkConfigurationManagerPrivate::~QNetworkConfigurationManagerPrivate() + 107
1 org.qt-project.QtNetwork 0x0000000100a5431e QNetworkConfigurationManagerPrivate::~QNetworkConfigurationManagerPrivate() + 14
2 org.qt-project.QtCore 0x0000000100d72427 QObject::event(QEvent*) + 823
3 org.qt-project.QtCore 0x0000000100d49588 QCoreApplication::notify(QObject*, QEvent*) + 104
4 org.qt-project.QtCore 0x0000000100d4a212 QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) + 1058
5 org.qt-project.QtCore 0x0000000100d997db QEventDispatcherUNIX::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 59
6 org.qt-project.QtCore 0x0000000100d46c1c QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) + 412
7 org.qt-project.QtCore 0x0000000100b9c07e QThread::exec() + 110
8 org.qt-project.QtCore 0x0000000100b9fc02 QThreadPrivate::start(void*) + 338
9 libsystem_pthread.dylib 0x00007fff8878605a _pthread_body + 131
10 libsystem_pthread.dylib 0x00007fff88785fd7 _pthread_start + 176
11 libsystem_pthread.dylib 0x00007fff887833ed thread_start + 13
如您所见,线程 0 已完成 - 我们在 main 之外。我确定在处理资源时我没有调用一些清理代码,但我不知道它可能是什么。
如果不将我们所有的源代码放在这里,基本的调用链是:
class RestServer : public QObject {
RestServer::RestServer() {
_tcpServer = new QTcpServer(this);
}
void RestServer::listen(quint16 port)
{
if (!_tcpServer->listen(QHostAddress::LocalHost, port)) {
LOG_ERROR("RestServer", "Failed to start server at: " << port);
throw std::exception();
}
_portNum = _tcpServer->serverPort();
LOG_INFO("RestServer", "Server is listening at: " << _portNum);
connect(_tcpServer, SIGNAL(newConnection()), this, SLOT(connectSocket()));
}
然后在我们的测试代码中,我们基本上是这样做的:
void RestAPIServer_test::responseCallback(QNetworkReply *reply)
{
auto response = reply->readAll();
_uri = response;
reply->close();
QCoreApplication::exit();
}
TEST_F(RestAPIServer_test, urlWithPercents)
{
RestServer restServer();
restServer.listen(0);
quint16 port = restServer.serverPort();
// "widget/foo bar.txt"
QUrl serviceUrl(QString("http://localhost:%1/path/?path=widget%2Ffoo%20bar.txt").arg(port));
QNetworkAccessManager networkManager(this);
connect(&networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(responseCallback(QNetworkReply*)));
QNetworkRequest request(serviceUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
networkManager.get(request);
QCoreApplication::exec();
ASSERT_EQ(QString("path=widget/foo bar.txt"), _uri);
}
在父级堆栈上实例化 QObject
是不安全的。查看行
QNetworkAccessManager networkManager(this);
对象会被销毁两次,这是不可能的,所以你看到了崩溃。如下更改该行(当然,还必须更新变量 networkManager
的使用):
QNetworkAccessManager* networkManager=new QNetworkAccessManager(this);
问题似乎是由于 Qt 创建的一些与网络相关的线程在调用 QCoreApplication::quit() 后仍然 运行 一段时间,可能是因为 quit()从注册到 QTcpSocket 的回调函数中调用。解决方案是在调用退出后休眠 1 秒:
void RestAPIServer_test::responseCallback(QNetworkReply *reply)
{
auto response = reply->readAll();
_uri = response;
reply->close();
QCoreApplication::exit();
// This prevents a shutdown race condition where Qt service
// threads continue to run after the call to exit().
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
我们有一个 OS X C++ 应用程序,它使用提供简单 HTTP 服务器接口的 Qt 5.5。它本质上类似于 Qt (http://doc.qt.io/qt-5/qtnetwork-fortuneserver-example.html) 提供的 Fortune Server 示例,但我们看到的是应用程序偶尔会在关闭时出现段错误,堆栈跟踪如下(线程 6 崩溃):
Thread 0:: Dispatch queue: com.apple.main-thread
0 libsystem_kernel.dylib 0x00007fff8deb4fca __open + 10
Thread 1:: Dispatch queue: com.apple.libdispatch-manager
0 libsystem_kernel.dylib 0x00007fff8deb6232 kevent64 + 10
1 libdispatch.dylib 0x00007fff90f0426e _dispatch_mgr_thread + 52
Thread 2:
0 libsystem_kernel.dylib 0x00007fff8deb594a __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x00007fff887833dd start_wqthread + 13
Thread 3:
0 libsystem_kernel.dylib 0x00007fff8deb594a __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x00007fff887833dd start_wqthread + 13
Thread 4:
0 libsystem_kernel.dylib 0x00007fff8deb594a __workq_kernreturn + 10
1 libsystem_pthread.dylib 0x00007fff887833dd start_wqthread + 13
Thread 5:: Dispatch queue: com.apple.NSXPCConnection.m-user.com.apple.airportd
0 libsystem_platform.dylib 0x00007fff8923378d _os_lock_handoff_lock + 23
1 libobjc.A.dylib 0x00007fff83258906 objc_object::sidetable_clearDeallocating() + 64
2 libobjc.A.dylib 0x00007fff8323e651 objc_destructInstance + 145
3 libobjc.A.dylib 0x00007fff8323e595 object_dispose + 22
4 com.apple.CoreFoundation 0x00007fff84eea448 -[__NSArrayM dealloc] + 376
5 libobjc.A.dylib 0x00007fff8325889c objc_object::sidetable_release(bool) + 236
6 com.apple.Foundation 0x00007fff85747909 -[_NSXPCInterfaceMethodInfo dealloc] + 63
7 libobjc.A.dylib 0x00007fff8325889c objc_object::sidetable_release(bool) + 236
8 com.apple.CoreFoundation 0x00007fff84ed5db0 CFRelease + 304
9 com.apple.CoreFoundation 0x00007fff84ee5b92 __CFBasicHashDrain + 498
10 com.apple.CoreFoundation 0x00007fff84ed5e8e CFRelease + 526
11 com.apple.Foundation 0x00007fff8578dd7a -[NSXPCInterface dealloc] + 28
12 libobjc.A.dylib 0x00007fff8325889c objc_object::sidetable_release(bool) + 236
13 com.apple.Foundation 0x00007fff8578df0c -[NSXPCConnection dealloc] + 281
14 libobjc.A.dylib 0x00007fff8325889c objc_object::sidetable_release(bool) + 236
15 libsystem_blocks.dylib 0x00007fff8d3166e5 _Block_release + 196
16 libdispatch.dylib 0x00007fff90effe73 _dispatch_client_callout + 8
17 libdispatch.dylib 0x00007fff90f035cd _dispatch_queue_drain + 1100
18 libdispatch.dylib 0x00007fff90f03030 _dispatch_queue_invoke + 202
19 libdispatch.dylib 0x00007fff90f02bef _dispatch_root_queue_drain + 463
20 libdispatch.dylib 0x00007fff90f02a1c _dispatch_worker_thread3 + 91
21 libsystem_pthread.dylib 0x00007fff88785a9d _pthread_wqthread + 729
22 libsystem_pthread.dylib 0x00007fff887833dd start_wqthread + 13
Thread 6 Crashed:: Qt bearer thread
0 org.qt-project.QtNetwork 0x0000000100a541cb QNetworkConfigurationManagerPrivate::~QNetworkConfigurationManagerPrivate() + 107
1 org.qt-project.QtNetwork 0x0000000100a5431e QNetworkConfigurationManagerPrivate::~QNetworkConfigurationManagerPrivate() + 14
2 org.qt-project.QtCore 0x0000000100d72427 QObject::event(QEvent*) + 823
3 org.qt-project.QtCore 0x0000000100d49588 QCoreApplication::notify(QObject*, QEvent*) + 104
4 org.qt-project.QtCore 0x0000000100d4a212 QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) + 1058
5 org.qt-project.QtCore 0x0000000100d997db QEventDispatcherUNIX::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 59
6 org.qt-project.QtCore 0x0000000100d46c1c QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) + 412
7 org.qt-project.QtCore 0x0000000100b9c07e QThread::exec() + 110
8 org.qt-project.QtCore 0x0000000100b9fc02 QThreadPrivate::start(void*) + 338
9 libsystem_pthread.dylib 0x00007fff8878605a _pthread_body + 131
10 libsystem_pthread.dylib 0x00007fff88785fd7 _pthread_start + 176
11 libsystem_pthread.dylib 0x00007fff887833ed thread_start + 13
如您所见,线程 0 已完成 - 我们在 main 之外。我确定在处理资源时我没有调用一些清理代码,但我不知道它可能是什么。
如果不将我们所有的源代码放在这里,基本的调用链是:
class RestServer : public QObject {
RestServer::RestServer() {
_tcpServer = new QTcpServer(this);
}
void RestServer::listen(quint16 port)
{
if (!_tcpServer->listen(QHostAddress::LocalHost, port)) {
LOG_ERROR("RestServer", "Failed to start server at: " << port);
throw std::exception();
}
_portNum = _tcpServer->serverPort();
LOG_INFO("RestServer", "Server is listening at: " << _portNum);
connect(_tcpServer, SIGNAL(newConnection()), this, SLOT(connectSocket()));
}
然后在我们的测试代码中,我们基本上是这样做的:
void RestAPIServer_test::responseCallback(QNetworkReply *reply)
{
auto response = reply->readAll();
_uri = response;
reply->close();
QCoreApplication::exit();
}
TEST_F(RestAPIServer_test, urlWithPercents)
{
RestServer restServer();
restServer.listen(0);
quint16 port = restServer.serverPort();
// "widget/foo bar.txt"
QUrl serviceUrl(QString("http://localhost:%1/path/?path=widget%2Ffoo%20bar.txt").arg(port));
QNetworkAccessManager networkManager(this);
connect(&networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(responseCallback(QNetworkReply*)));
QNetworkRequest request(serviceUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
networkManager.get(request);
QCoreApplication::exec();
ASSERT_EQ(QString("path=widget/foo bar.txt"), _uri);
}
在父级堆栈上实例化 QObject
是不安全的。查看行
QNetworkAccessManager networkManager(this);
对象会被销毁两次,这是不可能的,所以你看到了崩溃。如下更改该行(当然,还必须更新变量 networkManager
的使用):
QNetworkAccessManager* networkManager=new QNetworkAccessManager(this);
问题似乎是由于 Qt 创建的一些与网络相关的线程在调用 QCoreApplication::quit() 后仍然 运行 一段时间,可能是因为 quit()从注册到 QTcpSocket 的回调函数中调用。解决方案是在调用退出后休眠 1 秒:
void RestAPIServer_test::responseCallback(QNetworkReply *reply)
{
auto response = reply->readAll();
_uri = response;
reply->close();
QCoreApplication::exit();
// This prevents a shutdown race condition where Qt service
// threads continue to run after the call to exit().
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}