1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/src/storage/DOMStorageDBThread.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1326 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "DOMStorageDBThread.h" 1.10 +#include "DOMStorageCache.h" 1.11 + 1.12 +#include "nsIEffectiveTLDService.h" 1.13 +#include "nsDirectoryServiceUtils.h" 1.14 +#include "nsAppDirectoryServiceDefs.h" 1.15 +#include "nsThreadUtils.h" 1.16 +#include "nsProxyRelease.h" 1.17 +#include "mozStorageCID.h" 1.18 +#include "mozStorageHelper.h" 1.19 +#include "mozIStorageService.h" 1.20 +#include "mozIStorageBindingParamsArray.h" 1.21 +#include "mozIStorageBindingParams.h" 1.22 +#include "mozIStorageValueArray.h" 1.23 +#include "mozIStorageFunction.h" 1.24 +#include "nsIObserverService.h" 1.25 +#include "nsIVariant.h" 1.26 +#include "mozilla/IOInterposer.h" 1.27 +#include "mozilla/Services.h" 1.28 + 1.29 +// How long we collect write oprerations 1.30 +// before they are flushed to the database 1.31 +// In milliseconds. 1.32 +#define FLUSHING_INTERVAL_MS 5000 1.33 + 1.34 +// Write Ahead Log's maximum size is 512KB 1.35 +#define MAX_WAL_SIZE_BYTES 512 * 1024 1.36 + 1.37 +namespace mozilla { 1.38 +namespace dom { 1.39 + 1.40 +DOMStorageDBBridge::DOMStorageDBBridge() 1.41 +{ 1.42 +} 1.43 + 1.44 + 1.45 +DOMStorageDBThread::DOMStorageDBThread() 1.46 +: mThread(nullptr) 1.47 +, mMonitor("DOMStorageThreadMonitor") 1.48 +, mStopIOThread(false) 1.49 +, mWALModeEnabled(false) 1.50 +, mDBReady(false) 1.51 +, mStatus(NS_OK) 1.52 +, mWorkerStatements(mWorkerConnection) 1.53 +, mReaderStatements(mReaderConnection) 1.54 +, mDirtyEpoch(0) 1.55 +, mFlushImmediately(false) 1.56 +, mPriorityCounter(0) 1.57 +{ 1.58 +} 1.59 + 1.60 +nsresult 1.61 +DOMStorageDBThread::Init() 1.62 +{ 1.63 + nsresult rv; 1.64 + 1.65 + // Need to determine location on the main thread, since 1.66 + // NS_GetSpecialDirectory access the atom table that can 1.67 + // be accessed only on the main thread. 1.68 + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1.69 + getter_AddRefs(mDatabaseFile)); 1.70 + NS_ENSURE_SUCCESS(rv, rv); 1.71 + 1.72 + rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite")); 1.73 + NS_ENSURE_SUCCESS(rv, rv); 1.74 + 1.75 + // Ensure mozIStorageService init on the main thread first. 1.76 + nsCOMPtr<mozIStorageService> service = 1.77 + do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); 1.78 + NS_ENSURE_SUCCESS(rv, rv); 1.79 + 1.80 + // Need to keep the lock to avoid setting mThread later then 1.81 + // the thread body executes. 1.82 + MonitorAutoLock monitor(mMonitor); 1.83 + 1.84 + mThread = PR_CreateThread(PR_USER_THREAD, &DOMStorageDBThread::ThreadFunc, this, 1.85 + PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 1.86 + 262144); 1.87 + if (!mThread) { 1.88 + return NS_ERROR_OUT_OF_MEMORY; 1.89 + } 1.90 + 1.91 + return NS_OK; 1.92 +} 1.93 + 1.94 +nsresult 1.95 +DOMStorageDBThread::Shutdown() 1.96 +{ 1.97 + if (!mThread) { 1.98 + return NS_ERROR_NOT_INITIALIZED; 1.99 + } 1.100 + 1.101 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer; 1.102 + 1.103 + { 1.104 + MonitorAutoLock monitor(mMonitor); 1.105 + 1.106 + // After we stop, no other operations can be accepted 1.107 + mFlushImmediately = true; 1.108 + mStopIOThread = true; 1.109 + monitor.Notify(); 1.110 + } 1.111 + 1.112 + PR_JoinThread(mThread); 1.113 + mThread = nullptr; 1.114 + 1.115 + return mStatus; 1.116 +} 1.117 + 1.118 +void 1.119 +DOMStorageDBThread::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync) 1.120 +{ 1.121 + if (!aForceSync && aCache->LoadedCount()) { 1.122 + // Preload already started for this cache, just wait for it to finish. 1.123 + // LoadWait will exit after LoadDone on the cache has been called. 1.124 + SetHigherPriority(); 1.125 + aCache->LoadWait(); 1.126 + SetDefaultPriority(); 1.127 + return; 1.128 + } 1.129 + 1.130 + // Bypass sync load when an update is pending in the queue to write, we would 1.131 + // get incosistent data in the cache. Also don't allow sync main-thread preload 1.132 + // when DB open and init is still pending on the background thread. 1.133 + if (mDBReady && mWALModeEnabled) { 1.134 + bool pendingTasks; 1.135 + { 1.136 + MonitorAutoLock monitor(mMonitor); 1.137 + pendingTasks = mPendingTasks.IsScopeUpdatePending(aCache->Scope()) || 1.138 + mPendingTasks.IsScopeClearPending(aCache->Scope()); 1.139 + } 1.140 + 1.141 + if (!pendingTasks) { 1.142 + // WAL is enabled, thus do the load synchronously on the main thread. 1.143 + DBOperation preload(DBOperation::opPreload, aCache); 1.144 + preload.PerformAndFinalize(this); 1.145 + return; 1.146 + } 1.147 + } 1.148 + 1.149 + // Need to go asynchronously since WAL is not allowed or scheduled updates 1.150 + // need to be flushed first. 1.151 + // Schedule preload for this cache as the first operation. 1.152 + nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache)); 1.153 + 1.154 + // LoadWait exits after LoadDone of the cache has been called. 1.155 + if (NS_SUCCEEDED(rv)) { 1.156 + aCache->LoadWait(); 1.157 + } 1.158 +} 1.159 + 1.160 +void 1.161 +DOMStorageDBThread::AsyncFlush() 1.162 +{ 1.163 + MonitorAutoLock monitor(mMonitor); 1.164 + mFlushImmediately = true; 1.165 + monitor.Notify(); 1.166 +} 1.167 + 1.168 +bool 1.169 +DOMStorageDBThread::ShouldPreloadScope(const nsACString& aScope) 1.170 +{ 1.171 + MonitorAutoLock monitor(mMonitor); 1.172 + return mScopesHavingData.Contains(aScope); 1.173 +} 1.174 + 1.175 +namespace { // anon 1.176 + 1.177 +PLDHashOperator 1.178 +GetScopesHavingDataEnum(nsCStringHashKey* aKey, void* aArg) 1.179 +{ 1.180 + InfallibleTArray<nsCString>* scopes = 1.181 + static_cast<InfallibleTArray<nsCString>*>(aArg); 1.182 + scopes->AppendElement(aKey->GetKey()); 1.183 + return PL_DHASH_NEXT; 1.184 +} 1.185 + 1.186 +} // anon 1.187 + 1.188 +void 1.189 +DOMStorageDBThread::GetScopesHavingData(InfallibleTArray<nsCString>* aScopes) 1.190 +{ 1.191 + MonitorAutoLock monitor(mMonitor); 1.192 + mScopesHavingData.EnumerateEntries(GetScopesHavingDataEnum, aScopes); 1.193 +} 1.194 + 1.195 +nsresult 1.196 +DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation) 1.197 +{ 1.198 + MonitorAutoLock monitor(mMonitor); 1.199 + 1.200 + // Sentinel to don't forget to delete the operation when we exit early. 1.201 + nsAutoPtr<DOMStorageDBThread::DBOperation> opScope(aOperation); 1.202 + 1.203 + if (mStopIOThread) { 1.204 + // Thread use after shutdown demanded. 1.205 + MOZ_ASSERT(false); 1.206 + return NS_ERROR_NOT_INITIALIZED; 1.207 + } 1.208 + 1.209 + if (NS_FAILED(mStatus)) { 1.210 + MonitorAutoUnlock unlock(mMonitor); 1.211 + aOperation->Finalize(mStatus); 1.212 + return mStatus; 1.213 + } 1.214 + 1.215 + switch (aOperation->Type()) { 1.216 + case DBOperation::opPreload: 1.217 + case DBOperation::opPreloadUrgent: 1.218 + if (mPendingTasks.IsScopeUpdatePending(aOperation->Scope())) { 1.219 + // If there is a pending update operation for the scope first do the flush 1.220 + // before we preload the cache. This may happen in an extremely rare case 1.221 + // when a child process throws away its cache before flush on the parent 1.222 + // has finished. If we would preloaded the cache as a priority operation 1.223 + // before the pending flush, we would have got an inconsistent cache content. 1.224 + mFlushImmediately = true; 1.225 + } else if (mPendingTasks.IsScopeClearPending(aOperation->Scope())) { 1.226 + // The scope is scheduled to be cleared, so just quickly load as empty. 1.227 + // We need to do this to prevent load of the DB data before the scope has 1.228 + // actually been cleared from the database. Preloads are processed 1.229 + // immediately before update and clear operations on the database that 1.230 + // are flushed periodically in batches. 1.231 + MonitorAutoUnlock unlock(mMonitor); 1.232 + aOperation->Finalize(NS_OK); 1.233 + return NS_OK; 1.234 + } 1.235 + // NO BREAK 1.236 + 1.237 + case DBOperation::opGetUsage: 1.238 + if (aOperation->Type() == DBOperation::opPreloadUrgent) { 1.239 + SetHigherPriority(); // Dropped back after urgent preload execution 1.240 + mPreloads.InsertElementAt(0, aOperation); 1.241 + } else { 1.242 + mPreloads.AppendElement(aOperation); 1.243 + } 1.244 + 1.245 + // DB operation adopted, don't delete it. 1.246 + opScope.forget(); 1.247 + 1.248 + // Immediately start executing this. 1.249 + monitor.Notify(); 1.250 + break; 1.251 + 1.252 + default: 1.253 + // Update operations are first collected, coalesced and then flushed 1.254 + // after a short time. 1.255 + mPendingTasks.Add(aOperation); 1.256 + 1.257 + // DB operation adopted, don't delete it. 1.258 + opScope.forget(); 1.259 + 1.260 + ScheduleFlush(); 1.261 + break; 1.262 + } 1.263 + 1.264 + return NS_OK; 1.265 +} 1.266 + 1.267 +void 1.268 +DOMStorageDBThread::SetHigherPriority() 1.269 +{ 1.270 + ++mPriorityCounter; 1.271 + PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT); 1.272 +} 1.273 + 1.274 +void 1.275 +DOMStorageDBThread::SetDefaultPriority() 1.276 +{ 1.277 + if (--mPriorityCounter <= 0) { 1.278 + PR_SetThreadPriority(mThread, PR_PRIORITY_LOW); 1.279 + } 1.280 +} 1.281 + 1.282 +void 1.283 +DOMStorageDBThread::ThreadFunc(void* aArg) 1.284 +{ 1.285 + PR_SetCurrentThreadName("localStorage DB"); 1.286 + mozilla::IOInterposer::RegisterCurrentThread(); 1.287 + 1.288 + DOMStorageDBThread* thread = static_cast<DOMStorageDBThread*>(aArg); 1.289 + thread->ThreadFunc(); 1.290 + mozilla::IOInterposer::UnregisterCurrentThread(); 1.291 +} 1.292 + 1.293 +void 1.294 +DOMStorageDBThread::ThreadFunc() 1.295 +{ 1.296 + nsresult rv = InitDatabase(); 1.297 + 1.298 + MonitorAutoLock lockMonitor(mMonitor); 1.299 + 1.300 + if (NS_FAILED(rv)) { 1.301 + mStatus = rv; 1.302 + mStopIOThread = true; 1.303 + return; 1.304 + } 1.305 + 1.306 + while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() || mPendingTasks.HasTasks())) { 1.307 + if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) { 1.308 + // Flush time is up or flush has been forced, do it now. 1.309 + UnscheduleFlush(); 1.310 + if (mPendingTasks.Prepare()) { 1.311 + { 1.312 + MonitorAutoUnlock unlockMonitor(mMonitor); 1.313 + rv = mPendingTasks.Execute(this); 1.314 + } 1.315 + 1.316 + if (!mPendingTasks.Finalize(rv)) { 1.317 + mStatus = rv; 1.318 + NS_WARNING("localStorage DB access broken"); 1.319 + } 1.320 + } 1.321 + NotifyFlushCompletion(); 1.322 + } else if (MOZ_LIKELY(mPreloads.Length())) { 1.323 + nsAutoPtr<DBOperation> op(mPreloads[0]); 1.324 + mPreloads.RemoveElementAt(0); 1.325 + { 1.326 + MonitorAutoUnlock unlockMonitor(mMonitor); 1.327 + op->PerformAndFinalize(this); 1.328 + } 1.329 + 1.330 + if (op->Type() == DBOperation::opPreloadUrgent) { 1.331 + SetDefaultPriority(); // urgent preload unscheduled 1.332 + } 1.333 + } else if (MOZ_UNLIKELY(!mStopIOThread)) { 1.334 + lockMonitor.Wait(TimeUntilFlush()); 1.335 + } 1.336 + } // thread loop 1.337 + 1.338 + mStatus = ShutdownDatabase(); 1.339 +} 1.340 + 1.341 +extern void 1.342 +ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult); 1.343 + 1.344 +namespace { // anon 1.345 + 1.346 +class nsReverseStringSQLFunction MOZ_FINAL : public mozIStorageFunction 1.347 +{ 1.348 + NS_DECL_ISUPPORTS 1.349 + NS_DECL_MOZISTORAGEFUNCTION 1.350 +}; 1.351 + 1.352 +NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction) 1.353 + 1.354 +NS_IMETHODIMP 1.355 +nsReverseStringSQLFunction::OnFunctionCall( 1.356 + mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) 1.357 +{ 1.358 + nsresult rv; 1.359 + 1.360 + nsAutoCString stringToReverse; 1.361 + rv = aFunctionArguments->GetUTF8String(0, stringToReverse); 1.362 + NS_ENSURE_SUCCESS(rv, rv); 1.363 + 1.364 + nsAutoCString result; 1.365 + ReverseString(stringToReverse, result); 1.366 + 1.367 + nsCOMPtr<nsIWritableVariant> outVar(do_CreateInstance( 1.368 + NS_VARIANT_CONTRACTID, &rv)); 1.369 + NS_ENSURE_SUCCESS(rv, rv); 1.370 + 1.371 + rv = outVar->SetAsAUTF8String(result); 1.372 + NS_ENSURE_SUCCESS(rv, rv); 1.373 + 1.374 + *aResult = outVar.get(); 1.375 + outVar.forget(); 1.376 + return NS_OK; 1.377 +} 1.378 + 1.379 +} // anon 1.380 + 1.381 +nsresult 1.382 +DOMStorageDBThread::OpenDatabaseConnection() 1.383 +{ 1.384 + nsresult rv; 1.385 + 1.386 + MOZ_ASSERT(!NS_IsMainThread()); 1.387 + 1.388 + nsCOMPtr<mozIStorageService> service 1.389 + = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); 1.390 + NS_ENSURE_SUCCESS(rv, rv); 1.391 + 1.392 + nsCOMPtr<mozIStorageConnection> connection; 1.393 + rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection)); 1.394 + if (rv == NS_ERROR_FILE_CORRUPTED) { 1.395 + // delete the db and try opening again 1.396 + rv = mDatabaseFile->Remove(false); 1.397 + NS_ENSURE_SUCCESS(rv, rv); 1.398 + rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection)); 1.399 + } 1.400 + NS_ENSURE_SUCCESS(rv, rv); 1.401 + 1.402 + return NS_OK; 1.403 +} 1.404 + 1.405 +nsresult 1.406 +DOMStorageDBThread::InitDatabase() 1.407 +{ 1.408 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_INIT_DATABASE_MS> timer; 1.409 + 1.410 + nsresult rv; 1.411 + 1.412 + // Here we are on the worker thread. This opens the worker connection. 1.413 + MOZ_ASSERT(!NS_IsMainThread()); 1.414 + 1.415 + rv = OpenDatabaseConnection(); 1.416 + NS_ENSURE_SUCCESS(rv, rv); 1.417 + 1.418 + rv = TryJournalMode(); 1.419 + NS_ENSURE_SUCCESS(rv, rv); 1.420 + 1.421 + // Create a read-only clone 1.422 + (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection)); 1.423 + NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE); 1.424 + 1.425 + mozStorageTransaction transaction(mWorkerConnection, false); 1.426 + 1.427 + // Ensure Gecko 1.9.1 storage table 1.428 + rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( 1.429 + "CREATE TABLE IF NOT EXISTS webappsstore2 (" 1.430 + "scope TEXT, " 1.431 + "key TEXT, " 1.432 + "value TEXT, " 1.433 + "secure INTEGER, " 1.434 + "owner TEXT)")); 1.435 + NS_ENSURE_SUCCESS(rv, rv); 1.436 + 1.437 + rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( 1.438 + "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index" 1.439 + " ON webappsstore2(scope, key)")); 1.440 + NS_ENSURE_SUCCESS(rv, rv); 1.441 + 1.442 + nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction()); 1.443 + NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY); 1.444 + 1.445 + rv = mWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1); 1.446 + NS_ENSURE_SUCCESS(rv, rv); 1.447 + 1.448 + bool exists; 1.449 + 1.450 + // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage 1.451 + // to actual webappsstore2 table and drop the obsolete table. First process 1.452 + // this newer table upgrade to priority potential duplicates from older 1.453 + // storage table. 1.454 + rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"), 1.455 + &exists); 1.456 + NS_ENSURE_SUCCESS(rv, rv); 1.457 + 1.458 + if (exists) { 1.459 + rv = mWorkerConnection->ExecuteSimpleSQL( 1.460 + NS_LITERAL_CSTRING("INSERT OR IGNORE INTO " 1.461 + "webappsstore2(scope, key, value, secure, owner) " 1.462 + "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner " 1.463 + "FROM webappsstore")); 1.464 + NS_ENSURE_SUCCESS(rv, rv); 1.465 + 1.466 + rv = mWorkerConnection->ExecuteSimpleSQL( 1.467 + NS_LITERAL_CSTRING("DROP TABLE webappsstore")); 1.468 + NS_ENSURE_SUCCESS(rv, rv); 1.469 + } 1.470 + 1.471 + // Check if there is storage of Gecko 1.8 and if so, upgrade that storage 1.472 + // to actual webappsstore2 table and drop the obsolete table. Potential 1.473 + // duplicates will be ignored. 1.474 + rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"), 1.475 + &exists); 1.476 + NS_ENSURE_SUCCESS(rv, rv); 1.477 + 1.478 + if (exists) { 1.479 + rv = mWorkerConnection->ExecuteSimpleSQL( 1.480 + NS_LITERAL_CSTRING("INSERT OR IGNORE INTO " 1.481 + "webappsstore2(scope, key, value, secure, owner) " 1.482 + "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain " 1.483 + "FROM moz_webappsstore")); 1.484 + NS_ENSURE_SUCCESS(rv, rv); 1.485 + 1.486 + rv = mWorkerConnection->ExecuteSimpleSQL( 1.487 + NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore")); 1.488 + NS_ENSURE_SUCCESS(rv, rv); 1.489 + } 1.490 + 1.491 + rv = transaction.Commit(); 1.492 + NS_ENSURE_SUCCESS(rv, rv); 1.493 + 1.494 + // Database open and all initiation operation are done. Switching this flag 1.495 + // to true allow main thread to read directly from the database. 1.496 + // If we would allow this sooner, we would have opened a window where main thread 1.497 + // read might operate on a totaly broken and incosistent database. 1.498 + mDBReady = true; 1.499 + 1.500 + // List scopes having any stored data 1.501 + nsCOMPtr<mozIStorageStatement> stmt; 1.502 + rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT DISTINCT scope FROM webappsstore2"), 1.503 + getter_AddRefs(stmt)); 1.504 + NS_ENSURE_SUCCESS(rv, rv); 1.505 + mozStorageStatementScoper scope(stmt); 1.506 + 1.507 + while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) { 1.508 + nsAutoCString foundScope; 1.509 + rv = stmt->GetUTF8String(0, foundScope); 1.510 + NS_ENSURE_SUCCESS(rv, rv); 1.511 + 1.512 + MonitorAutoLock monitor(mMonitor); 1.513 + mScopesHavingData.PutEntry(foundScope); 1.514 + } 1.515 + 1.516 + return NS_OK; 1.517 +} 1.518 + 1.519 +nsresult 1.520 +DOMStorageDBThread::SetJournalMode(bool aIsWal) 1.521 +{ 1.522 + nsresult rv; 1.523 + 1.524 + nsAutoCString stmtString( 1.525 + MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = "); 1.526 + if (aIsWal) { 1.527 + stmtString.AppendLiteral("wal"); 1.528 + } else { 1.529 + stmtString.AppendLiteral("truncate"); 1.530 + } 1.531 + 1.532 + nsCOMPtr<mozIStorageStatement> stmt; 1.533 + rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt)); 1.534 + NS_ENSURE_SUCCESS(rv, rv); 1.535 + mozStorageStatementScoper scope(stmt); 1.536 + 1.537 + bool hasResult = false; 1.538 + rv = stmt->ExecuteStep(&hasResult); 1.539 + NS_ENSURE_SUCCESS(rv, rv); 1.540 + if (!hasResult) { 1.541 + return NS_ERROR_FAILURE; 1.542 + } 1.543 + 1.544 + nsAutoCString journalMode; 1.545 + rv = stmt->GetUTF8String(0, journalMode); 1.546 + NS_ENSURE_SUCCESS(rv, rv); 1.547 + if ((aIsWal && !journalMode.EqualsLiteral("wal")) || 1.548 + (!aIsWal && !journalMode.EqualsLiteral("truncate"))) { 1.549 + return NS_ERROR_FAILURE; 1.550 + } 1.551 + 1.552 + return NS_OK; 1.553 +} 1.554 + 1.555 +nsresult 1.556 +DOMStorageDBThread::TryJournalMode() 1.557 +{ 1.558 + nsresult rv; 1.559 + 1.560 + rv = SetJournalMode(true); 1.561 + if (NS_FAILED(rv)) { 1.562 + mWALModeEnabled = false; 1.563 + 1.564 + rv = SetJournalMode(false); 1.565 + NS_ENSURE_SUCCESS(rv, rv); 1.566 + } else { 1.567 + mWALModeEnabled = true; 1.568 + 1.569 + rv = ConfigureWALBehavior(); 1.570 + NS_ENSURE_SUCCESS(rv, rv); 1.571 + } 1.572 + 1.573 + return NS_OK; 1.574 +} 1.575 + 1.576 +nsresult 1.577 +DOMStorageDBThread::ConfigureWALBehavior() 1.578 +{ 1.579 + // Get the DB's page size 1.580 + nsCOMPtr<mozIStorageStatement> stmt; 1.581 + nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING( 1.582 + MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size" 1.583 + ), getter_AddRefs(stmt)); 1.584 + NS_ENSURE_SUCCESS(rv, rv); 1.585 + 1.586 + bool hasResult = false; 1.587 + rv = stmt->ExecuteStep(&hasResult); 1.588 + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE); 1.589 + 1.590 + int32_t pageSize = 0; 1.591 + rv = stmt->GetInt32(0, &pageSize); 1.592 + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED); 1.593 + 1.594 + // Set the threshold for auto-checkpointing the WAL. 1.595 + // We don't want giant logs slowing down reads & shutdown. 1.596 + int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize); 1.597 + nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = "); 1.598 + thresholdPragma.AppendInt(thresholdInPages); 1.599 + rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma); 1.600 + NS_ENSURE_SUCCESS(rv, rv); 1.601 + 1.602 + // Set the maximum WAL log size to reduce footprint on mobile (large empty 1.603 + // WAL files will be truncated) 1.604 + nsAutoCString journalSizePragma("PRAGMA journal_size_limit = "); 1.605 + // bug 600307: mak recommends setting this to 3 times the auto-checkpoint threshold 1.606 + journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3); 1.607 + rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma); 1.608 + NS_ENSURE_SUCCESS(rv, rv); 1.609 + 1.610 + return NS_OK; 1.611 +} 1.612 + 1.613 +nsresult 1.614 +DOMStorageDBThread::ShutdownDatabase() 1.615 +{ 1.616 + // Has to be called on the worker thread. 1.617 + MOZ_ASSERT(!NS_IsMainThread()); 1.618 + 1.619 + nsresult rv = mStatus; 1.620 + 1.621 + mDBReady = false; 1.622 + 1.623 + // Finalize the cached statements. 1.624 + mReaderStatements.FinalizeStatements(); 1.625 + mWorkerStatements.FinalizeStatements(); 1.626 + 1.627 + if (mReaderConnection) { 1.628 + // No need to sync access to mReaderConnection since the main thread 1.629 + // is right now joining this thread, unable to execute any events. 1.630 + mReaderConnection->Close(); 1.631 + mReaderConnection = nullptr; 1.632 + } 1.633 + 1.634 + if (mWorkerConnection) { 1.635 + rv = mWorkerConnection->Close(); 1.636 + mWorkerConnection = nullptr; 1.637 + } 1.638 + 1.639 + return rv; 1.640 +} 1.641 + 1.642 +void 1.643 +DOMStorageDBThread::ScheduleFlush() 1.644 +{ 1.645 + if (mDirtyEpoch) { 1.646 + return; // Already scheduled 1.647 + } 1.648 + 1.649 + mDirtyEpoch = PR_IntervalNow() | 1; // Must be non-zero to indicate we are scheduled 1.650 + 1.651 + // Wake the monitor from indefinite sleep... 1.652 + mMonitor.Notify(); 1.653 +} 1.654 + 1.655 +void 1.656 +DOMStorageDBThread::UnscheduleFlush() 1.657 +{ 1.658 + // We are just about to do the flush, drop flags 1.659 + mFlushImmediately = false; 1.660 + mDirtyEpoch = 0; 1.661 +} 1.662 + 1.663 +PRIntervalTime 1.664 +DOMStorageDBThread::TimeUntilFlush() 1.665 +{ 1.666 + if (mFlushImmediately) { 1.667 + return 0; // Do it now regardless the timeout. 1.668 + } 1.669 + 1.670 + static_assert(PR_INTERVAL_NO_TIMEOUT != 0, 1.671 + "PR_INTERVAL_NO_TIMEOUT must be non-zero"); 1.672 + 1.673 + if (!mDirtyEpoch) { 1.674 + return PR_INTERVAL_NO_TIMEOUT; // No pending task... 1.675 + } 1.676 + 1.677 + static const PRIntervalTime kMaxAge = PR_MillisecondsToInterval(FLUSHING_INTERVAL_MS); 1.678 + 1.679 + PRIntervalTime now = PR_IntervalNow() | 1; 1.680 + PRIntervalTime age = now - mDirtyEpoch; 1.681 + if (age > kMaxAge) { 1.682 + return 0; // It is time. 1.683 + } 1.684 + 1.685 + return kMaxAge - age; // Time left, this is used to sleep the monitor 1.686 +} 1.687 + 1.688 +void 1.689 +DOMStorageDBThread::NotifyFlushCompletion() 1.690 +{ 1.691 +#ifdef DOM_STORAGE_TESTS 1.692 + if (!NS_IsMainThread()) { 1.693 + nsRefPtr<nsRunnableMethod<DOMStorageDBThread, void, false> > event = 1.694 + NS_NewNonOwningRunnableMethod(this, &DOMStorageDBThread::NotifyFlushCompletion); 1.695 + NS_DispatchToMainThread(event); 1.696 + return; 1.697 + } 1.698 + 1.699 + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 1.700 + if (obs) { 1.701 + obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr); 1.702 + } 1.703 +#endif 1.704 +} 1.705 + 1.706 +// DOMStorageDBThread::DBOperation 1.707 + 1.708 +DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType, 1.709 + DOMStorageCacheBridge* aCache, 1.710 + const nsAString& aKey, 1.711 + const nsAString& aValue) 1.712 +: mType(aType) 1.713 +, mCache(aCache) 1.714 +, mKey(aKey) 1.715 +, mValue(aValue) 1.716 +{ 1.717 + MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation); 1.718 +} 1.719 + 1.720 +DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType, 1.721 + DOMStorageUsageBridge* aUsage) 1.722 +: mType(aType) 1.723 +, mUsage(aUsage) 1.724 +{ 1.725 + MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation); 1.726 +} 1.727 + 1.728 +DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType, 1.729 + const nsACString& aScope) 1.730 +: mType(aType) 1.731 +, mCache(nullptr) 1.732 +, mScope(aScope) 1.733 +{ 1.734 + MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation); 1.735 +} 1.736 + 1.737 +DOMStorageDBThread::DBOperation::~DBOperation() 1.738 +{ 1.739 + MOZ_COUNT_DTOR(DOMStorageDBThread::DBOperation); 1.740 +} 1.741 + 1.742 +const nsCString 1.743 +DOMStorageDBThread::DBOperation::Scope() 1.744 +{ 1.745 + if (mCache) { 1.746 + return mCache->Scope(); 1.747 + } 1.748 + 1.749 + return mScope; 1.750 +} 1.751 + 1.752 +const nsCString 1.753 +DOMStorageDBThread::DBOperation::Target() 1.754 +{ 1.755 + switch (mType) { 1.756 + case opAddItem: 1.757 + case opUpdateItem: 1.758 + case opRemoveItem: 1.759 + return Scope() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey); 1.760 + 1.761 + default: 1.762 + return Scope(); 1.763 + } 1.764 +} 1.765 + 1.766 +void 1.767 +DOMStorageDBThread::DBOperation::PerformAndFinalize(DOMStorageDBThread* aThread) 1.768 +{ 1.769 + Finalize(Perform(aThread)); 1.770 +} 1.771 + 1.772 +nsresult 1.773 +DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread) 1.774 +{ 1.775 + nsresult rv; 1.776 + 1.777 + switch (mType) { 1.778 + case opPreload: 1.779 + case opPreloadUrgent: 1.780 + { 1.781 + // Already loaded? 1.782 + if (mCache->Loaded()) { 1.783 + break; 1.784 + } 1.785 + 1.786 + StatementCache* statements; 1.787 + if (MOZ_UNLIKELY(NS_IsMainThread())) { 1.788 + statements = &aThread->mReaderStatements; 1.789 + } else { 1.790 + statements = &aThread->mWorkerStatements; 1.791 + } 1.792 + 1.793 + // OFFSET is an optimization when we have to do a sync load 1.794 + // and cache has already loaded some parts asynchronously. 1.795 + // It skips keys we have already loaded. 1.796 + nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement( 1.797 + "SELECT key, value FROM webappsstore2 " 1.798 + "WHERE scope = :scope ORDER BY key " 1.799 + "LIMIT -1 OFFSET :offset"); 1.800 + NS_ENSURE_STATE(stmt); 1.801 + mozStorageStatementScoper scope(stmt); 1.802 + 1.803 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), 1.804 + mCache->Scope()); 1.805 + NS_ENSURE_SUCCESS(rv, rv); 1.806 + 1.807 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"), 1.808 + static_cast<int32_t>(mCache->LoadedCount())); 1.809 + NS_ENSURE_SUCCESS(rv, rv); 1.810 + 1.811 + bool exists; 1.812 + while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) { 1.813 + nsAutoString key; 1.814 + rv = stmt->GetString(0, key); 1.815 + NS_ENSURE_SUCCESS(rv, rv); 1.816 + 1.817 + nsAutoString value; 1.818 + rv = stmt->GetString(1, value); 1.819 + NS_ENSURE_SUCCESS(rv, rv); 1.820 + 1.821 + if (!mCache->LoadItem(key, value)) { 1.822 + break; 1.823 + } 1.824 + } 1.825 + 1.826 + mCache->LoadDone(NS_OK); 1.827 + break; 1.828 + } 1.829 + 1.830 + case opGetUsage: 1.831 + { 1.832 + nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement( 1.833 + "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2" 1.834 + " WHERE scope LIKE :scope" 1.835 + ); 1.836 + NS_ENSURE_STATE(stmt); 1.837 + 1.838 + mozStorageStatementScoper scope(stmt); 1.839 + 1.840 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), 1.841 + mUsage->Scope() + NS_LITERAL_CSTRING("%")); 1.842 + NS_ENSURE_SUCCESS(rv, rv); 1.843 + 1.844 + bool exists; 1.845 + rv = stmt->ExecuteStep(&exists); 1.846 + NS_ENSURE_SUCCESS(rv, rv); 1.847 + 1.848 + int64_t usage = 0; 1.849 + if (exists) { 1.850 + rv = stmt->GetInt64(0, &usage); 1.851 + NS_ENSURE_SUCCESS(rv, rv); 1.852 + } 1.853 + 1.854 + mUsage->LoadUsage(usage); 1.855 + break; 1.856 + } 1.857 + 1.858 + case opAddItem: 1.859 + case opUpdateItem: 1.860 + { 1.861 + MOZ_ASSERT(!NS_IsMainThread()); 1.862 + 1.863 + nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement( 1.864 + "INSERT OR REPLACE INTO webappsstore2 (scope, key, value) " 1.865 + "VALUES (:scope, :key, :value) " 1.866 + ); 1.867 + NS_ENSURE_STATE(stmt); 1.868 + 1.869 + mozStorageStatementScoper scope(stmt); 1.870 + 1.871 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), 1.872 + mCache->Scope()); 1.873 + NS_ENSURE_SUCCESS(rv, rv); 1.874 + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), 1.875 + mKey); 1.876 + NS_ENSURE_SUCCESS(rv, rv); 1.877 + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), 1.878 + mValue); 1.879 + NS_ENSURE_SUCCESS(rv, rv); 1.880 + 1.881 + rv = stmt->Execute(); 1.882 + NS_ENSURE_SUCCESS(rv, rv); 1.883 + 1.884 + aThread->mScopesHavingData.PutEntry(Scope()); 1.885 + break; 1.886 + } 1.887 + 1.888 + case opRemoveItem: 1.889 + { 1.890 + MOZ_ASSERT(!NS_IsMainThread()); 1.891 + 1.892 + nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement( 1.893 + "DELETE FROM webappsstore2 " 1.894 + "WHERE scope = :scope " 1.895 + "AND key = :key " 1.896 + ); 1.897 + NS_ENSURE_STATE(stmt); 1.898 + mozStorageStatementScoper scope(stmt); 1.899 + 1.900 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), 1.901 + mCache->Scope()); 1.902 + NS_ENSURE_SUCCESS(rv, rv); 1.903 + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), 1.904 + mKey); 1.905 + NS_ENSURE_SUCCESS(rv, rv); 1.906 + 1.907 + rv = stmt->Execute(); 1.908 + NS_ENSURE_SUCCESS(rv, rv); 1.909 + 1.910 + break; 1.911 + } 1.912 + 1.913 + case opClear: 1.914 + { 1.915 + MOZ_ASSERT(!NS_IsMainThread()); 1.916 + 1.917 + nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement( 1.918 + "DELETE FROM webappsstore2 " 1.919 + "WHERE scope = :scope" 1.920 + ); 1.921 + NS_ENSURE_STATE(stmt); 1.922 + mozStorageStatementScoper scope(stmt); 1.923 + 1.924 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), 1.925 + mCache->Scope()); 1.926 + NS_ENSURE_SUCCESS(rv, rv); 1.927 + 1.928 + rv = stmt->Execute(); 1.929 + NS_ENSURE_SUCCESS(rv, rv); 1.930 + 1.931 + aThread->mScopesHavingData.RemoveEntry(Scope()); 1.932 + break; 1.933 + } 1.934 + 1.935 + case opClearAll: 1.936 + { 1.937 + MOZ_ASSERT(!NS_IsMainThread()); 1.938 + 1.939 + nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement( 1.940 + "DELETE FROM webappsstore2" 1.941 + ); 1.942 + NS_ENSURE_STATE(stmt); 1.943 + mozStorageStatementScoper scope(stmt); 1.944 + 1.945 + rv = stmt->Execute(); 1.946 + NS_ENSURE_SUCCESS(rv, rv); 1.947 + 1.948 + aThread->mScopesHavingData.Clear(); 1.949 + break; 1.950 + } 1.951 + 1.952 + case opClearMatchingScope: 1.953 + { 1.954 + MOZ_ASSERT(!NS_IsMainThread()); 1.955 + 1.956 + nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement( 1.957 + "DELETE FROM webappsstore2" 1.958 + " WHERE scope GLOB :scope" 1.959 + ); 1.960 + NS_ENSURE_STATE(stmt); 1.961 + mozStorageStatementScoper scope(stmt); 1.962 + 1.963 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), 1.964 + mScope + NS_LITERAL_CSTRING("*")); 1.965 + NS_ENSURE_SUCCESS(rv, rv); 1.966 + 1.967 + rv = stmt->Execute(); 1.968 + NS_ENSURE_SUCCESS(rv, rv); 1.969 + 1.970 + break; 1.971 + } 1.972 + 1.973 + default: 1.974 + NS_ERROR("Unknown task type"); 1.975 + break; 1.976 + } 1.977 + 1.978 + return NS_OK; 1.979 +} 1.980 + 1.981 +void 1.982 +DOMStorageDBThread::DBOperation::Finalize(nsresult aRv) 1.983 +{ 1.984 + switch (mType) { 1.985 + case opPreloadUrgent: 1.986 + case opPreload: 1.987 + if (NS_FAILED(aRv)) { 1.988 + // When we are here, something failed when loading from the database. 1.989 + // Notify that the storage is loaded to prevent deadlock of the main thread, 1.990 + // even though it is actually empty or incomplete. 1.991 + NS_WARNING("Failed to preload localStorage"); 1.992 + } 1.993 + 1.994 + mCache->LoadDone(aRv); 1.995 + break; 1.996 + 1.997 + case opGetUsage: 1.998 + if (NS_FAILED(aRv)) { 1.999 + mUsage->LoadUsage(0); 1.1000 + } 1.1001 + 1.1002 + break; 1.1003 + 1.1004 + default: 1.1005 + if (NS_FAILED(aRv)) { 1.1006 + NS_WARNING("localStorage update/clear operation failed," 1.1007 + " data may not persist or clean up"); 1.1008 + } 1.1009 + 1.1010 + break; 1.1011 + } 1.1012 +} 1.1013 + 1.1014 +// DOMStorageDBThread::PendingOperations 1.1015 + 1.1016 +DOMStorageDBThread::PendingOperations::PendingOperations() 1.1017 +: mFlushFailureCount(0) 1.1018 +{ 1.1019 +} 1.1020 + 1.1021 +bool 1.1022 +DOMStorageDBThread::PendingOperations::HasTasks() 1.1023 +{ 1.1024 + return !!mUpdates.Count() || !!mClears.Count(); 1.1025 +} 1.1026 + 1.1027 +namespace { // anon 1.1028 + 1.1029 +PLDHashOperator 1.1030 +ForgetUpdatesForScope(const nsACString& aMapping, 1.1031 + nsAutoPtr<DOMStorageDBThread::DBOperation>& aPendingTask, 1.1032 + void* aArg) 1.1033 +{ 1.1034 + DOMStorageDBThread::DBOperation* newOp = static_cast<DOMStorageDBThread::DBOperation*>(aArg); 1.1035 + 1.1036 + if (newOp->Type() == DOMStorageDBThread::DBOperation::opClear && 1.1037 + aPendingTask->Scope() != newOp->Scope()) { 1.1038 + return PL_DHASH_NEXT; 1.1039 + } 1.1040 + 1.1041 + if (newOp->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope && 1.1042 + !StringBeginsWith(aPendingTask->Scope(), newOp->Scope())) { 1.1043 + return PL_DHASH_NEXT; 1.1044 + } 1.1045 + 1.1046 + return PL_DHASH_REMOVE; 1.1047 +} 1.1048 + 1.1049 +} // anon 1.1050 + 1.1051 +bool 1.1052 +DOMStorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp, 1.1053 + DBOperation::OperationType aPendingType, 1.1054 + DBOperation::OperationType aNewType) 1.1055 +{ 1.1056 + if (aNewOp->Type() != aNewType) { 1.1057 + return false; 1.1058 + } 1.1059 + 1.1060 + DOMStorageDBThread::DBOperation* pendingTask; 1.1061 + if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) { 1.1062 + return false; 1.1063 + } 1.1064 + 1.1065 + if (pendingTask->Type() != aPendingType) { 1.1066 + return false; 1.1067 + } 1.1068 + 1.1069 + return true; 1.1070 +} 1.1071 + 1.1072 +void 1.1073 +DOMStorageDBThread::PendingOperations::Add(DOMStorageDBThread::DBOperation* aOperation) 1.1074 +{ 1.1075 + // Optimize: when a key to remove has never been written to disk 1.1076 + // just bypass this operation. A kew is new when an operation scheduled 1.1077 + // to write it to the database is of type opAddItem. 1.1078 + if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opRemoveItem)) { 1.1079 + mUpdates.Remove(aOperation->Target()); 1.1080 + delete aOperation; 1.1081 + return; 1.1082 + } 1.1083 + 1.1084 + // Optimize: when changing a key that is new and has never been 1.1085 + // written to disk, keep type of the operation to store it at opAddItem. 1.1086 + // This allows optimization to just forget adding a new key when 1.1087 + // it is removed from the storage before flush. 1.1088 + if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opUpdateItem)) { 1.1089 + aOperation->mType = DBOperation::opAddItem; 1.1090 + } 1.1091 + 1.1092 + // Optimize: to prevent lose of remove operation on a key when doing 1.1093 + // remove/set/remove on a previously existing key we have to change 1.1094 + // opAddItem to opUpdateItem on the new operation when there is opRemoveItem 1.1095 + // pending for the key. 1.1096 + if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem, DBOperation::opAddItem)) { 1.1097 + aOperation->mType = DBOperation::opUpdateItem; 1.1098 + } 1.1099 + 1.1100 + switch (aOperation->Type()) 1.1101 + { 1.1102 + // Operations on single keys 1.1103 + 1.1104 + case DBOperation::opAddItem: 1.1105 + case DBOperation::opUpdateItem: 1.1106 + case DBOperation::opRemoveItem: 1.1107 + // Override any existing operation for the target (=scope+key). 1.1108 + mUpdates.Put(aOperation->Target(), aOperation); 1.1109 + break; 1.1110 + 1.1111 + // Clear operations 1.1112 + 1.1113 + case DBOperation::opClear: 1.1114 + case DBOperation::opClearMatchingScope: 1.1115 + // Drop all update (insert/remove) operations for equivavelent or matching scope. 1.1116 + // We do this as an optimization as well as a must based on the logic, 1.1117 + // if we would not delete the update tasks, changes would have been stored 1.1118 + // to the database after clear operations have been executed. 1.1119 + mUpdates.Enumerate(ForgetUpdatesForScope, aOperation); 1.1120 + mClears.Put(aOperation->Target(), aOperation); 1.1121 + break; 1.1122 + 1.1123 + case DBOperation::opClearAll: 1.1124 + // Drop simply everything, this is a super-operation. 1.1125 + mUpdates.Clear(); 1.1126 + mClears.Clear(); 1.1127 + mClears.Put(aOperation->Target(), aOperation); 1.1128 + break; 1.1129 + 1.1130 + default: 1.1131 + MOZ_ASSERT(false); 1.1132 + break; 1.1133 + } 1.1134 +} 1.1135 + 1.1136 +namespace { // anon 1.1137 + 1.1138 +PLDHashOperator 1.1139 +CollectTasks(const nsACString& aMapping, nsAutoPtr<DOMStorageDBThread::DBOperation>& aOperation, void* aArg) 1.1140 +{ 1.1141 + nsTArray<nsAutoPtr<DOMStorageDBThread::DBOperation> >* tasks = 1.1142 + static_cast<nsTArray<nsAutoPtr<DOMStorageDBThread::DBOperation> >*>(aArg); 1.1143 + 1.1144 + tasks->AppendElement(aOperation.forget()); 1.1145 + return PL_DHASH_NEXT; 1.1146 +} 1.1147 + 1.1148 +} // anon 1.1149 + 1.1150 +bool 1.1151 +DOMStorageDBThread::PendingOperations::Prepare() 1.1152 +{ 1.1153 + // Called under the lock 1.1154 + 1.1155 + // First collect clear operations and then updates, we can 1.1156 + // do this since whenever a clear operation for a scope is 1.1157 + // scheduled, we drop all updates matching that scope. So, 1.1158 + // all scope-related update operations we have here now were 1.1159 + // scheduled after the clear operations. 1.1160 + mClears.Enumerate(CollectTasks, &mExecList); 1.1161 + mClears.Clear(); 1.1162 + 1.1163 + mUpdates.Enumerate(CollectTasks, &mExecList); 1.1164 + mUpdates.Clear(); 1.1165 + 1.1166 + return !!mExecList.Length(); 1.1167 +} 1.1168 + 1.1169 +nsresult 1.1170 +DOMStorageDBThread::PendingOperations::Execute(DOMStorageDBThread* aThread) 1.1171 +{ 1.1172 + // Called outside the lock 1.1173 + 1.1174 + mozStorageTransaction transaction(aThread->mWorkerConnection, false); 1.1175 + 1.1176 + nsresult rv; 1.1177 + 1.1178 + for (uint32_t i = 0; i < mExecList.Length(); ++i) { 1.1179 + DOMStorageDBThread::DBOperation* task = mExecList[i]; 1.1180 + rv = task->Perform(aThread); 1.1181 + if (NS_FAILED(rv)) { 1.1182 + return rv; 1.1183 + } 1.1184 + } 1.1185 + 1.1186 + rv = transaction.Commit(); 1.1187 + if (NS_FAILED(rv)) { 1.1188 + return rv; 1.1189 + } 1.1190 + 1.1191 + return NS_OK; 1.1192 +} 1.1193 + 1.1194 +bool 1.1195 +DOMStorageDBThread::PendingOperations::Finalize(nsresult aRv) 1.1196 +{ 1.1197 + // Called under the lock 1.1198 + 1.1199 + // The list is kept on a failure to retry it 1.1200 + if (NS_FAILED(aRv)) { 1.1201 + // XXX Followup: we may try to reopen the database and flush these 1.1202 + // pending tasks, however testing showed that even though I/O is actually 1.1203 + // broken some amount of operations is left in sqlite+system buffers and 1.1204 + // seems like successfully flushed to disk. 1.1205 + // Tested by removing a flash card and disconnecting from network while 1.1206 + // using a network drive on Windows system. 1.1207 + NS_WARNING("Flush operation on localStorage database failed"); 1.1208 + 1.1209 + ++mFlushFailureCount; 1.1210 + 1.1211 + return mFlushFailureCount >= 5; 1.1212 + } 1.1213 + 1.1214 + mFlushFailureCount = 0; 1.1215 + mExecList.Clear(); 1.1216 + return true; 1.1217 +} 1.1218 + 1.1219 +namespace { // anon 1.1220 + 1.1221 +class FindPendingOperationForScopeData 1.1222 +{ 1.1223 +public: 1.1224 + FindPendingOperationForScopeData(const nsACString& aScope) : mScope(aScope), mFound(false) {} 1.1225 + nsCString mScope; 1.1226 + bool mFound; 1.1227 +}; 1.1228 + 1.1229 +PLDHashOperator 1.1230 +FindPendingClearForScope(const nsACString& aMapping, 1.1231 + DOMStorageDBThread::DBOperation* aPendingOperation, 1.1232 + void* aArg) 1.1233 +{ 1.1234 + FindPendingOperationForScopeData* data = 1.1235 + static_cast<FindPendingOperationForScopeData*>(aArg); 1.1236 + 1.1237 + if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearAll) { 1.1238 + data->mFound = true; 1.1239 + return PL_DHASH_STOP; 1.1240 + } 1.1241 + 1.1242 + if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClear && 1.1243 + data->mScope == aPendingOperation->Scope()) { 1.1244 + data->mFound = true; 1.1245 + return PL_DHASH_STOP; 1.1246 + } 1.1247 + 1.1248 + if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope && 1.1249 + StringBeginsWith(data->mScope, aPendingOperation->Scope())) { 1.1250 + data->mFound = true; 1.1251 + return PL_DHASH_STOP; 1.1252 + } 1.1253 + 1.1254 + return PL_DHASH_NEXT; 1.1255 +} 1.1256 + 1.1257 +} // anon 1.1258 + 1.1259 +bool 1.1260 +DOMStorageDBThread::PendingOperations::IsScopeClearPending(const nsACString& aScope) 1.1261 +{ 1.1262 + // Called under the lock 1.1263 + 1.1264 + FindPendingOperationForScopeData data(aScope); 1.1265 + mClears.EnumerateRead(FindPendingClearForScope, &data); 1.1266 + if (data.mFound) { 1.1267 + return true; 1.1268 + } 1.1269 + 1.1270 + for (uint32_t i = 0; i < mExecList.Length(); ++i) { 1.1271 + DOMStorageDBThread::DBOperation* task = mExecList[i]; 1.1272 + FindPendingClearForScope(EmptyCString(), task, &data); 1.1273 + 1.1274 + if (data.mFound) { 1.1275 + return true; 1.1276 + } 1.1277 + } 1.1278 + 1.1279 + return false; 1.1280 +} 1.1281 + 1.1282 +namespace { // anon 1.1283 + 1.1284 +PLDHashOperator 1.1285 +FindPendingUpdateForScope(const nsACString& aMapping, 1.1286 + DOMStorageDBThread::DBOperation* aPendingOperation, 1.1287 + void* aArg) 1.1288 +{ 1.1289 + FindPendingOperationForScopeData* data = 1.1290 + static_cast<FindPendingOperationForScopeData*>(aArg); 1.1291 + 1.1292 + if ((aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opAddItem || 1.1293 + aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opUpdateItem || 1.1294 + aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opRemoveItem) && 1.1295 + data->mScope == aPendingOperation->Scope()) { 1.1296 + data->mFound = true; 1.1297 + return PL_DHASH_STOP; 1.1298 + } 1.1299 + 1.1300 + return PL_DHASH_NEXT; 1.1301 +} 1.1302 + 1.1303 +} // anon 1.1304 + 1.1305 +bool 1.1306 +DOMStorageDBThread::PendingOperations::IsScopeUpdatePending(const nsACString& aScope) 1.1307 +{ 1.1308 + // Called under the lock 1.1309 + 1.1310 + FindPendingOperationForScopeData data(aScope); 1.1311 + mUpdates.EnumerateRead(FindPendingUpdateForScope, &data); 1.1312 + if (data.mFound) { 1.1313 + return true; 1.1314 + } 1.1315 + 1.1316 + for (uint32_t i = 0; i < mExecList.Length(); ++i) { 1.1317 + DOMStorageDBThread::DBOperation* task = mExecList[i]; 1.1318 + FindPendingUpdateForScope(EmptyCString(), task, &data); 1.1319 + 1.1320 + if (data.mFound) { 1.1321 + return true; 1.1322 + } 1.1323 + } 1.1324 + 1.1325 + return false; 1.1326 +} 1.1327 + 1.1328 +} // ::dom 1.1329 +} // ::mozilla