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
main.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->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->databbase(std::move(dbStr))
。对传递给database::operator[]
的字符串执行相同的操作。这能解决你的问题吗?如果是这样,我将深入研究 QString
到 std::string
是如何在呼叫站点绑定的。
@acm 今天我尝试了你的建议,但是当我按照你在评论中的建议编写代码时,它仍然会出现同样的崩溃......
嗯,我能想到的唯一下一步就是编写一个 MCVE,这样我就可以在我身边尝试了。***.com/help/mcve以上是关于MongoDB C++ 驱动程序稳定分支在 Qt 应用程序中崩溃的主要内容,如果未能解决你的问题,请参考以下文章
8个维度的c++后台开发技能树如何学习?为什么不推荐c/c++程序员做mfc/qt开发
如何在启动 C++ 应用程序时检查 mongodb 是不是正在运行