Changeset 92155 in webkit
- Timestamp:
- Aug 1, 2011 5:21:56 PM (13 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r92152 r92155 1 2011-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 1 51 2011-08-01 Tim Horton <timothy_horton@apple.com> 2 52 -
trunk/Source/WebCore/storage/AbstractDatabase.cpp
r74093 r92155 35 35 #include "Logging.h" 36 36 #include "SQLiteStatement.h" 37 #include "SQLiteTransaction.h" 37 38 #include "ScriptExecutionContext.h" 38 39 #include "SecurityOrigin.h" … … 236 237 String AbstractDatabase::version() const 237 238 { 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 243 245 bool AbstractDatabase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec) 244 246 { 247 const int maxSqliteBusyWaitTime = 30000; 248 245 249 if (!m_sqliteDatabase.open(m_filename, true)) { 246 250 LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data()); … … 251 255 LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data()); 252 256 253 ASSERT(m_databaseAuthorizer);254 m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);255 257 m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); 256 258 … … 264 266 currentVersion = entry->second.isNull() ? String("") : entry->second; 265 267 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 266 285 } else { 267 286 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 } 268 296 269 297 if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) { … … 273 301 LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data()); 274 302 ec = INVALID_STATE_ERR; 275 // Close the handle to the database file.303 transaction.rollback(); 276 304 m_sqliteDatabase.close(); 277 305 return false; 278 306 } 279 } 280 281 if (!getVersionFromDatabase(currentVersion)) { 307 } else if (!getVersionFromDatabase(currentVersion, false)) { 282 308 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data()); 283 309 ec = INVALID_STATE_ERR; 284 // Close the handle to the database file.310 transaction.rollback(); 285 311 m_sqliteDatabase.close(); 286 312 return false; 287 313 } 314 288 315 if (currentVersion.length()) { 289 316 LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data()); 290 317 } else if (!m_new || shouldSetVersionInNewDatabase) { 291 318 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)) { 293 320 LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); 294 321 ec = INVALID_STATE_ERR; 295 // Close the handle to the database file.322 transaction.rollback(); 296 323 m_sqliteDatabase.close(); 297 324 return false; … … 299 326 currentVersion = m_expectedVersion; 300 327 } 301 302 328 updateGuidVersionMap(m_guid, currentVersion); 329 transaction.commit(); 303 330 } 304 331 } … … 315 342 databaseDebugName().ascii().data(), currentVersion.ascii().data()); 316 343 ec = INVALID_STATE_ERR; 317 // Close the handle to the database file.318 344 m_sqliteDatabase.close(); 319 345 return false; 320 346 } 321 347 348 ASSERT(m_databaseAuthorizer); 349 m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer); 350 322 351 m_opened = true; 352 353 if (m_new && !shouldSetVersionInNewDatabase) 354 m_expectedVersion = ""; // The caller provided a creationCallback which will set the expected version. 323 355 324 356 return true; … … 365 397 } 366 398 367 bool AbstractDatabase::getVersionFromDatabase(String& version )399 bool AbstractDatabase::getVersionFromDatabase(String& version, bool shouldCacheVersion) 368 400 { 369 401 DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';")); … … 372 404 373 405 bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version); 374 if (!result) 406 if (result) { 407 if (shouldCacheVersion) 408 setCachedVersion(version); 409 } else 375 410 LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data()); 376 411 … … 380 415 } 381 416 382 bool AbstractDatabase::setVersionInDatabase(const String& version )417 bool AbstractDatabase::setVersionInDatabase(const String& version, bool shouldCacheVersion) 383 418 { 384 419 // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE … … 389 424 390 425 bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version); 391 if (!result) 426 if (result) { 427 if (shouldCacheVersion) 428 setCachedVersion(version); 429 } else 392 430 LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data()); 393 431 … … 397 435 } 398 436 399 bool AbstractDatabase::versionMatchesExpected() const400 {401 if (!m_expectedVersion.isEmpty()) {402 MutexLocker locker(guidMutex());403 return m_expectedVersion == guidToVersionMap().get(m_guid);404 }405 406 return true;407 }408 409 437 void AbstractDatabase::setExpectedVersion(const String& version) 410 438 { 411 439 m_expectedVersion = version.threadsafeCopy(); 440 } 441 442 String AbstractDatabase::getCachedVersion() const 443 { 444 MutexLocker locker(guidMutex()); 445 return guidToVersionMap().get(m_guid).threadsafeCopy(); 446 } 447 448 void AbstractDatabase::setCachedVersion(const String& actualVersion) 449 { 412 450 // Update the in memory database version map. 413 451 MutexLocker locker(guidMutex()); 414 updateGuidVersionMap(m_guid, version); 452 updateGuidVersionMap(m_guid, actualVersion); 453 } 454 455 bool 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 415 466 } 416 467 -
trunk/Source/WebCore/storage/AbstractDatabase.h
r89708 r92155 72 72 bool isInterrupted(); 73 73 74 // FIXME: move all version-related methods to a DatabaseVersionTracker class75 bool versionMatchesExpected() const;76 void setExpectedVersion(const String& version);77 bool getVersionFromDatabase(String& version);78 bool setVersionInDatabase(const String& version);79 80 74 void disableAuthorizer(); 81 75 void enableAuthorizer(); … … 92 86 93 87 protected: 88 friend class SQLTransactionSync; 89 friend class SQLTransaction; 90 friend class ChangeVersionWrapper; 91 94 92 AbstractDatabase(ScriptExecutionContext*, const String& name, const String& expectedVersion, 95 93 const String& displayName, unsigned long estimatedSize); … … 98 96 99 97 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); 100 106 101 107 static const String& databaseInfoTableName(); -
trunk/Source/WebCore/storage/ChangeVersionWrapper.cpp
r63278 r92155 79 79 } 80 80 81 void ChangeVersionWrapper::handleCommitFailedAfterPostflight(SQLTransaction* transaction) 82 { 83 transaction->database()->setCachedVersion(m_oldVersion); 84 } 85 81 86 } // namespace WebCore 82 87 -
trunk/Source/WebCore/storage/ChangeVersionWrapper.h
r60508 r92155 45 45 virtual bool performPreflight(SQLTransaction*); 46 46 virtual bool performPostflight(SQLTransaction*); 47 48 47 virtual SQLError* sqlError() const { return m_sqlError.get(); } 48 virtual void handleCommitFailedAfterPostflight(SQLTransaction*); 49 49 50 50 private: -
trunk/Source/WebCore/storage/Database.cpp
r79769 r92155 110 110 InspectorInstrumentation::didOpenDatabase(context, database, context->securityOrigin()->host(), name, expectedVersion); 111 111 112 // If it's a new database and a creation callback was provided, reset the expected113 // version to "" and schedule the creation callback. Because of some subtle String114 // implementation issues, we have to reset m_expectedVersion here instead of doing115 // it inside performOpenAndVerify() which is run on the DB thread.116 112 if (database->isNew() && creationCallback.get()) { 117 database->m_expectedVersion = "";118 113 LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n", database.get()); 119 114 database->m_scriptExecutionContext->postTask(DatabaseCreationCallbackTask::create(database, creationCallback)); -
trunk/Source/WebCore/storage/DatabaseSync.cpp
r67619 r92155 68 68 69 69 if (database->isNew() && creationCallback.get()) { 70 database->m_expectedVersion = "";71 70 LOG(StorageAPI, "Invoking the creation callback for database %p\n", database.get()); 72 71 creationCallback->handleEvent(database.get()); … … 124 123 } 125 124 126 if ((ec = transaction->commit())) 127 return; 125 if ((ec = transaction->commit())) { 126 setCachedVersion(oldVersion); 127 return; 128 } 128 129 129 130 setExpectedVersion(newVersion); -
trunk/Source/WebCore/storage/SQLTransaction.cpp
r89708 r92155 79 79 , m_lockAcquired(false) 80 80 , m_readOnly(readOnly) 81 , m_hasVersionMismatch(false) 81 82 { 82 83 ASSERT(m_database); … … 105 106 if (m_database->deleted()) 106 107 statement->setDatabaseDeletedError(); 107 108 if (!m_database->versionMatchesExpected())109 statement->setVersionMismatchedError();110 108 111 109 enqueueStatement(statement); … … 273 271 } 274 272 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 275 288 // Transaction Steps 3 - Peform preflight steps, jumping to the error callback if they fail 276 289 if (m_wrapper && !m_wrapper->performPreflight(this)) { 290 m_database->disableAuthorizer(); 277 291 m_sqliteTransaction.clear(); 292 m_database->enableAuthorizer(); 278 293 m_transactionError = m_wrapper->sqlError(); 279 294 if (!m_transactionError) … … 370 385 m_database->resetAuthorizer(); 371 386 387 if (m_hasVersionMismatch) 388 m_currentStatement->setVersionMismatchedError(); 389 372 390 if (m_currentStatement->execute(m_database.get())) { 373 391 if (m_database->lastActionChangedDatabase()) { … … 466 484 // If the commit failed, the transaction will still be marked as "in progress" 467 485 if (m_sqliteTransaction->inProgress()) { 486 if (m_wrapper) 487 m_wrapper->handleCommitFailedAfterPostflight(this); 468 488 m_successCallbackWrapper.clear(); 469 489 m_transactionError = SQLError::create(SQLError::DATABASE_ERR, "failed to commit the transaction"); -
trunk/Source/WebCore/storage/SQLTransaction.h
r89708 r92155 56 56 virtual bool performPreflight(SQLTransaction*) = 0; 57 57 virtual bool performPostflight(SQLTransaction*) = 0; 58 59 58 virtual SQLError* sqlError() const = 0; 59 virtual void handleCommitFailedAfterPostflight(SQLTransaction*) = 0; 60 60 }; 61 61 … … 124 124 bool m_lockAcquired; 125 125 bool m_readOnly; 126 bool m_hasVersionMismatch; 126 127 127 128 Mutex m_statementMutex; -
trunk/Source/WebCore/storage/SQLTransactionSync.cpp
r74093 r92155 59 59 , m_callback(callback) 60 60 , m_readOnly(readOnly) 61 , m_hasVersionMismatch(false) 61 62 , m_modifiedDatabase(false) 62 63 , m_transactionClient(adoptPtr(new SQLTransactionClient())) … … 80 81 } 81 82 82 if ( !m_database->versionMatchesExpected()) {83 if (m_hasVersionMismatch) { 83 84 ec = SQLException::VERSION_ERR; 84 85 return 0; … … 151 152 } 152 153 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); 153 164 return 0; 154 165 } -
trunk/Source/WebCore/storage/SQLTransactionSync.h
r65021 r92155 35 35 36 36 #include "ExceptionCode.h" 37 #include "PlatformString.h" 37 38 #include <wtf/Forward.h> 38 39 #include <wtf/RefCounted.h> … … 71 72 RefPtr<SQLTransactionSyncCallback> m_callback; 72 73 bool m_readOnly; 74 bool m_hasVersionMismatch; 73 75 74 76 bool m_modifiedDatabase;
Note: See TracChangeset
for help on using the changeset viewer.