2010-12-01 2 views
1

ich qt 4.5.3 bin mit der SQLite-Datenbank zugreifen zu können, wie folgt aus:Datenbank öffnen in ungültige Stelle verursacht Speicherverlust

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

Nun, wenn ich versuchen, diesen Fall wie diesem zu testen (ich benutze CxxTest) :

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

ich ein Speicherleck von valgrind gemeldet bekommen:

<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> 

Wer weiß, wie dieses Leck zu beheben?

+0

@DumbCode Ich versuche nicht, irgendetwas zu optimieren, aber ich versuche meinen Code zu testen. Das Problem besteht darin, den Fall zu testen, wenn beim Öffnen der Datenbank ein Problem aufgetreten ist. Wie Sie im Beispiel sehen können, übergebe ich "/" (das Stammverzeichnis) als Datenbanknamen, um diesen Fall zu emulieren. –

Antwort

3

Hatte ein Durchsuchen der Qt und SQLite Quellen ... interessant.

das Handbuch für sqlite3_open16() Lesen, http://www.sqlite.org/c3ref/open.html findet man das folgende Zitat:

Unabhängig davon, ob ein Fehler auftritt, wenn es geöffnet wird, mit der Datenbankverbindung Griff zugeordneten Ressourcen sollten, indem sie freigegeben werden, um sqlite3_close() wenn es nicht mehr benötigt wird.

QSQLiteDriver::close() scheint, dass, http://qt.gitorious.org/qt/qt/blobs/4.7/src/sql/drivers/sqlite/qsql_sqlite.cpp, nur im Fall der offenen Aufruf zu Erfolg beschieden war. In der SQLite-Dokumentation wird möglicherweise angegeben, dass sqlite3_close() in beiden Fällen aufgerufen werden sollte.

Auf der anderen Seite, behauptet http://www.sqlite.org/c3ref/close.html ein No-Op sein, wenn NULL für ein Handle übergeben wird (was wäre, wenn das Öffnen fehlgeschlagen wäre). Ein Blick in den SQLite-Quellcode (DIY - ich kenne keine Web-Source-Browser-Schnittstelle dafür) bestätigt, dass es nur zurückkommt, wenn es mit NULL aufgerufen wird.

Nun - jetzt für den winzigsten Spaß der Ausgabe ...

Naiv, geht man davon aus, dass ein Ausfall von sqlite3_open*() einen NULL db Griff bedeuten würde. Aber laut Quellen von SQLite, lesen Sie openDatabase() in main.c, das ist nicht wahr - der Aufruf kann fehlschlagen, aber immer noch eine nicht-NULL Db-Handle zurückgeben.

Qt sieht aus wie es annimmt, dass ein Fehler beim Öffnen der DB-Verbindung bedeutet, dass ein NULL Db-Handle empfangen wird. Aber das macht SQLite nicht. Die Dokumentation könnte jedoch klarer sein.

Versuchen Sie, diese in QSQLiteDriver::open() schließen und sehen, ob es das Leck behebt. Wenn ja, einen Fehler mit den Qt-Leuten einreichen und einen weiteren mit den SQLite-Leuten, um die Dokumentation zu klären ;-)

+0

Noch besser erstellen Sie ein RAII-Objekt, das das für Sie tun wird. Ich würde nicht unbedingt erwarten, dass es nicht ausläuft, aber ich würde erwarten, dass Qt automatische Objekte bereitstellt, die ihre Ressourcen ordnungsgemäß bereinigen. – CashCow

+0

@CashCow: In jedem Fall, wenn SQLite das Schließen aller Nicht-NULL-Handles verlangt, dann ist es ein Fehler in Qt, wenn es das nicht tut. Keine große Sache; Bugs sind dort behoben werden. –

+0

@CashCow: QSQL verwendet standardmäßig RAII, aber die Lösung des Problems erfordert, dass die Implementierung und die Entwurfsabsicht übereinstimmen. –

0

Ihr Code

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

sieht ziemlich seltsam, nicht sicher, warum es kompiliert. Ich sehe keinen Konstruktor von QSqlDatabase, der QSqlDatabase * als Parameter verwendet.

Sie haben QSqlDatabase :: addDatabase aufgerufen, die QSqlDatabase * zurückgibt und dann eine andere QSqlDatabase mit new erstellt und diese als Parameter übergeben hat.

Statt auto_ptr können Sie boost :: shared_ptr dann zurückgesetzt mit

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

dass removeDatabase Vorsicht kann eine Ressource Leck verlassen, wenn es offene Anfragen darauf.

+0

Werfen Sie einen Blick zurück. Der Konstruktor von auto_ptr erwartet den Zeiger. In der open() - Methode öffne ich die Verbindung und versuche, die Datenbank zu öffnen. Im Destruktor zerstöre ich das QSqlDatabase-Objekt und entferne dann die Verbindung, daher leckt keine Ressource. –

+0

Ich habe vergessen zu erwähnen, dass diese Klasse nicht kopierbar sein sollte (sie erbt von boost :: noncopyable), daher ist die Verwendung von shared_ptr dasselbe wie die Verwendung von auto_ptr in diesem Fall. –

+0

Sie rufen neu in QSqlDatabase, die ein Objekt erstellt. Obwohl die Schnittstelle keinen öffentlichen Konstruktor für QSqlDatabase bereitstellt, wird erwartet, dass Sie QSqlDatabase :: addDatabase zum Erstellen aufrufen. Daher bin ich überrascht, dass Ihr Code kompiliert, wenn ich nicht die falsche Dokumentation anschaue. Sie können das Ergebnis von QSqlDatabase :: addDatabase nicht einfach an auto_ptr übergeben, da Sie removeDatabase verwenden sollen, um es zu schließen, nicht einfach löschen – CashCow