Открытие базы данных в неверном месте приводит к утечке памяти - PullRequest
1 голос
/ 01 декабря 2010

Я использую qt 4.5.3 для доступа к базе данных sqlite, например:

class db : private boost::noncopyable
{
 public:
  db( QString file ) : filename( file ),
                       realdb( NULL ),
                       theConnectionEstablished( false )
  {
  }
  ~db()
  {
    if ( NULL != realdb.get() )
    {
      realdb.reset( NULL );
    }
    if ( theConnectionEstablished )
    {
      QSqlDatabase::removeDatabase( "ConnName" );
    }
  }

  void open()
  {
    realdb.reset( new QSqlDatabase( QSqlDatabase::addDatabase( "QSQLITE", "ConnName" ) ) );
    theConnectionEstablished = true;

    // open the db
    realdb->setDatabaseName( filename );
    if ( ! realdb->open() )
    {
        const QSqlError dbError = realdb->lastError();
        const QString errorDesc = "Error opening the database : " + filename +
                                  "\nDatabase error : " + dbError.databaseText() +
                                  "\nDatabase driver error : " + dbError.driverText();

        // DatabaseError is a class type which accepts the std::string for logging purposes
        throw DatabaseError( errorDesc.toStdString() );
    }
  }

  const QString filename;
  std::auto_ptr<QSqlDatabase> realdb;
  bool theConnectionEstablished;
};

Теперь, если я попытаюсь проверить этот случай следующим образом (я использую cxxtest):

void test_failed_connection()
{
  db obj( "/" );
  TS_ASSERT_THROWS( obj.open(), DatabaseError );
}

Я получаю сообщение об утечке памяти от Valgrind:

<error>
  <unique>0x5b</unique>
  <tid>1</tid>
  <kind>Leak_DefinitelyLost</kind>
  <what>986 (384 direct, 602 indirect) bytes in 1 blocks are definitely lost in loss record 23 of 23</what>
  <leakedbytes>986</leakedbytes>
  <leakedblocks>1</leakedblocks>
  <stack>
    <frame>
      <ip>0x4006D3E</ip>
      <obj>/opt/valgrind341/lib/valgrind/x86-linux/vgpreload_memcheck.so</obj>
      <fn>malloc</fn>
      <dir>/home/slawomir/valgrind-3.4.1/build/valgrind-3.4.1/coregrind/m_replacemalloc</dir>
      <file>vg_replace_malloc.c</file>
      <line>207</line>
    </frame>
    <frame>
      <ip>0x67FADC4</ip>
      <obj>/usr/lib/libsqlite3.so.0.8.6</obj>
      <fn>sqlite3_malloc</fn>
    </frame>
    <frame>
      <ip>0x67FAF13</ip>
      <obj>/usr/lib/libsqlite3.so.0.8.6</obj>
    </frame>
    <frame>
      <ip>0x6816DA3</ip>
      <obj>/usr/lib/libsqlite3.so.0.8.6</obj>
    </frame>
    <frame>
      <ip>0x68175FD</ip>
      <obj>/usr/lib/libsqlite3.so.0.8.6</obj>
      <fn>sqlite3_open16</fn>
    </frame>
    <frame>
      <ip>0x40DDEF9</ip>
      <obj>/usr/lib/qt4/plugins/sqldrivers/libqsqlite.so</obj>
    </frame>
    <frame>
      <ip>0x7F34AE0</ip>
      <obj>/usr/lib/libQtSql.so.4.5.2</obj>
      <fn>QSqlDatabase::open()</fn>
    </frame>
    </frame>
  </stack>
</error>

Кто-нибудь знает, как исправить эту утечку?

Ответы [ 2 ]

3 голосов
/ 01 декабря 2010

Был просмотр источников Qt и sqlite ... интересно.

Читая руководство для sqlite3_open16(), http://www.sqlite.org/c3ref/open.html,, можно найти следующую цитату:

Независимо от того, возникает ли ошибка при ее открытии, ресурсы, связанные с дескриптором подключения к базе данных, должны быть освобождены путем передачи ее в sqlite3_close (), когда она больше не требуется.

QSQLiteDriver::close(), кажется, называет это, http://qt.gitorious.org/qt/qt/blobs/4.7/src/sql/drivers/sqlite/qsql_sqlite.cpp, только в случае успешного открытия. Документация SQLite может указывать, что sqlite3_close() должен называться в любом случае.

С другой стороны, http://www.sqlite.org/c3ref/close.html утверждает, что он не работает, если для дескриптора передано NULL (что было бы, если открытие не удалось). Взгляд на исходный код SQLite (сделай сам - я не знаю интерфейс браузера для него) подтверждает, что он просто возвращается, если вызывается с NULL.

Ну что ж, теперь ради забавы ...

Наивно, предполагается, что сбой sqlite3_open*() будет означать ручку NULL дБ. Но, согласно источникам SQLite, прочитайте openDatabase() в main.c, это не так - вызов может завершиться неудачно, но все равно вернет вам дескриптор не-1030 * db.

Похоже, Qt предполагает, что сбой при открытии соединения с БД подразумевает получение дескриптора NULL дБ. Но это не то, что делает SQLite. Документация может быть более понятной.

Попробуйте добавить это близко к QSQLiteDriver::open() и посмотрите, исправит ли это утечку. Если это так, отправьте сообщение об ошибке ребятам из Qt, а другое - людям из SQLite, чтобы уточнить документацию; -)

0 голосов
/ 01 декабря 2010

Ваш код

realdb.reset( new QSqlDatabase( QSqlDatabase::addDatabase( "QSQLITE", "ConnName" ) ) );

выглядит довольно странно, не уверен, почему он компилируется. Я не вижу конструктор QSqlDatabase, который принимает QSqlDatabase * в качестве параметра.

Вы вызвали QSqlDatabase :: addDatabase, который возвращает QSqlDatabase *, затем сконструировали другую QSqlDatabase, используя new и передав ее в качестве параметра.

Вместо auto_ptr вы можете использовать boost :: shared_ptr, а затем сбросить с помощью

realdb.reset(QSqlDatabase::addDatabase( "QSQLITE", "ConnName" ), QSqlDatabase::removeDatabase);

Остерегайтесь того, что removeDatabase может оставить утечку ресурса, если к нему есть открытые запросы.

...