MongoDB C++ 驱动程序稳定分支在 Qt 应用程序中崩溃

Posted

技术标签:

【中文标题】MongoDB C++ 驱动程序稳定分支在 Qt 应用程序中崩溃【英文标题】:MongoDB C++ driver stable branch crashes in Qt application 【发布时间】:2018-01-11 20:30:22 【问题描述】:

我正在使用 Qt 5.7.0 和 MSVC 2015 64 位编译器开发一个 Web 应用程序。我使用 Qt Creator 作为 IDE。我设法构建并将 mongoc、mongocxx 和 bsoncxx 库与系统链接。这些库是在发布模式下构建的,具有与 Qt Web 应用程序相同的编译器和运行时。我使用了 1.9.0 版的 C 驱动程序和 3.1 版的稳定 C++ 驱动程序。驱动程序是根据 MongoDB 提供的说明和标准设置构建的。我只将两个驱动程序的发布版本与我的应用程序相关联。

在我的应用程序的调试版本中,一切正常。但是,在 Web 应用程序的发布版本中,我设法在应用程序的主线程中创建了一个实例和连接池,但是当我尝试在子线程中从该池中获取连接时,系统崩溃了。下面的代码示例中指出了崩溃的确切位置。

我不确定这是驱动程序中的错误还是我的多线程代码中的错误,因此我在下面包含了相关的 sn-ps 代码。 MongoPool 对象是在主线程中创建的。 MongoConnector 对象在收到新用户请求时在子线程中创建。请注意,我按照 cmets 中的要求尽可能地减少了代码。任何希望收到完整申请代码的人,请与我联系,我很乐意为您提供。

The error message and stack trace

The stack trace

ma​​in.cpp

#include "mongopool.h"

MongoPool *pool;

int main(int argc, char *argv[])

    QCoreApplication app(argc,argv);

    // Initialize MongoDB
    pool = new MongoPool();

    app.exec();


#include "mongoconnector.h"

extern MongoPool *pool;

MongoConnector::MongoConnector()



bool MongoConnector::storeDocument(QString dbName,
                                   QString collectionName,
                                   QString docID,
                                   QString doc)


    try 
        std::cout << "Creating database connection" << "\n";

        mongocxx::pool::entry client = pool->get_connection();
        if(!client)
        
            std::cout << "No Connection!";
        

        std::cout << "Obtaining database and collection" << "\n";

        // HERE THE CRASH HAPPENS!!!
        mongocxx::database dataDB = client->database(dbName.toLatin1().toStdString());
        mongocxx::collection dataCollection = dataDB[collectionName.toLatin1().toStdString()];
        // ....

     catch (const std::exception& xcp) 
        std::cout << "connection failed: " << xcp.what() << "\n";
    

    return true;


#include "mongopool.h"

// INCLUDE STATEMENTS ARE OMITTED FOR MONGOCXX AND BSONCXX

class MongoConnector

public:
    MongoConnector();

    bool storeDocument(QString dbName,
                       QString collectionName,
                       QString docID,
                       QString doc);
;

#include "mongopool.h"

MongoPool::MongoPool()

    using bsoncxx::builder::stream::document;

    auto uri = mongocxx::urimongocxx::uri::k_default_uri;
    configure(std::move(uri));

    try 
        mongocxx::pool::entry client = get_connection();

        auto admin = client->database("admin");

        document ismaster;
        ismaster << "isMaster" << 1;

        auto result = admin.run_command(ismaster.view());

     catch (const std::exception& xcp) 
        std::cout << "connection failed: " << xcp.what() << "\n";
    


void MongoPool::configure(mongocxx::uri uri) 
    class noop_logger : public mongocxx::logger 
    public:
        virtual void operator()(mongocxx::log_level, mongocxx::stdx::string_view,
                                mongocxx::stdx::string_view) noexcept 
        
    ;

    auto instance =
            mongocxx::stdx::make_unique<mongocxx::instance>(mongocxx::stdx::make_unique<noop_logger>());

    configure(std::move(instance),
              mongocxx::stdx::make_unique<mongocxx::pool>(std::move(uri)));


void MongoPool::configure(std::unique_ptr<mongocxx::instance> instance,
                               std::unique_ptr<mongocxx::pool> pool) 
    _instance = std::move(instance);
    _pool = std::move(pool);


mongocxx::pool::entry MongoPool::get_connection() 
    return _pool->acquire();


mongocxx::stdx::optional<mongocxx::pool::entry> MongoPool::try_get_connection() 
    return _pool->try_acquire();


// INCLUDE STATEMENTS ARE OMITTED FOR MONGOCXX AND BSONCXX

class MongoPool

public:
    MongoPool();

    void configure(mongocxx::uri uri);
    void configure(std::unique_ptr<mongocxx::instance> instance,
                   std::unique_ptr<mongocxx::pool> pool);
    mongocxx::pool::entry get_connection();
    mongocxx::stdx::optional<mongocxx::pool::entry> try_get_connection();

private:
    std::unique_ptr<mongocxx::instance> _instance;
    std::unique_ptr<mongocxx::pool> _pool;

;

【问题讨论】:

这个池和其他对象是否可以像这样使用线程安全? 你能分享堆栈跟踪吗? 为什么要包装 mongocxx 类? 将其编辑为MCVE,没有人会经历这么多的代码。 我想你也在 mongodb-dev 上问过这个问题。我同意上述 cmets:提供堆栈跟踪、驱动程序版本、驱动程序的构建信息,并使用您的工具链提供的诊断工具来查找未定义的行为、内存错误和数据竞争。上面要求的 MCVE 也更有可能揭示问题。 【参考方案1】:

查看堆栈跟踪图像和 mongocxx 驱动程序代码后,以下代码仅打印“无连接”,但如果客户端无效,则不会返回 false(或断言或退出),这是您应该做的

if(!client)
    
        std::cout << "No Connection!";
    

很明显,在这一行mongocxx::database dataDB = client-&gt;database(dbName.toLatin1().toStdString()); 如果client 是一个无效的指针,那么你会崩溃

此外,基于示例(第 35 行 https://github.com/mongodb/mongo-cxx-driver/blob/master/examples/mongocxx/mongodb.com/usage_overview.cpp ) 最好把它写成 mongocxx::database dataDB = (*client)[dbName.toLatin1().toStdString()];

【讨论】:

我同意,这似乎是最有可能的解释。不可能从用户发布的“堆栈跟踪”中确定(为什么人们坚持发布屏幕截图?)。如果客户端指针为 NULL,在调试器中应该很明显。 @acm 嗯,不知道为什么操作员会发布屏幕截图,我只是在寻找堆栈跟踪【参考方案2】:

最后,我设法获得了用于获取数据库和集合的代码。现在可以在调试和发布模式下插入文档。我知道我的解决方案可能并不完美,但至少它可以解决这部分问题。我检查了 mongo shell,插入确实正确执行。

    auto dbNameString = dbName.toStdString();
    auto collectionNameString = collectionName.toStdString();

    char *dbNameArray = new char[dbNameString.length()+1];
    for(int i=0; i<dbNameString.length(); i++)
    
        dbNameArray[i] = dbNameString.at(i);
    
    dbNameArray[dbNameString.length()] = '\0';

    char *collectionArray = new char[collectionNameString.length()+1];
    for(int i=0; i<collectionNameString.length(); i++)
    
        collectionArray[i] = collectionNameString.at(i);
    
    collectionArray[collectionNameString.length()] = '\0';

    mongocxx::database dataDB = client->database(dbNameArray);
    mongocxx::collection dataCollection = dataDB[collectionArray];

    std::string docDB = doc.toLatin1().toStdString();
    bsoncxx::document::value obj = bsoncxx::from_json(docDB.c_str());
    std::string id = docID.toStdString();

    auto builder = bsoncxx::builder::stream::document;
    bsoncxx::document::value doc_value = builder
            << "_id" << id.c_str()
            << "doc" << obj
            << bsoncxx::builder::stream::finalize;

    bsoncxx::document::view view = doc_value.view();
    auto insert_result = dataCollection.insert_one(view);

但是,当我现在尝试从数据库中检索文档时,这在调试模式下工作正常,但在发布模式下崩溃。当我尝试访问返回对象的内容时,我会遇到内存访问冲突或损坏的堆异常。此外,当尝试打印查询结果的内容时,文档已完全损坏。

    auto builder = bsoncxx::builder::stream::document;
    bsoncxx::document::value doc_value = builder
            << "_id" << id.c_str()
            << bsoncxx::builder::stream::finalize;
    bsoncxx::document::view view = doc_value.view();

    bsoncxx::stdx::optional<bsoncxx::document::value> maybe_result =
                dataCollection.find_one(view);

    if(maybe_result)
    
        // HERE THE CRASH HAPPENS
        std::cout << bsoncxx::to_json(*maybe_result) << "\n";
    

我仔细检查了我是如何构建 MongoCXX 和 MongoC 库的,它们都是使用动态运行时和发布模式构建的。我没有链接任何静态库,因为我的 Qt 应用程序使用动态运行时。

会不会是 Qt 和 MongoCXX 不兼容?

【讨论】:

最后一个示例中的堆栈跟踪是什么? 您不能混合调试和零售版本。如果您将驱动程序构建为发布版,您也需要以这种方式构建您的应用程序。如果您将驱动程序构建为调试,请将应用程序构建为调试。 当你传入 C 风格的字符串时它工作的原因是你不再通过 ABI 传递 std::string。 你也在泄漏内存。【参考方案3】:

我在条件 if 块中添加了 return 语句,但这并没有解决问题,因为打印“无连接”字符串和 return 语句都不会执行......执行总是跳过条件 if 块...

我也修改了数据库请求的代码行,但结果还是一样...

问题仍然存在,为什么从池中获取的客户端在子线程中不可用?

编辑: 今天我设法发现问题与将 Qt 中使用的类型转换为 MongoDB 驱动程序中使用的类型有关。

当我替换以下代码行时:

mongocxx::database dataDB = client->database(dbName.toLatin1().toStdString());
mongocxx::collection dataCollection = dataDB[collectionName.toLatin1().toStdString()];

通过以下代码行一切正常:

mongocxx::database dataDB = client->database("testdb");
mongocxx::collection dataCollection = dataDB["testcollection"];

我尝试以多种方式转换 QString 以用作驱动程序方法调用的参数,但尚未设法使其正常工作。有人知道如何正确地将 QString 变量转换为 MongoDB C++ 驱动程序的参数吗?

【讨论】:

我在您的代码中看不到任何与线程有关的内容。请根据此线程中的多个请求将其分解为 minimal 可编译示例。理想情况下不涉及 QT。 @acm 我编辑了上面的帖子以反映我的最新发现。这是足够的信息还是我需要提供更多详细信息? 嗯。我有一个想法,这与临时对象的生命周期和 view_or_value 参数类型有关。也许绑定没有按预期工作。我不熟悉QString,但可以尝试使用auto dbStr = dbNAme.toLatin1().toStdString() 捕获转换为std::string 的结果,然后将其移至database 的调用中,如client-&gt;databbase(std::move(dbStr))。对传递给database::operator[] 的字符串执行相同的操作。这能解决你的问题吗?如果是这样,我将深入研究 QStringstd::string 是如何在呼叫站点绑定的。 @acm 今天我尝试了你的建议,但是当我按照你在评论中的建议编写代码时,它仍然会出现同样的崩溃...... 嗯,我能想到的唯一下一步就是编写一个 MCVE,这样我就可以在我身边尝试了。***.com/help/mcve

以上是关于MongoDB C++ 驱动程序稳定分支在 Qt 应用程序中崩溃的主要内容,如果未能解决你的问题,请参考以下文章

8个维度的c++后台开发技能树如何学习?为什么不推荐c/c++程序员做mfc/qt开发

如何连接两个程序(c++,qt)

如何在启动 C++ 应用程序时检查 mongodb 是不是正在运行

如何在 Android 上重新启动 Qt 或 c++ 中的程序?

Qt5、C++:未加载 QMYSQL 驱动程序

使用新的 MongoDB C++ 驱动程序创建索引