SQLITE_忙碌与内存中的DB和SQLiteCPP库

ars1skjm  于 7个月前  发布在  SQLite
关注(0)|答案(1)|浏览(51)

通过下面这个非常简单的C++程序(从真实的东西简化而来),我得到了:

test: ./SQLiteCpp-3.3.0/src/Database.cpp:92: void SQLite::Database::Deleter::operator()(sqlite3*): Assertion `0 == ret && "database is locked"' failed.
Aborted (core dumped)

字符串
当库试图在Database的析构函数中使用sqlite3_close()关闭与数据库的连接时,就会发生这种情况。
实际上,ret的值是SQLITE_忙碌(5)。SQLITE文档表明这通常是指向另一个与同一数据库有连接的进程/线程,但是你可以看到这里不是这样。
main.cpp:

#include "nl.hpp"
int main()
{
    auto nl = new NL();
    delete nl;  // Here only to illustrate the problem.
}


nl.hpp:

#include "SQLiteCpp/SQLiteCpp.h"
using namespace std;
class NL
{
public:
    NL();
    unique_ptr<SQLite::Statement> sqlst_insert_net;
    optional<SQLite::Database> db;
    void compile_sql_statements();
    void create_db();

};


nl.cpp:

#include <nl.hpp>

NL::NL() {
    this->create_db();
    this->compile_sql_statements();
}

void NL::create_db() {
    this->db.emplace(SQLite::Database(":memory:", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE));

    this->db->exec(
        "CREATE TABLE net(id INTEGER PRIMARY KEY, name TEXT UNIQUE);"
    );
}

void NL::compile_sql_statements() {
    this->sqlst_insert_net = make_unique<SQLite::Statement>(
        *(this->db), "INSERT INTO net (name) VALUES (?)");
}


在这里起作用的一些事情似乎是:

  • 如果我删除delete nl;,问题不会发生。我不是C++Maven,但我会想象nl对象在main()结束时被销毁,对吗?
  • 如果我不准备这些语句,只要从NL的构造函数中删除this->compile_sql_statements();,问题就不会发生。

以上两点是必要的。这只是一个非常简化的代码,只是为了复制问题。NL的示例保存在真实的程序中的shared_pointer中,因此最终必须调用析构函数。并且还需要准备好的语句。
我以前使用过看起来相同的代码,我没有这个问题。

编辑:附加信息。

下面是堆栈跟踪:

SQLite::Database::Deleter::operator()(SQLite::Database::Deleter * const this, sqlite3 * apSQLite) (.../SQLiteCpp-3.3.0/src/Database.cpp:92)
std::unique_ptr<sqlite3, SQLite::Database::Deleter>::~unique_ptr(std::unique_ptr<sqlite3, SQLite::Database::Deleter> * const this) (/usr/include/c++/9/bits/unique_ptr.h:292)
SQLite::Database::~Database(SQLite::Database * const this) (.../SQLiteCpp-3.3.0/include/SQLiteCpp/Database.h:264)
std::_Optional_payload_base<SQLite::Database>::_M_destroy(std::_Optional_payload_base<SQLite::Database> * const this) (/usr/include/c++/9/optional:257)
std::_Optional_payload_base<SQLite::Database>::_M_reset(std::_Optional_payload_base<SQLite::Database> * const this) (/usr/include/c++/9/optional:277)
std::_Optional_payload<SQLite::Database, false, false, false>::~_Optional_payload(std::_Optional_payload<SQLite::Database, false, false, false> * const this) (/usr/include/c++/9/optional:398)
std::_Optional_base<SQLite::Database, false, false>::~_Optional_base(std::_Optional_base<SQLite::Database, false, false> * const this) (/usr/include/c++/9/optional:471)
std::optional<SQLite::Database>::~optional(std::optional<SQLite::Database> * const this) (/usr/include/c++/9/optional:656)
NL::~NL(NL * const this) (.../src/nl.hpp:8)
main() (.../src/test.cpp:5)


以及Assert失败的函数(来自SQLiteCpp的Database.cpp):

// Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion.
void Database::Deleter::operator()(sqlite3* apSQLite)
{
    const int ret = sqlite3_close(apSQLite); // Calling sqlite3_close() with a nullptr argument is a harmless no-op.

    // Avoid unreferenced variable warning when build in release mode
    (void) ret;

    // Only case of error is SQLITE_BUSY: "database is locked" (some statements are not finalized)
    // Never throw an exception in a destructor :
    SQLITECPP_ASSERT(SQLITE_OK == ret, "database is locked");  // See SQLITECPP_ENABLE_ASSERT_HANDLER
}


如前所述,ret==5 (SQLITE_BUSY)
这是 Package 库:SQLiteC++

编辑:附加信息。

正如注解中所建议的,语句没有正确终止。这里显示的析构函数(其中sqlite3_finalize(stmt)是)从未被调用:

// Prepare SQLite statement object and return shared pointer to this object
Statement::TStatementPtr Statement::prepareStatement()
{
    sqlite3_stmt* statement;
    const int ret = sqlite3_prepare_v2(mpSQLite, mQuery.c_str(), static_cast<int>(mQuery.size()), &statement, nullptr);
    if (SQLITE_OK != ret)
    {
        throw SQLite::Exception(mpSQLite, ret);
    }
    return Statement::TStatementPtr(statement, [](sqlite3_stmt* stmt)
        {
            sqlite3_finalize(stmt);
        });
}


TStatementPtr是存在于Statement中的shared_pointer。Statement的析构函数只是默认值。所以看起来Statement没有被销毁。

1hdlvixo

1hdlvixo1#

这个问题非常微妙。正如@Shawn在对这个问题的评论中所建议的那样,在关闭与数据库的连接时,准备好的语句还没有完成。实际上,Statement的析构函数应该调用sqlite3_finalize(stmt),但它从未被调用。这是因为~Database被首先调用。
这仅仅是因为NL中声明StatementDatabase的顺序。这决定了成员被销毁的顺序。
首先声明Database,然后声明Statement,会导致Statement首先被销毁,从而解决问题。

相关问题