Changeset 92155 in webkit


Ignore:
Timestamp:
Aug 1, 2011 5:21:56 PM (13 years ago)
Author:
Michael Nordman
Message:

[Chromium] WebSQLDatabase version handling is broken in multi-process browsers.
https://bugs.webkit.org/show_bug.cgi?id=65486

The WebCore::AbstractDatabase class maintains a global in-memory map of
the version numbers associated with open database files, but that map is
not reliable in a multi-process system like Chrome. So instead of relying
on the cached values in that map, we read the value from the database (and
update the cached value) where possible. There are two edge cases where that's
not possible because the scriptable interface requires synchronous access
to the version: the .version attribute getter and the .openDatabase() method.
In those cases, we have no choice but to use the potentially stale cached value.

Reviewed by Darin Fisher.

No new tests. Existing layout tests cover the version handling functionality.

  • storage/AbstractDatabase.cpp:

(WebCore::AbstractDatabase::version):
(WebCore::AbstractDatabase::performOpenAndVerify):
(WebCore::AbstractDatabase::getVersionFromDatabase):
(WebCore::AbstractDatabase::setVersionInDatabase):
(WebCore::AbstractDatabase::setExpectedVersion):
(WebCore::AbstractDatabase::getCachedVersion):
(WebCore::AbstractDatabase::setCachedVersion):
(WebCore::AbstractDatabase::getActualVersionForTransaction):

  • storage/AbstractDatabase.h:

(WebCore::AbstractDatabase::expectedVersion):

  • storage/ChangeVersionWrapper.cpp:

(WebCore::ChangeVersionWrapper::handleCommitFailedAfterPostflight):

  • storage/ChangeVersionWrapper.h:
  • storage/Database.cpp:

(WebCore::Database::openDatabase):

  • storage/DatabaseSync.cpp:

(WebCore::DatabaseSync::openDatabaseSync):
(WebCore::DatabaseSync::changeVersion):

  • storage/SQLTransaction.cpp:

(WebCore::SQLTransaction::SQLTransaction):
(WebCore::SQLTransaction::executeSQL):
(WebCore::SQLTransaction::openTransactionAndPreflight):
(WebCore::SQLTransaction::runCurrentStatement):
(WebCore::SQLTransaction::postflightAndCommit):

  • storage/SQLTransaction.h:
  • storage/SQLTransactionSync.cpp:

(WebCore::SQLTransactionSync::SQLTransactionSync):
(WebCore::SQLTransactionSync::executeSQL):
(WebCore::SQLTransactionSync::begin):

  • storage/SQLTransactionSync.h:
Location:
trunk/Source/WebCore
Files:
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r92152 r92155  
     12011-08-01  Michael Nordman  <michaeln@google.com>
     2
     3        [Chromium] WebSQLDatabase version handling is broken in multi-process browsers.
     4        https://bugs.webkit.org/show_bug.cgi?id=65486
     5
     6        The WebCore::AbstractDatabase class maintains a global in-memory map of
     7        the version numbers associated with open database files, but that map is
     8        not reliable in a multi-process system like Chrome. So instead of relying
     9        on the cached values in that map, we read the value from the database (and
     10        update the cached value) where possible. There are two edge cases where that's
     11        not possible because the scriptable interface requires synchronous access
     12        to the version: the .version attribute getter and the .openDatabase() method.
     13        In those cases, we have no choice but to use the potentially stale cached value.
     14
     15        Reviewed by Darin Fisher.
     16
     17        No new tests. Existing layout tests cover the version handling functionality.
     18
     19        * storage/AbstractDatabase.cpp:
     20        (WebCore::AbstractDatabase::version):
     21        (WebCore::AbstractDatabase::performOpenAndVerify):
     22        (WebCore::AbstractDatabase::getVersionFromDatabase):
     23        (WebCore::AbstractDatabase::setVersionInDatabase):
     24        (WebCore::AbstractDatabase::setExpectedVersion):
     25        (WebCore::AbstractDatabase::getCachedVersion):
     26        (WebCore::AbstractDatabase::setCachedVersion):
     27        (WebCore::AbstractDatabase::getActualVersionForTransaction):
     28        * storage/AbstractDatabase.h:
     29        (WebCore::AbstractDatabase::expectedVersion):
     30        * storage/ChangeVersionWrapper.cpp:
     31        (WebCore::ChangeVersionWrapper::handleCommitFailedAfterPostflight):
     32        * storage/ChangeVersionWrapper.h:
     33        * storage/Database.cpp:
     34        (WebCore::Database::openDatabase):
     35        * storage/DatabaseSync.cpp:
     36        (WebCore::DatabaseSync::openDatabaseSync):
     37        (WebCore::DatabaseSync::changeVersion):
     38        * storage/SQLTransaction.cpp:
     39        (WebCore::SQLTransaction::SQLTransaction):
     40        (WebCore::SQLTransaction::executeSQL):
     41        (WebCore::SQLTransaction::openTransactionAndPreflight):
     42        (WebCore::SQLTransaction::runCurrentStatement):
     43        (WebCore::SQLTransaction::postflightAndCommit):
     44        * storage/SQLTransaction.h:
     45        * storage/SQLTransactionSync.cpp:
     46        (WebCore::SQLTransactionSync::SQLTransactionSync):
     47        (WebCore::SQLTransactionSync::executeSQL):
     48        (WebCore::SQLTransactionSync::begin):
     49        * storage/SQLTransactionSync.h:
     50
    1512011-08-01  Tim Horton  <timothy_horton@apple.com>
    252
  • trunk/Source/WebCore/storage/AbstractDatabase.cpp

    r74093 r92155  
    3535#include "Logging.h"
    3636#include "SQLiteStatement.h"
     37#include "SQLiteTransaction.h"
    3738#include "ScriptExecutionContext.h"
    3839#include "SecurityOrigin.h"
     
    236237String AbstractDatabase::version() const
    237238{
    238     MutexLocker locker(guidMutex());
    239     return guidToVersionMap().get(m_guid).threadsafeCopy();
    240 }
    241 
    242 static const int maxSqliteBusyWaitTime = 30000;
     239    // Note: In multi-process browsers the cached value may be accurate, but we cannot read the
     240    // actual version from the database without potentially inducing a deadlock.
     241    // FIXME: Add an async version getter to the DatabaseAPI.
     242    return getCachedVersion();
     243}
     244
    243245bool AbstractDatabase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec)
    244246{
     247    const int maxSqliteBusyWaitTime = 30000;
     248
    245249    if (!m_sqliteDatabase.open(m_filename, true)) {
    246250        LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data());
     
    251255        LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data());
    252256
    253     ASSERT(m_databaseAuthorizer);
    254     m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
    255257    m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime);
    256258
     
    264266            currentVersion = entry->second.isNull() ? String("") : entry->second;
    265267            LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data());
     268
     269#if PLATFORM(CHROMIUM)
     270            // Note: In multi-process browsers the cached value may be inaccurate, but
     271            // we cannot read the actual version from the database without potentially
     272            // inducing a form of deadlock, a busytimeout error when trying to
     273            // access the database. So we'll use the cached value if we're able to read
     274            // the value without waiting, and otherwise use the cached value (which may be off).
     275            // FIXME: Add an async openDatabase method to the DatabaseAPI.
     276            const int noSqliteBusyWaitTime = 0;
     277            m_sqliteDatabase.setBusyTimeout(noSqliteBusyWaitTime);
     278            String versionFromDatabase;
     279            if (getVersionFromDatabase(versionFromDatabase, false)) {
     280                currentVersion = versionFromDatabase;
     281                updateGuidVersionMap(m_guid, currentVersion);
     282            }
     283            m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime);
     284#endif
    266285        } else {
    267286            LOG(StorageAPI, "No cached version for guid %i", m_guid);
     287
     288            SQLiteTransaction transaction(m_sqliteDatabase);
     289            transaction.begin();
     290            if (!transaction.inProgress()) {
     291                LOG_ERROR("Unable to begin transaction while opening %s", databaseDebugName().ascii().data());
     292                ec = INVALID_STATE_ERR;
     293                m_sqliteDatabase.close();
     294                return false;
     295            }
    268296
    269297            if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) {
     
    273301                    LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data());
    274302                    ec = INVALID_STATE_ERR;
    275                     // Close the handle to the database file.
     303                    transaction.rollback();
    276304                    m_sqliteDatabase.close();
    277305                    return false;
    278306                }
    279             }
    280 
    281             if (!getVersionFromDatabase(currentVersion)) {
     307            } else if (!getVersionFromDatabase(currentVersion, false)) {
    282308                LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data());
    283309                ec = INVALID_STATE_ERR;
    284                 // Close the handle to the database file.
     310                transaction.rollback();
    285311                m_sqliteDatabase.close();
    286312                return false;
    287313            }
     314
    288315            if (currentVersion.length()) {
    289316                LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data());
    290317            } else if (!m_new || shouldSetVersionInNewDatabase) {
    291318                LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
    292                 if (!setVersionInDatabase(m_expectedVersion)) {
     319                if (!setVersionInDatabase(m_expectedVersion, false)) {
    293320                    LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
    294321                    ec = INVALID_STATE_ERR;
    295                     // Close the handle to the database file.
     322                    transaction.rollback();
    296323                    m_sqliteDatabase.close();
    297324                    return false;
     
    299326                currentVersion = m_expectedVersion;
    300327            }
    301 
    302328            updateGuidVersionMap(m_guid, currentVersion);
     329            transaction.commit();
    303330        }
    304331    }
     
    315342            databaseDebugName().ascii().data(), currentVersion.ascii().data());
    316343        ec = INVALID_STATE_ERR;
    317         // Close the handle to the database file.
    318344        m_sqliteDatabase.close();
    319345        return false;
    320346    }
    321347
     348    ASSERT(m_databaseAuthorizer);
     349    m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
     350
    322351    m_opened = true;
     352
     353    if (m_new && !shouldSetVersionInNewDatabase)
     354        m_expectedVersion = ""; // The caller provided a creationCallback which will set the expected version.
    323355
    324356    return true;
     
    365397}
    366398
    367 bool AbstractDatabase::getVersionFromDatabase(String& version)
     399bool AbstractDatabase::getVersionFromDatabase(String& version, bool shouldCacheVersion)
    368400{
    369401    DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';"));
     
    372404
    373405    bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version);
    374     if (!result)
     406    if (result) {
     407        if (shouldCacheVersion)
     408            setCachedVersion(version);
     409    } else
    375410        LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data());
    376411
     
    380415}
    381416
    382 bool AbstractDatabase::setVersionInDatabase(const String& version)
     417bool AbstractDatabase::setVersionInDatabase(const String& version, bool shouldCacheVersion)
    383418{
    384419    // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE
     
    389424
    390425    bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version);
    391     if (!result)
     426    if (result) {
     427        if (shouldCacheVersion)
     428            setCachedVersion(version);
     429    } else
    392430        LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data());
    393431
     
    397435}
    398436
    399 bool AbstractDatabase::versionMatchesExpected() const
    400 {
    401     if (!m_expectedVersion.isEmpty()) {
    402         MutexLocker locker(guidMutex());
    403         return m_expectedVersion == guidToVersionMap().get(m_guid);
    404     }
    405 
    406     return true;
    407 }
    408 
    409437void AbstractDatabase::setExpectedVersion(const String& version)
    410438{
    411439    m_expectedVersion = version.threadsafeCopy();
     440}
     441
     442String AbstractDatabase::getCachedVersion() const
     443{
     444    MutexLocker locker(guidMutex());
     445    return guidToVersionMap().get(m_guid).threadsafeCopy();
     446}
     447
     448void AbstractDatabase::setCachedVersion(const String& actualVersion)
     449{
    412450    // Update the in memory database version map.
    413451    MutexLocker locker(guidMutex());
    414     updateGuidVersionMap(m_guid, version);
     452    updateGuidVersionMap(m_guid, actualVersion);
     453}
     454
     455bool AbstractDatabase::getActualVersionForTransaction(String &actualVersion)
     456{
     457    ASSERT(m_sqliteDatabase.transactionInProgress());
     458#if PLATFORM(CHROMIUM)
     459    // Note: In multi-process browsers the cached value may be inaccurate.
     460    // So we retrieve the value from the database and update the cached value here.
     461    return getVersionFromDatabase(actualVersion, true);
     462#else
     463    actualVersion = getCachedVersion();
     464    return true;
     465#endif
    415466}
    416467
  • trunk/Source/WebCore/storage/AbstractDatabase.h

    r89708 r92155  
    7272    bool isInterrupted();
    7373
    74     // FIXME: move all version-related methods to a DatabaseVersionTracker class
    75     bool versionMatchesExpected() const;
    76     void setExpectedVersion(const String& version);
    77     bool getVersionFromDatabase(String& version);
    78     bool setVersionInDatabase(const String& version);
    79 
    8074    void disableAuthorizer();
    8175    void enableAuthorizer();
     
    9286
    9387protected:
     88    friend class SQLTransactionSync;
     89    friend class SQLTransaction;
     90    friend class ChangeVersionWrapper;
     91
    9492    AbstractDatabase(ScriptExecutionContext*, const String& name, const String& expectedVersion,
    9593                     const String& displayName, unsigned long estimatedSize);
     
    9896
    9997    virtual bool performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec);
     98
     99    bool getVersionFromDatabase(String& version, bool shouldCacheVersion = true);
     100    bool setVersionInDatabase(const String& version, bool shouldCacheVersion = true);
     101    void setExpectedVersion(const String&);
     102    const String& expectedVersion() const { return m_expectedVersion; }
     103    String getCachedVersion()const;
     104    void setCachedVersion(const String&);
     105    bool getActualVersionForTransaction(String& version);
    100106
    101107    static const String& databaseInfoTableName();
  • trunk/Source/WebCore/storage/ChangeVersionWrapper.cpp

    r63278 r92155  
    7979}
    8080
     81void ChangeVersionWrapper::handleCommitFailedAfterPostflight(SQLTransaction* transaction)
     82{
     83    transaction->database()->setCachedVersion(m_oldVersion);
     84}
     85
    8186} // namespace WebCore
    8287
  • trunk/Source/WebCore/storage/ChangeVersionWrapper.h

    r60508 r92155  
    4545    virtual bool performPreflight(SQLTransaction*);
    4646    virtual bool performPostflight(SQLTransaction*);
    47 
    4847    virtual SQLError* sqlError() const { return m_sqlError.get(); }
     48    virtual void handleCommitFailedAfterPostflight(SQLTransaction*);
    4949
    5050private:
  • trunk/Source/WebCore/storage/Database.cpp

    r79769 r92155  
    110110    InspectorInstrumentation::didOpenDatabase(context, database, context->securityOrigin()->host(), name, expectedVersion);
    111111
    112     // If it's a new database and a creation callback was provided, reset the expected
    113     // version to "" and schedule the creation callback. Because of some subtle String
    114     // implementation issues, we have to reset m_expectedVersion here instead of doing
    115     // it inside performOpenAndVerify() which is run on the DB thread.
    116112    if (database->isNew() && creationCallback.get()) {
    117         database->m_expectedVersion = "";
    118113        LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n", database.get());
    119114        database->m_scriptExecutionContext->postTask(DatabaseCreationCallbackTask::create(database, creationCallback));
  • trunk/Source/WebCore/storage/DatabaseSync.cpp

    r67619 r92155  
    6868
    6969    if (database->isNew() && creationCallback.get()) {
    70         database->m_expectedVersion = "";
    7170        LOG(StorageAPI, "Invoking the creation callback for database %p\n", database.get());
    7271        creationCallback->handleEvent(database.get());
     
    124123    }
    125124
    126     if ((ec = transaction->commit()))
    127         return;
     125    if ((ec = transaction->commit())) {
     126        setCachedVersion(oldVersion);
     127        return;
     128    }
    128129
    129130    setExpectedVersion(newVersion);
  • trunk/Source/WebCore/storage/SQLTransaction.cpp

    r89708 r92155  
    7979    , m_lockAcquired(false)
    8080    , m_readOnly(readOnly)
     81    , m_hasVersionMismatch(false)
    8182{
    8283    ASSERT(m_database);
     
    105106    if (m_database->deleted())
    106107        statement->setDatabaseDeletedError();
    107 
    108     if (!m_database->versionMatchesExpected())
    109         statement->setVersionMismatchedError();
    110108
    111109    enqueueStatement(statement);
     
    273271    }
    274272
     273    // Note: We intentionally retrieve the actual version even with an empty expected version.
     274    // In multi-process browsers, we take this opportinutiy to update the cached value for
     275    // the actual version. In single-process browsers, this is just a map lookup.
     276    String actualVersion;
     277    if (!m_database->getActualVersionForTransaction(actualVersion)) {
     278        m_database->disableAuthorizer();
     279        m_sqliteTransaction.clear();
     280        m_database->enableAuthorizer();
     281        m_transactionError = SQLError::create(SQLError::DATABASE_ERR, "unable to read version");
     282        handleTransactionError(false);
     283        return;
     284    }
     285    m_hasVersionMismatch = !m_database->expectedVersion().isEmpty()
     286                           && (m_database->expectedVersion() != actualVersion);
     287
    275288    // Transaction Steps 3 - Peform preflight steps, jumping to the error callback if they fail
    276289    if (m_wrapper && !m_wrapper->performPreflight(this)) {
     290        m_database->disableAuthorizer();
    277291        m_sqliteTransaction.clear();
     292        m_database->enableAuthorizer();
    278293        m_transactionError = m_wrapper->sqlError();
    279294        if (!m_transactionError)
     
    370385    m_database->resetAuthorizer();
    371386
     387    if (m_hasVersionMismatch)
     388        m_currentStatement->setVersionMismatchedError();
     389
    372390    if (m_currentStatement->execute(m_database.get())) {
    373391        if (m_database->lastActionChangedDatabase()) {
     
    466484    // If the commit failed, the transaction will still be marked as "in progress"
    467485    if (m_sqliteTransaction->inProgress()) {
     486        if (m_wrapper)
     487            m_wrapper->handleCommitFailedAfterPostflight(this);
    468488        m_successCallbackWrapper.clear();
    469489        m_transactionError = SQLError::create(SQLError::DATABASE_ERR, "failed to commit the transaction");
  • trunk/Source/WebCore/storage/SQLTransaction.h

    r89708 r92155  
    5656    virtual bool performPreflight(SQLTransaction*) = 0;
    5757    virtual bool performPostflight(SQLTransaction*) = 0;
    58 
    5958    virtual SQLError* sqlError() const = 0;
     59    virtual void handleCommitFailedAfterPostflight(SQLTransaction*) = 0;
    6060};
    6161
     
    124124    bool m_lockAcquired;
    125125    bool m_readOnly;
     126    bool m_hasVersionMismatch;
    126127
    127128    Mutex m_statementMutex;
  • trunk/Source/WebCore/storage/SQLTransactionSync.cpp

    r74093 r92155  
    5959    , m_callback(callback)
    6060    , m_readOnly(readOnly)
     61    , m_hasVersionMismatch(false)
    6162    , m_modifiedDatabase(false)
    6263    , m_transactionClient(adoptPtr(new SQLTransactionClient()))
     
    8081    }
    8182
    82     if (!m_database->versionMatchesExpected()) {
     83    if (m_hasVersionMismatch) {
    8384        ec = SQLException::VERSION_ERR;
    8485        return 0;
     
    151152    }
    152153
     154    // Note: We intentionally retrieve the actual version even with an empty expected version.
     155    // In multi-process browsers, we take this opportinutiy to update the cached value for
     156    // the actual version. In single-process browsers, this is just a map lookup.
     157    String actualVersion;
     158    if (!m_database->getActualVersionForTransaction(actualVersion)) {
     159        rollback();
     160        return SQLException::DATABASE_ERR;
     161    }
     162    m_hasVersionMismatch = !m_database->expectedVersion().isEmpty()
     163                           && (m_database->expectedVersion() != actualVersion);
    153164    return 0;
    154165}
  • trunk/Source/WebCore/storage/SQLTransactionSync.h

    r65021 r92155  
    3535
    3636#include "ExceptionCode.h"
     37#include "PlatformString.h"
    3738#include <wtf/Forward.h>
    3839#include <wtf/RefCounted.h>
     
    7172    RefPtr<SQLTransactionSyncCallback> m_callback;
    7273    bool m_readOnly;
     74    bool m_hasVersionMismatch;
    7375
    7476    bool m_modifiedDatabase;
Note: See TracChangeset for help on using the changeset viewer.