如何使用Qt Network类防止关闭竞争条件

时间:2016-03-17 20:12:52

标签: c++ qt qnetworkaccessmanager

我们有一个OS X C ++应用程序,使用Qt 5.5提供简单的HTTP服务器接口。它基本上类似于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已经完成 - 我们在主要之外。我确定有一些清理代码,当我们处理我们的资源时,我没有打电话,但我不知道它是什么。

如果不把我们所有的资源放在这里,基本的调用链就是:

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);
}

2 个答案:

答案 0 :(得分:0)

在父堆栈上实例化QObject是不安全的。看一下

QNetworkAccessManager networkManager(this);

对象将被销毁两次,这是不可能的,所以你看到了崩溃。如下更改该行(当然,还必须更新变量networkManager的使用):

QNetworkAccessManager* networkManager=new QNetworkAccessManager(this);

答案 1 :(得分:0)

问题似乎是由于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));
}