WebCore: Turn on AUTO_VACUUM = INCREMENTAL for all HTML5 databases.
authordumi@chromium.org <dumi@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Apr 2010 01:24:12 +0000 (01:24 +0000)
committerdumi@chromium.org <dumi@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Apr 2010 01:24:12 +0000 (01:24 +0000)
https://bugs.webkit.org/show_bug.cgi?id=38191

Reviewed by David Levin.

Vacuum all databases when the number of free pages is at least 10%
of the number of total pages. Also, add a guard against a bug that
was fixed in SQLite only starting with version 3.6.16.

* platform/sql/SQLiteDatabase.cpp:
(WebCore::SQLiteDatabase::maximumSize):
(WebCore::SQLiteDatabase::freeSpaceSize):
(WebCore::SQLiteDatabase::totalSize):
(WebCore::SQLiteDatabase::runIncrementalVacuumCommand):
(WebCore::SQLiteDatabase::turnOnIncrementalAutoVacuum):
* platform/sql/SQLiteDatabase.h:
(WebCore::SQLiteDatabase::):
* platform/sql/SQLiteStatement.cpp:
(WebCore::SQLiteStatement::prepare):
(WebCore::SQLiteStatement::step):
* storage/Database.cpp:
(WebCore::Database::performOpenAndVerify):
(WebCore::Database::incrementalVacuumIfNeeded):
* storage/Database.h:
* storage/SQLTransaction.cpp:
(WebCore::SQLTransaction::postflightAndCommit):

LayoutTests: Adjusting the expected amount of space used by quota-tracking.html.
https://bugs.webkit.org/show_bug.cgi?id=38191

Reviewed by David Levin.

The expectations changed because of AUTO_VACUUM's overhead.

* platform/chromium/test_expectations.txt:
* storage/quota-tracking-expected.txt:
* storage/quota-tracking.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@58366 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/platform/chromium/test_expectations.txt
LayoutTests/storage/quota-tracking-expected.txt
LayoutTests/storage/quota-tracking.html
WebCore/ChangeLog
WebCore/platform/sql/SQLiteDatabase.cpp
WebCore/platform/sql/SQLiteDatabase.h
WebCore/platform/sql/SQLiteStatement.cpp
WebCore/storage/Database.cpp
WebCore/storage/Database.h
WebCore/storage/SQLTransaction.cpp

index 8b916a8..3ada88e 100644 (file)
@@ -1,3 +1,16 @@
+2010-04-27  Dumitru Daniliuc  <dumi@chromium.org>
+
+        Reviewed by David Levin.
+
+        Adjusting the expected amount of space used by quota-tracking.html.
+        https://bugs.webkit.org/show_bug.cgi?id=38191
+
+        The expectations changed because of AUTO_VACUUM's overhead.
+
+        * platform/chromium/test_expectations.txt:
+        * storage/quota-tracking-expected.txt:
+        * storage/quota-tracking.html:
+
 2010-04-27  Evan Martin  <evan@chromium.org>
 
         Reviewed by David Levin.
 2010-04-27  Evan Martin  <evan@chromium.org>
 
         Reviewed by David Levin.
index c85cf71..f39dd99 100644 (file)
@@ -2877,6 +2877,3 @@ BUGWK38106 MAC : svg/custom/dominant-baseline-hanging.svg = IMAGE
 
 // Started failing at 58212
 BUGWK38108 MAC : fast/backgrounds/size/contain-and-cover.html = IMAGE
 
 // Started failing at 58212
 BUGWK38108 MAC : fast/backgrounds/size/contain-and-cover.html = IMAGE
-
-// Started failing after revert.
-BUGWK38191 : storage/quota-tracking.html = TIMEOUT TEXT
index 62a2c6b..6d860f0 100644 (file)
@@ -1,6 +1,6 @@
 UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{file, , 0} database:QuotaManagementDatabase2
 This test checks to make sure that quotas are enforced per-origin instead of per-database, as they were prior to http://trac.webkit.org/projects/webkit/changeset/29983.
 UI DELEGATE DATABASE CALLBACK: exceededDatabaseQuotaForSecurityOrigin:{file, , 0} database:QuotaManagementDatabase2
 This test checks to make sure that quotas are enforced per-origin instead of per-database, as they were prior to http://trac.webkit.org/projects/webkit/changeset/29983.
-The test clears all databases, sets the quota for the origin to 32k, then tries to insert 17k of data into two databases. If things go as planned, the UI Delegate will be informed of the exceeded quota and should increase the quota for this origin. Inserting 17k of data the third time should succeed again.
+The test clears all databases, sets the quota for the origin to 40k, then tries to insert 17k of data into two databases. If things go as planned, the second insert should fail, the UI Delegate should be informed of the exceeded quota and should increase the quota for this origin. Inserting 17k of data the third time should succeed again.
 Adding a table
 Inserting some data
 Done adding data
 Adding a table
 Inserting some data
 Done adding data
index 2b5b49d..6e52523 100644 (file)
@@ -91,7 +91,7 @@ function runTest()
     if (window.layoutTestController) {
         layoutTestController.clearAllDatabases();
         layoutTestController.dumpDatabaseCallbacks();
     if (window.layoutTestController) {
         layoutTestController.clearAllDatabases();
         layoutTestController.dumpDatabaseCallbacks();
-        layoutTestController.setDatabaseQuota(32768);
+        layoutTestController.setDatabaseQuota(40960);
         layoutTestController.dumpAsText();
         layoutTestController.waitUntilDone();
     }
         layoutTestController.dumpAsText();
         layoutTestController.waitUntilDone();
     }
@@ -105,7 +105,7 @@ function runTest()
 
 <body onload="runTest()">
 This test checks to make sure that quotas are enforced per-origin instead of per-database, as they were prior to http://trac.webkit.org/projects/webkit/changeset/29983.<br>
 
 <body onload="runTest()">
 This test checks to make sure that quotas are enforced per-origin instead of per-database, as they were prior to http://trac.webkit.org/projects/webkit/changeset/29983.<br>
-The test clears all databases, sets the quota for the origin to 32k, then tries to insert 17k of data into two databases. If things go as planned, the UI Delegate will be informed of the exceeded quota and should increase the quota for this origin. Inserting 17k of data the third time should succeed again.
+The test clears all databases, sets the quota for the origin to 40k, then tries to insert 17k of data into two databases. If things go as planned, the second insert should fail, the UI Delegate should be informed of the exceeded quota and should increase the quota for this origin. Inserting 17k of data the third time should succeed again.
 <pre id="console">
 </pre>
 </body>
 <pre id="console">
 </pre>
 </body>
index d77b535..92bcfe9 100644 (file)
@@ -1,3 +1,32 @@
+2010-04-27  Dumitru Daniliuc  <dumi@chromium.org>
+
+        Reviewed by David Levin.
+
+        Turn on AUTO_VACUUM = INCREMENTAL for all HTML5 databases.
+        https://bugs.webkit.org/show_bug.cgi?id=38191
+
+        Vacuum all databases when the number of free pages is at least 10%
+        of the number of total pages. Also, add a guard against a bug that
+        was fixed in SQLite only starting with version 3.6.16.
+
+        * platform/sql/SQLiteDatabase.cpp:
+        (WebCore::SQLiteDatabase::maximumSize):
+        (WebCore::SQLiteDatabase::freeSpaceSize):
+        (WebCore::SQLiteDatabase::totalSize):
+        (WebCore::SQLiteDatabase::runIncrementalVacuumCommand):
+        (WebCore::SQLiteDatabase::turnOnIncrementalAutoVacuum):
+        * platform/sql/SQLiteDatabase.h:
+        (WebCore::SQLiteDatabase::):
+        * platform/sql/SQLiteStatement.cpp:
+        (WebCore::SQLiteStatement::prepare):
+        (WebCore::SQLiteStatement::step):
+        * storage/Database.cpp:
+        (WebCore::Database::performOpenAndVerify):
+        (WebCore::Database::incrementalVacuumIfNeeded):
+        * storage/Database.h:
+        * storage/SQLTransaction.cpp:
+        (WebCore::SQLTransaction::postflightAndCommit):
+
 2010-04-27  Garret Kelly  <gdk@chromium.org>
 
         Reviewed by Darin Fisher.
 2010-04-27  Garret Kelly  <gdk@chromium.org>
 
         Reviewed by Darin Fisher.
index 58fdc7c..b24cd10 100644 (file)
@@ -102,14 +102,17 @@ void SQLiteDatabase::setFullsync(bool fsync)
 
 int64_t SQLiteDatabase::maximumSize()
 {
 
 int64_t SQLiteDatabase::maximumSize()
 {
-    MutexLocker locker(m_authorizerLock);
-    enableAuthorizer(false);
-    
-    SQLiteStatement statement(*this, "PRAGMA max_page_count");
-    int64_t size = statement.getColumnInt64(0) * pageSize();
-    
-    enableAuthorizer(true);
-    return size;
+    int64_t maxPageCount = 0;
+
+    {
+        MutexLocker locker(m_authorizerLock);
+        enableAuthorizer(false);
+        SQLiteStatement statement(*this, "PRAGMA max_page_count");
+        maxPageCount = statement.getColumnInt64(0);
+        enableAuthorizer(true);
+    }
+
+    return maxPageCount * pageSize();
 }
 
 void SQLiteDatabase::setMaximumSize(int64_t size)
 }
 
 void SQLiteDatabase::setMaximumSize(int64_t size)
@@ -153,14 +156,33 @@ int SQLiteDatabase::pageSize()
 
 int64_t SQLiteDatabase::freeSpaceSize()
 {
 
 int64_t SQLiteDatabase::freeSpaceSize()
 {
-    MutexLocker locker(m_authorizerLock);
-    enableAuthorizer(false);
-    // Note: freelist_count was added in SQLite 3.4.1.
-    SQLiteStatement statement(*this, "PRAGMA freelist_count");
-    int64_t size = statement.getColumnInt64(0) * pageSize();
+    int64_t freelistCount = 0;
 
 
-    enableAuthorizer(true);
-    return size;
+    {
+        MutexLocker locker(m_authorizerLock);
+        enableAuthorizer(false);
+        // Note: freelist_count was added in SQLite 3.4.1.
+        SQLiteStatement statement(*this, "PRAGMA freelist_count");
+        freelistCount = statement.getColumnInt64(0);
+        enableAuthorizer(true);
+    }
+
+    return freelistCount * pageSize();
+}
+
+int64_t SQLiteDatabase::totalSize()
+{
+    int64_t pageCount = 0;
+
+    {
+        MutexLocker locker(m_authorizerLock);
+        enableAuthorizer(false);
+        SQLiteStatement statement(*this, "PRAGMA page_count");
+        pageCount = statement.getColumnInt64(0);
+        enableAuthorizer(true);
+    }
+
+    return pageCount * pageSize();
 }
 
 void SQLiteDatabase::setSynchronous(SynchronousPragma sync)
 }
 
 void SQLiteDatabase::setSynchronous(SynchronousPragma sync)
@@ -229,6 +251,17 @@ void SQLiteDatabase::runVacuumCommand()
         LOG(SQLDatabase, "Unable to vacuum database - %s", lastErrorMsg());
 }
 
         LOG(SQLDatabase, "Unable to vacuum database - %s", lastErrorMsg());
 }
 
+void SQLiteDatabase::runIncrementalVacuumCommand()
+{
+    MutexLocker locker(m_authorizerLock);
+    enableAuthorizer(false);
+
+    if (!executeCommand("PRAGMA incremental_vacuum"))
+        LOG(SQLDatabase, "Unable to run incremental vacuum - %s", lastErrorMsg());
+
+    enableAuthorizer(true);
+}
+
 int64_t SQLiteDatabase::lastInsertRowID()
 {
     if (!m_db)
 int64_t SQLiteDatabase::lastInsertRowID()
 {
     if (!m_db)
@@ -379,4 +412,34 @@ bool SQLiteDatabase::isAutoCommitOn() const
     return sqlite3_get_autocommit(m_db);
 }
 
     return sqlite3_get_autocommit(m_db);
 }
 
+bool SQLiteDatabase::turnOnIncrementalAutoVacuum()
+{
+    SQLiteStatement statement(*this, "PRAGMA auto_vacuum");
+    int autoVacuumMode = statement.getColumnInt(0);
+    int error = lastError();
+
+    // Check if we got an error while trying to get the value of the auto_vacuum flag.
+    // If we got a SQLITE_BUSY error, then there's probably another transaction in
+    // progress on this database. In this case, keep the current value of the
+    // auto_vacuum flag and try to set it to INCREMENTAL the next time we open this
+    // database. If the error is not SQLITE_BUSY, then we probably ran into a more
+    // serious problem and should return false (to log an error message).
+    if (error != SQLITE_ROW)
+        return false;
+
+    switch (autoVacuumMode) {
+    case AutoVacuumIncremental:
+        return true;
+    case AutoVacuumFull:
+        return executeCommand("PRAGMA auto_vacuum = 2");
+    case AutoVacuumNone:
+    default:
+        if (!executeCommand("PRAGMA auto_vacuum = 2"))
+            return false;
+        runVacuumCommand();
+        error = lastError();
+        return (error == SQLITE_OK);
+    }
+}
+
 } // namespace WebCore
 } // namespace WebCore
index bc58a32..c5924c0 100644 (file)
@@ -65,6 +65,7 @@ public:
     bool tableExists(const String&);
     void clearAllTables();
     void runVacuumCommand();
     bool tableExists(const String&);
     void clearAllTables();
     void runVacuumCommand();
+    void runIncrementalVacuumCommand();
     
     bool transactionInProgress() const { return m_transactionInProgress; }
     
     
     bool transactionInProgress() const { return m_transactionInProgress; }
     
@@ -85,6 +86,7 @@ public:
     
     // Gets the number of unused bytes in the database file.
     int64_t freeSpaceSize();
     
     // Gets the number of unused bytes in the database file.
     int64_t freeSpaceSize();
+    int64_t totalSize();
 
     // The SQLite SYNCHRONOUS pragma can be either FULL, NORMAL, or OFF
     // FULL - Any writing calls to the DB block until the data is actually on the disk surface
 
     // The SQLite SYNCHRONOUS pragma can be either FULL, NORMAL, or OFF
     // FULL - Any writing calls to the DB block until the data is actually on the disk surface
@@ -108,6 +110,18 @@ public:
     void unlock();
     bool isAutoCommitOn() const;
 
     void unlock();
     bool isAutoCommitOn() const;
 
+    // The SQLite AUTO_VACUUM pragma can be either NONE, FULL, or INCREMENTAL.
+    // NONE - SQLite does not do any vacuuming
+    // FULL - SQLite moves all empty pages to the end of the DB file and truncates
+    //        the file to remove those pages after every transaction. This option
+    //        requires SQLite to store additional information about each page in
+    //        the database file.
+    // INCREMENTAL - SQLite stores extra information for each page in the database
+    //               file, but removes the empty pages only when PRAGMA INCREMANTAL_VACUUM
+    //               is called.
+    enum AutoVacuumPragma { AutoVacuumNone = 0, AutoVacuumFull = 1, AutoVacuumIncremental = 2 };
+    bool turnOnIncrementalAutoVacuum();
+
     // Set this flag to allow access from multiple threads.  Not all multi-threaded accesses are safe!
     // See http://www.sqlite.org/cvstrac/wiki?p=MultiThreading for more info.
 #ifndef NDEBUG
     // Set this flag to allow access from multiple threads.  Not all multi-threaded accesses are safe!
     // See http://www.sqlite.org/cvstrac/wiki?p=MultiThreading for more info.
 #ifndef NDEBUG
index 8963adb..cd2a467 100644 (file)
@@ -65,6 +65,15 @@ int SQLiteStatement::prepare()
     LOG(SQLDatabase, "SQL - prepare - %s", m_query.ascii().data());
     String strippedQuery = m_query.stripWhiteSpace();
     int error = sqlite3_prepare16_v2(m_database.sqlite3Handle(), strippedQuery.charactersWithNullTermination(), -1, &m_statement, &tail);
     LOG(SQLDatabase, "SQL - prepare - %s", m_query.ascii().data());
     String strippedQuery = m_query.stripWhiteSpace();
     int error = sqlite3_prepare16_v2(m_database.sqlite3Handle(), strippedQuery.charactersWithNullTermination(), -1, &m_statement, &tail);
+
+    // Starting with version 3.6.16, sqlite has a patch (http://www.sqlite.org/src/ci/256ec3c6af)
+    // that should make sure sqlite3_prepare16_v2 doesn't return a SQLITE_SCHEMA error.
+    // If we're using an older sqlite version, try to emulate the patch.
+    if (error == SQLITE_SCHEMA) {
+      sqlite3_finalize(m_statement);
+      error = sqlite3_prepare16_v2(m_database.sqlite3Handle(), m_query.charactersWithNullTermination(), -1, &m_statement, &tail);
+    }
+
     if (error != SQLITE_OK)
         LOG(SQLDatabase, "sqlite3_prepare16 failed (%i)\n%s\n%s", error, m_query.ascii().data(), sqlite3_errmsg(m_database.sqlite3Handle()));
     const UChar* ch = static_cast<const UChar*>(tail);
     if (error != SQLITE_OK)
         LOG(SQLDatabase, "sqlite3_prepare16 failed (%i)\n%s\n%s", error, m_query.ascii().data(), sqlite3_errmsg(m_database.sqlite3Handle()));
     const UChar* ch = static_cast<const UChar*>(tail);
@@ -87,6 +96,7 @@ int SQLiteStatement::step()
         LOG(SQLDatabase, "sqlite3_step failed (%i)\nQuery - %s\nError - %s", 
             error, m_query.ascii().data(), sqlite3_errmsg(m_database.sqlite3Handle()));
     }
         LOG(SQLDatabase, "sqlite3_step failed (%i)\nQuery - %s\nError - %s", 
             error, m_query.ascii().data(), sqlite3_errmsg(m_database.sqlite3Handle()));
     }
+
     return error;
 }
     
     return error;
 }
     
index cb61d48..0644df5 100644 (file)
@@ -543,6 +543,8 @@ bool Database::performOpenAndVerify(ExceptionCode& e)
         e = INVALID_STATE_ERR;
         return false;
     }
         e = INVALID_STATE_ERR;
         return false;
     }
+    if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum())
+        LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data());
 
     ASSERT(m_databaseAuthorizer);
     m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
 
     ASSERT(m_databaseAuthorizer);
     m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
@@ -808,6 +810,14 @@ String Database::fileName() const
     return m_filename.threadsafeCopy();
 }
 
     return m_filename.threadsafeCopy();
 }
 
+void Database::incrementalVacuumIfNeeded()
+{
+    int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize();
+    int64_t totalSize = m_sqliteDatabase.totalSize();
+    if (totalSize <= 10 * freeSpaceSize)
+        m_sqliteDatabase.runIncrementalVacuumCommand();
+}
+
 #endif // ENABLE(DATABASE)
 
 } // namespace WebCore
 #endif // ENABLE(DATABASE)
 
 } // namespace WebCore
index f7b88a2..7bf9dc8 100644 (file)
@@ -133,6 +133,8 @@ public:
     SQLTransactionClient* transactionClient() const;
     SQLTransactionCoordinator* transactionCoordinator() const;
 
     SQLTransactionClient* transactionClient() const;
     SQLTransactionCoordinator* transactionCoordinator() const;
 
+    void incrementalVacuumIfNeeded();
+
 private:
     Database(ScriptExecutionContext* context, const String& name,
              const String& expectedVersion, const String& displayName,
 private:
     Database(ScriptExecutionContext* context, const String& name,
              const String& expectedVersion, const String& displayName,
index ea7deee..7dea578 100644 (file)
@@ -466,6 +466,9 @@ void SQLTransaction::postflightAndCommit()
         return;
     }
 
         return;
     }
 
+    // The commit was successful, so vacuum the database if needed
+    m_database->incrementalVacuumIfNeeded();
+
     // The commit was successful, notify the delegates if the transaction modified this database
     if (m_modifiedDatabase)
         m_database->transactionClient()->didCommitTransaction(this);
     // The commit was successful, notify the delegates if the transaction modified this database
     if (m_modifiedDatabase)
         m_database->transactionClient()->didCommitTransaction(this);