dom/src/storage/DOMStorageDBThread.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "DOMStorageDBThread.h"
michael@0 7 #include "DOMStorageCache.h"
michael@0 8
michael@0 9 #include "nsIEffectiveTLDService.h"
michael@0 10 #include "nsDirectoryServiceUtils.h"
michael@0 11 #include "nsAppDirectoryServiceDefs.h"
michael@0 12 #include "nsThreadUtils.h"
michael@0 13 #include "nsProxyRelease.h"
michael@0 14 #include "mozStorageCID.h"
michael@0 15 #include "mozStorageHelper.h"
michael@0 16 #include "mozIStorageService.h"
michael@0 17 #include "mozIStorageBindingParamsArray.h"
michael@0 18 #include "mozIStorageBindingParams.h"
michael@0 19 #include "mozIStorageValueArray.h"
michael@0 20 #include "mozIStorageFunction.h"
michael@0 21 #include "nsIObserverService.h"
michael@0 22 #include "nsIVariant.h"
michael@0 23 #include "mozilla/IOInterposer.h"
michael@0 24 #include "mozilla/Services.h"
michael@0 25
michael@0 26 // How long we collect write oprerations
michael@0 27 // before they are flushed to the database
michael@0 28 // In milliseconds.
michael@0 29 #define FLUSHING_INTERVAL_MS 5000
michael@0 30
michael@0 31 // Write Ahead Log's maximum size is 512KB
michael@0 32 #define MAX_WAL_SIZE_BYTES 512 * 1024
michael@0 33
michael@0 34 namespace mozilla {
michael@0 35 namespace dom {
michael@0 36
michael@0 37 DOMStorageDBBridge::DOMStorageDBBridge()
michael@0 38 {
michael@0 39 }
michael@0 40
michael@0 41
michael@0 42 DOMStorageDBThread::DOMStorageDBThread()
michael@0 43 : mThread(nullptr)
michael@0 44 , mMonitor("DOMStorageThreadMonitor")
michael@0 45 , mStopIOThread(false)
michael@0 46 , mWALModeEnabled(false)
michael@0 47 , mDBReady(false)
michael@0 48 , mStatus(NS_OK)
michael@0 49 , mWorkerStatements(mWorkerConnection)
michael@0 50 , mReaderStatements(mReaderConnection)
michael@0 51 , mDirtyEpoch(0)
michael@0 52 , mFlushImmediately(false)
michael@0 53 , mPriorityCounter(0)
michael@0 54 {
michael@0 55 }
michael@0 56
michael@0 57 nsresult
michael@0 58 DOMStorageDBThread::Init()
michael@0 59 {
michael@0 60 nsresult rv;
michael@0 61
michael@0 62 // Need to determine location on the main thread, since
michael@0 63 // NS_GetSpecialDirectory access the atom table that can
michael@0 64 // be accessed only on the main thread.
michael@0 65 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
michael@0 66 getter_AddRefs(mDatabaseFile));
michael@0 67 NS_ENSURE_SUCCESS(rv, rv);
michael@0 68
michael@0 69 rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
michael@0 70 NS_ENSURE_SUCCESS(rv, rv);
michael@0 71
michael@0 72 // Ensure mozIStorageService init on the main thread first.
michael@0 73 nsCOMPtr<mozIStorageService> service =
michael@0 74 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
michael@0 75 NS_ENSURE_SUCCESS(rv, rv);
michael@0 76
michael@0 77 // Need to keep the lock to avoid setting mThread later then
michael@0 78 // the thread body executes.
michael@0 79 MonitorAutoLock monitor(mMonitor);
michael@0 80
michael@0 81 mThread = PR_CreateThread(PR_USER_THREAD, &DOMStorageDBThread::ThreadFunc, this,
michael@0 82 PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
michael@0 83 262144);
michael@0 84 if (!mThread) {
michael@0 85 return NS_ERROR_OUT_OF_MEMORY;
michael@0 86 }
michael@0 87
michael@0 88 return NS_OK;
michael@0 89 }
michael@0 90
michael@0 91 nsresult
michael@0 92 DOMStorageDBThread::Shutdown()
michael@0 93 {
michael@0 94 if (!mThread) {
michael@0 95 return NS_ERROR_NOT_INITIALIZED;
michael@0 96 }
michael@0 97
michael@0 98 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
michael@0 99
michael@0 100 {
michael@0 101 MonitorAutoLock monitor(mMonitor);
michael@0 102
michael@0 103 // After we stop, no other operations can be accepted
michael@0 104 mFlushImmediately = true;
michael@0 105 mStopIOThread = true;
michael@0 106 monitor.Notify();
michael@0 107 }
michael@0 108
michael@0 109 PR_JoinThread(mThread);
michael@0 110 mThread = nullptr;
michael@0 111
michael@0 112 return mStatus;
michael@0 113 }
michael@0 114
michael@0 115 void
michael@0 116 DOMStorageDBThread::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
michael@0 117 {
michael@0 118 if (!aForceSync && aCache->LoadedCount()) {
michael@0 119 // Preload already started for this cache, just wait for it to finish.
michael@0 120 // LoadWait will exit after LoadDone on the cache has been called.
michael@0 121 SetHigherPriority();
michael@0 122 aCache->LoadWait();
michael@0 123 SetDefaultPriority();
michael@0 124 return;
michael@0 125 }
michael@0 126
michael@0 127 // Bypass sync load when an update is pending in the queue to write, we would
michael@0 128 // get incosistent data in the cache. Also don't allow sync main-thread preload
michael@0 129 // when DB open and init is still pending on the background thread.
michael@0 130 if (mDBReady && mWALModeEnabled) {
michael@0 131 bool pendingTasks;
michael@0 132 {
michael@0 133 MonitorAutoLock monitor(mMonitor);
michael@0 134 pendingTasks = mPendingTasks.IsScopeUpdatePending(aCache->Scope()) ||
michael@0 135 mPendingTasks.IsScopeClearPending(aCache->Scope());
michael@0 136 }
michael@0 137
michael@0 138 if (!pendingTasks) {
michael@0 139 // WAL is enabled, thus do the load synchronously on the main thread.
michael@0 140 DBOperation preload(DBOperation::opPreload, aCache);
michael@0 141 preload.PerformAndFinalize(this);
michael@0 142 return;
michael@0 143 }
michael@0 144 }
michael@0 145
michael@0 146 // Need to go asynchronously since WAL is not allowed or scheduled updates
michael@0 147 // need to be flushed first.
michael@0 148 // Schedule preload for this cache as the first operation.
michael@0 149 nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache));
michael@0 150
michael@0 151 // LoadWait exits after LoadDone of the cache has been called.
michael@0 152 if (NS_SUCCEEDED(rv)) {
michael@0 153 aCache->LoadWait();
michael@0 154 }
michael@0 155 }
michael@0 156
michael@0 157 void
michael@0 158 DOMStorageDBThread::AsyncFlush()
michael@0 159 {
michael@0 160 MonitorAutoLock monitor(mMonitor);
michael@0 161 mFlushImmediately = true;
michael@0 162 monitor.Notify();
michael@0 163 }
michael@0 164
michael@0 165 bool
michael@0 166 DOMStorageDBThread::ShouldPreloadScope(const nsACString& aScope)
michael@0 167 {
michael@0 168 MonitorAutoLock monitor(mMonitor);
michael@0 169 return mScopesHavingData.Contains(aScope);
michael@0 170 }
michael@0 171
michael@0 172 namespace { // anon
michael@0 173
michael@0 174 PLDHashOperator
michael@0 175 GetScopesHavingDataEnum(nsCStringHashKey* aKey, void* aArg)
michael@0 176 {
michael@0 177 InfallibleTArray<nsCString>* scopes =
michael@0 178 static_cast<InfallibleTArray<nsCString>*>(aArg);
michael@0 179 scopes->AppendElement(aKey->GetKey());
michael@0 180 return PL_DHASH_NEXT;
michael@0 181 }
michael@0 182
michael@0 183 } // anon
michael@0 184
michael@0 185 void
michael@0 186 DOMStorageDBThread::GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
michael@0 187 {
michael@0 188 MonitorAutoLock monitor(mMonitor);
michael@0 189 mScopesHavingData.EnumerateEntries(GetScopesHavingDataEnum, aScopes);
michael@0 190 }
michael@0 191
michael@0 192 nsresult
michael@0 193 DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation)
michael@0 194 {
michael@0 195 MonitorAutoLock monitor(mMonitor);
michael@0 196
michael@0 197 // Sentinel to don't forget to delete the operation when we exit early.
michael@0 198 nsAutoPtr<DOMStorageDBThread::DBOperation> opScope(aOperation);
michael@0 199
michael@0 200 if (mStopIOThread) {
michael@0 201 // Thread use after shutdown demanded.
michael@0 202 MOZ_ASSERT(false);
michael@0 203 return NS_ERROR_NOT_INITIALIZED;
michael@0 204 }
michael@0 205
michael@0 206 if (NS_FAILED(mStatus)) {
michael@0 207 MonitorAutoUnlock unlock(mMonitor);
michael@0 208 aOperation->Finalize(mStatus);
michael@0 209 return mStatus;
michael@0 210 }
michael@0 211
michael@0 212 switch (aOperation->Type()) {
michael@0 213 case DBOperation::opPreload:
michael@0 214 case DBOperation::opPreloadUrgent:
michael@0 215 if (mPendingTasks.IsScopeUpdatePending(aOperation->Scope())) {
michael@0 216 // If there is a pending update operation for the scope first do the flush
michael@0 217 // before we preload the cache. This may happen in an extremely rare case
michael@0 218 // when a child process throws away its cache before flush on the parent
michael@0 219 // has finished. If we would preloaded the cache as a priority operation
michael@0 220 // before the pending flush, we would have got an inconsistent cache content.
michael@0 221 mFlushImmediately = true;
michael@0 222 } else if (mPendingTasks.IsScopeClearPending(aOperation->Scope())) {
michael@0 223 // The scope is scheduled to be cleared, so just quickly load as empty.
michael@0 224 // We need to do this to prevent load of the DB data before the scope has
michael@0 225 // actually been cleared from the database. Preloads are processed
michael@0 226 // immediately before update and clear operations on the database that
michael@0 227 // are flushed periodically in batches.
michael@0 228 MonitorAutoUnlock unlock(mMonitor);
michael@0 229 aOperation->Finalize(NS_OK);
michael@0 230 return NS_OK;
michael@0 231 }
michael@0 232 // NO BREAK
michael@0 233
michael@0 234 case DBOperation::opGetUsage:
michael@0 235 if (aOperation->Type() == DBOperation::opPreloadUrgent) {
michael@0 236 SetHigherPriority(); // Dropped back after urgent preload execution
michael@0 237 mPreloads.InsertElementAt(0, aOperation);
michael@0 238 } else {
michael@0 239 mPreloads.AppendElement(aOperation);
michael@0 240 }
michael@0 241
michael@0 242 // DB operation adopted, don't delete it.
michael@0 243 opScope.forget();
michael@0 244
michael@0 245 // Immediately start executing this.
michael@0 246 monitor.Notify();
michael@0 247 break;
michael@0 248
michael@0 249 default:
michael@0 250 // Update operations are first collected, coalesced and then flushed
michael@0 251 // after a short time.
michael@0 252 mPendingTasks.Add(aOperation);
michael@0 253
michael@0 254 // DB operation adopted, don't delete it.
michael@0 255 opScope.forget();
michael@0 256
michael@0 257 ScheduleFlush();
michael@0 258 break;
michael@0 259 }
michael@0 260
michael@0 261 return NS_OK;
michael@0 262 }
michael@0 263
michael@0 264 void
michael@0 265 DOMStorageDBThread::SetHigherPriority()
michael@0 266 {
michael@0 267 ++mPriorityCounter;
michael@0 268 PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
michael@0 269 }
michael@0 270
michael@0 271 void
michael@0 272 DOMStorageDBThread::SetDefaultPriority()
michael@0 273 {
michael@0 274 if (--mPriorityCounter <= 0) {
michael@0 275 PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
michael@0 276 }
michael@0 277 }
michael@0 278
michael@0 279 void
michael@0 280 DOMStorageDBThread::ThreadFunc(void* aArg)
michael@0 281 {
michael@0 282 PR_SetCurrentThreadName("localStorage DB");
michael@0 283 mozilla::IOInterposer::RegisterCurrentThread();
michael@0 284
michael@0 285 DOMStorageDBThread* thread = static_cast<DOMStorageDBThread*>(aArg);
michael@0 286 thread->ThreadFunc();
michael@0 287 mozilla::IOInterposer::UnregisterCurrentThread();
michael@0 288 }
michael@0 289
michael@0 290 void
michael@0 291 DOMStorageDBThread::ThreadFunc()
michael@0 292 {
michael@0 293 nsresult rv = InitDatabase();
michael@0 294
michael@0 295 MonitorAutoLock lockMonitor(mMonitor);
michael@0 296
michael@0 297 if (NS_FAILED(rv)) {
michael@0 298 mStatus = rv;
michael@0 299 mStopIOThread = true;
michael@0 300 return;
michael@0 301 }
michael@0 302
michael@0 303 while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() || mPendingTasks.HasTasks())) {
michael@0 304 if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
michael@0 305 // Flush time is up or flush has been forced, do it now.
michael@0 306 UnscheduleFlush();
michael@0 307 if (mPendingTasks.Prepare()) {
michael@0 308 {
michael@0 309 MonitorAutoUnlock unlockMonitor(mMonitor);
michael@0 310 rv = mPendingTasks.Execute(this);
michael@0 311 }
michael@0 312
michael@0 313 if (!mPendingTasks.Finalize(rv)) {
michael@0 314 mStatus = rv;
michael@0 315 NS_WARNING("localStorage DB access broken");
michael@0 316 }
michael@0 317 }
michael@0 318 NotifyFlushCompletion();
michael@0 319 } else if (MOZ_LIKELY(mPreloads.Length())) {
michael@0 320 nsAutoPtr<DBOperation> op(mPreloads[0]);
michael@0 321 mPreloads.RemoveElementAt(0);
michael@0 322 {
michael@0 323 MonitorAutoUnlock unlockMonitor(mMonitor);
michael@0 324 op->PerformAndFinalize(this);
michael@0 325 }
michael@0 326
michael@0 327 if (op->Type() == DBOperation::opPreloadUrgent) {
michael@0 328 SetDefaultPriority(); // urgent preload unscheduled
michael@0 329 }
michael@0 330 } else if (MOZ_UNLIKELY(!mStopIOThread)) {
michael@0 331 lockMonitor.Wait(TimeUntilFlush());
michael@0 332 }
michael@0 333 } // thread loop
michael@0 334
michael@0 335 mStatus = ShutdownDatabase();
michael@0 336 }
michael@0 337
michael@0 338 extern void
michael@0 339 ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
michael@0 340
michael@0 341 namespace { // anon
michael@0 342
michael@0 343 class nsReverseStringSQLFunction MOZ_FINAL : public mozIStorageFunction
michael@0 344 {
michael@0 345 NS_DECL_ISUPPORTS
michael@0 346 NS_DECL_MOZISTORAGEFUNCTION
michael@0 347 };
michael@0 348
michael@0 349 NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
michael@0 350
michael@0 351 NS_IMETHODIMP
michael@0 352 nsReverseStringSQLFunction::OnFunctionCall(
michael@0 353 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
michael@0 354 {
michael@0 355 nsresult rv;
michael@0 356
michael@0 357 nsAutoCString stringToReverse;
michael@0 358 rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
michael@0 359 NS_ENSURE_SUCCESS(rv, rv);
michael@0 360
michael@0 361 nsAutoCString result;
michael@0 362 ReverseString(stringToReverse, result);
michael@0 363
michael@0 364 nsCOMPtr<nsIWritableVariant> outVar(do_CreateInstance(
michael@0 365 NS_VARIANT_CONTRACTID, &rv));
michael@0 366 NS_ENSURE_SUCCESS(rv, rv);
michael@0 367
michael@0 368 rv = outVar->SetAsAUTF8String(result);
michael@0 369 NS_ENSURE_SUCCESS(rv, rv);
michael@0 370
michael@0 371 *aResult = outVar.get();
michael@0 372 outVar.forget();
michael@0 373 return NS_OK;
michael@0 374 }
michael@0 375
michael@0 376 } // anon
michael@0 377
michael@0 378 nsresult
michael@0 379 DOMStorageDBThread::OpenDatabaseConnection()
michael@0 380 {
michael@0 381 nsresult rv;
michael@0 382
michael@0 383 MOZ_ASSERT(!NS_IsMainThread());
michael@0 384
michael@0 385 nsCOMPtr<mozIStorageService> service
michael@0 386 = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
michael@0 387 NS_ENSURE_SUCCESS(rv, rv);
michael@0 388
michael@0 389 nsCOMPtr<mozIStorageConnection> connection;
michael@0 390 rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
michael@0 391 if (rv == NS_ERROR_FILE_CORRUPTED) {
michael@0 392 // delete the db and try opening again
michael@0 393 rv = mDatabaseFile->Remove(false);
michael@0 394 NS_ENSURE_SUCCESS(rv, rv);
michael@0 395 rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
michael@0 396 }
michael@0 397 NS_ENSURE_SUCCESS(rv, rv);
michael@0 398
michael@0 399 return NS_OK;
michael@0 400 }
michael@0 401
michael@0 402 nsresult
michael@0 403 DOMStorageDBThread::InitDatabase()
michael@0 404 {
michael@0 405 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_INIT_DATABASE_MS> timer;
michael@0 406
michael@0 407 nsresult rv;
michael@0 408
michael@0 409 // Here we are on the worker thread. This opens the worker connection.
michael@0 410 MOZ_ASSERT(!NS_IsMainThread());
michael@0 411
michael@0 412 rv = OpenDatabaseConnection();
michael@0 413 NS_ENSURE_SUCCESS(rv, rv);
michael@0 414
michael@0 415 rv = TryJournalMode();
michael@0 416 NS_ENSURE_SUCCESS(rv, rv);
michael@0 417
michael@0 418 // Create a read-only clone
michael@0 419 (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
michael@0 420 NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
michael@0 421
michael@0 422 mozStorageTransaction transaction(mWorkerConnection, false);
michael@0 423
michael@0 424 // Ensure Gecko 1.9.1 storage table
michael@0 425 rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
michael@0 426 "CREATE TABLE IF NOT EXISTS webappsstore2 ("
michael@0 427 "scope TEXT, "
michael@0 428 "key TEXT, "
michael@0 429 "value TEXT, "
michael@0 430 "secure INTEGER, "
michael@0 431 "owner TEXT)"));
michael@0 432 NS_ENSURE_SUCCESS(rv, rv);
michael@0 433
michael@0 434 rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
michael@0 435 "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
michael@0 436 " ON webappsstore2(scope, key)"));
michael@0 437 NS_ENSURE_SUCCESS(rv, rv);
michael@0 438
michael@0 439 nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
michael@0 440 NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
michael@0 441
michael@0 442 rv = mWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1);
michael@0 443 NS_ENSURE_SUCCESS(rv, rv);
michael@0 444
michael@0 445 bool exists;
michael@0 446
michael@0 447 // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
michael@0 448 // to actual webappsstore2 table and drop the obsolete table. First process
michael@0 449 // this newer table upgrade to priority potential duplicates from older
michael@0 450 // storage table.
michael@0 451 rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
michael@0 452 &exists);
michael@0 453 NS_ENSURE_SUCCESS(rv, rv);
michael@0 454
michael@0 455 if (exists) {
michael@0 456 rv = mWorkerConnection->ExecuteSimpleSQL(
michael@0 457 NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
michael@0 458 "webappsstore2(scope, key, value, secure, owner) "
michael@0 459 "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
michael@0 460 "FROM webappsstore"));
michael@0 461 NS_ENSURE_SUCCESS(rv, rv);
michael@0 462
michael@0 463 rv = mWorkerConnection->ExecuteSimpleSQL(
michael@0 464 NS_LITERAL_CSTRING("DROP TABLE webappsstore"));
michael@0 465 NS_ENSURE_SUCCESS(rv, rv);
michael@0 466 }
michael@0 467
michael@0 468 // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
michael@0 469 // to actual webappsstore2 table and drop the obsolete table. Potential
michael@0 470 // duplicates will be ignored.
michael@0 471 rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
michael@0 472 &exists);
michael@0 473 NS_ENSURE_SUCCESS(rv, rv);
michael@0 474
michael@0 475 if (exists) {
michael@0 476 rv = mWorkerConnection->ExecuteSimpleSQL(
michael@0 477 NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
michael@0 478 "webappsstore2(scope, key, value, secure, owner) "
michael@0 479 "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
michael@0 480 "FROM moz_webappsstore"));
michael@0 481 NS_ENSURE_SUCCESS(rv, rv);
michael@0 482
michael@0 483 rv = mWorkerConnection->ExecuteSimpleSQL(
michael@0 484 NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
michael@0 485 NS_ENSURE_SUCCESS(rv, rv);
michael@0 486 }
michael@0 487
michael@0 488 rv = transaction.Commit();
michael@0 489 NS_ENSURE_SUCCESS(rv, rv);
michael@0 490
michael@0 491 // Database open and all initiation operation are done. Switching this flag
michael@0 492 // to true allow main thread to read directly from the database.
michael@0 493 // If we would allow this sooner, we would have opened a window where main thread
michael@0 494 // read might operate on a totaly broken and incosistent database.
michael@0 495 mDBReady = true;
michael@0 496
michael@0 497 // List scopes having any stored data
michael@0 498 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 499 rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT DISTINCT scope FROM webappsstore2"),
michael@0 500 getter_AddRefs(stmt));
michael@0 501 NS_ENSURE_SUCCESS(rv, rv);
michael@0 502 mozStorageStatementScoper scope(stmt);
michael@0 503
michael@0 504 while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
michael@0 505 nsAutoCString foundScope;
michael@0 506 rv = stmt->GetUTF8String(0, foundScope);
michael@0 507 NS_ENSURE_SUCCESS(rv, rv);
michael@0 508
michael@0 509 MonitorAutoLock monitor(mMonitor);
michael@0 510 mScopesHavingData.PutEntry(foundScope);
michael@0 511 }
michael@0 512
michael@0 513 return NS_OK;
michael@0 514 }
michael@0 515
michael@0 516 nsresult
michael@0 517 DOMStorageDBThread::SetJournalMode(bool aIsWal)
michael@0 518 {
michael@0 519 nsresult rv;
michael@0 520
michael@0 521 nsAutoCString stmtString(
michael@0 522 MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
michael@0 523 if (aIsWal) {
michael@0 524 stmtString.AppendLiteral("wal");
michael@0 525 } else {
michael@0 526 stmtString.AppendLiteral("truncate");
michael@0 527 }
michael@0 528
michael@0 529 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 530 rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
michael@0 531 NS_ENSURE_SUCCESS(rv, rv);
michael@0 532 mozStorageStatementScoper scope(stmt);
michael@0 533
michael@0 534 bool hasResult = false;
michael@0 535 rv = stmt->ExecuteStep(&hasResult);
michael@0 536 NS_ENSURE_SUCCESS(rv, rv);
michael@0 537 if (!hasResult) {
michael@0 538 return NS_ERROR_FAILURE;
michael@0 539 }
michael@0 540
michael@0 541 nsAutoCString journalMode;
michael@0 542 rv = stmt->GetUTF8String(0, journalMode);
michael@0 543 NS_ENSURE_SUCCESS(rv, rv);
michael@0 544 if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
michael@0 545 (!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
michael@0 546 return NS_ERROR_FAILURE;
michael@0 547 }
michael@0 548
michael@0 549 return NS_OK;
michael@0 550 }
michael@0 551
michael@0 552 nsresult
michael@0 553 DOMStorageDBThread::TryJournalMode()
michael@0 554 {
michael@0 555 nsresult rv;
michael@0 556
michael@0 557 rv = SetJournalMode(true);
michael@0 558 if (NS_FAILED(rv)) {
michael@0 559 mWALModeEnabled = false;
michael@0 560
michael@0 561 rv = SetJournalMode(false);
michael@0 562 NS_ENSURE_SUCCESS(rv, rv);
michael@0 563 } else {
michael@0 564 mWALModeEnabled = true;
michael@0 565
michael@0 566 rv = ConfigureWALBehavior();
michael@0 567 NS_ENSURE_SUCCESS(rv, rv);
michael@0 568 }
michael@0 569
michael@0 570 return NS_OK;
michael@0 571 }
michael@0 572
michael@0 573 nsresult
michael@0 574 DOMStorageDBThread::ConfigureWALBehavior()
michael@0 575 {
michael@0 576 // Get the DB's page size
michael@0 577 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 578 nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
michael@0 579 MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
michael@0 580 ), getter_AddRefs(stmt));
michael@0 581 NS_ENSURE_SUCCESS(rv, rv);
michael@0 582
michael@0 583 bool hasResult = false;
michael@0 584 rv = stmt->ExecuteStep(&hasResult);
michael@0 585 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
michael@0 586
michael@0 587 int32_t pageSize = 0;
michael@0 588 rv = stmt->GetInt32(0, &pageSize);
michael@0 589 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
michael@0 590
michael@0 591 // Set the threshold for auto-checkpointing the WAL.
michael@0 592 // We don't want giant logs slowing down reads & shutdown.
michael@0 593 int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
michael@0 594 nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
michael@0 595 thresholdPragma.AppendInt(thresholdInPages);
michael@0 596 rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
michael@0 597 NS_ENSURE_SUCCESS(rv, rv);
michael@0 598
michael@0 599 // Set the maximum WAL log size to reduce footprint on mobile (large empty
michael@0 600 // WAL files will be truncated)
michael@0 601 nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
michael@0 602 // bug 600307: mak recommends setting this to 3 times the auto-checkpoint threshold
michael@0 603 journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3);
michael@0 604 rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma);
michael@0 605 NS_ENSURE_SUCCESS(rv, rv);
michael@0 606
michael@0 607 return NS_OK;
michael@0 608 }
michael@0 609
michael@0 610 nsresult
michael@0 611 DOMStorageDBThread::ShutdownDatabase()
michael@0 612 {
michael@0 613 // Has to be called on the worker thread.
michael@0 614 MOZ_ASSERT(!NS_IsMainThread());
michael@0 615
michael@0 616 nsresult rv = mStatus;
michael@0 617
michael@0 618 mDBReady = false;
michael@0 619
michael@0 620 // Finalize the cached statements.
michael@0 621 mReaderStatements.FinalizeStatements();
michael@0 622 mWorkerStatements.FinalizeStatements();
michael@0 623
michael@0 624 if (mReaderConnection) {
michael@0 625 // No need to sync access to mReaderConnection since the main thread
michael@0 626 // is right now joining this thread, unable to execute any events.
michael@0 627 mReaderConnection->Close();
michael@0 628 mReaderConnection = nullptr;
michael@0 629 }
michael@0 630
michael@0 631 if (mWorkerConnection) {
michael@0 632 rv = mWorkerConnection->Close();
michael@0 633 mWorkerConnection = nullptr;
michael@0 634 }
michael@0 635
michael@0 636 return rv;
michael@0 637 }
michael@0 638
michael@0 639 void
michael@0 640 DOMStorageDBThread::ScheduleFlush()
michael@0 641 {
michael@0 642 if (mDirtyEpoch) {
michael@0 643 return; // Already scheduled
michael@0 644 }
michael@0 645
michael@0 646 mDirtyEpoch = PR_IntervalNow() | 1; // Must be non-zero to indicate we are scheduled
michael@0 647
michael@0 648 // Wake the monitor from indefinite sleep...
michael@0 649 mMonitor.Notify();
michael@0 650 }
michael@0 651
michael@0 652 void
michael@0 653 DOMStorageDBThread::UnscheduleFlush()
michael@0 654 {
michael@0 655 // We are just about to do the flush, drop flags
michael@0 656 mFlushImmediately = false;
michael@0 657 mDirtyEpoch = 0;
michael@0 658 }
michael@0 659
michael@0 660 PRIntervalTime
michael@0 661 DOMStorageDBThread::TimeUntilFlush()
michael@0 662 {
michael@0 663 if (mFlushImmediately) {
michael@0 664 return 0; // Do it now regardless the timeout.
michael@0 665 }
michael@0 666
michael@0 667 static_assert(PR_INTERVAL_NO_TIMEOUT != 0,
michael@0 668 "PR_INTERVAL_NO_TIMEOUT must be non-zero");
michael@0 669
michael@0 670 if (!mDirtyEpoch) {
michael@0 671 return PR_INTERVAL_NO_TIMEOUT; // No pending task...
michael@0 672 }
michael@0 673
michael@0 674 static const PRIntervalTime kMaxAge = PR_MillisecondsToInterval(FLUSHING_INTERVAL_MS);
michael@0 675
michael@0 676 PRIntervalTime now = PR_IntervalNow() | 1;
michael@0 677 PRIntervalTime age = now - mDirtyEpoch;
michael@0 678 if (age > kMaxAge) {
michael@0 679 return 0; // It is time.
michael@0 680 }
michael@0 681
michael@0 682 return kMaxAge - age; // Time left, this is used to sleep the monitor
michael@0 683 }
michael@0 684
michael@0 685 void
michael@0 686 DOMStorageDBThread::NotifyFlushCompletion()
michael@0 687 {
michael@0 688 #ifdef DOM_STORAGE_TESTS
michael@0 689 if (!NS_IsMainThread()) {
michael@0 690 nsRefPtr<nsRunnableMethod<DOMStorageDBThread, void, false> > event =
michael@0 691 NS_NewNonOwningRunnableMethod(this, &DOMStorageDBThread::NotifyFlushCompletion);
michael@0 692 NS_DispatchToMainThread(event);
michael@0 693 return;
michael@0 694 }
michael@0 695
michael@0 696 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
michael@0 697 if (obs) {
michael@0 698 obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
michael@0 699 }
michael@0 700 #endif
michael@0 701 }
michael@0 702
michael@0 703 // DOMStorageDBThread::DBOperation
michael@0 704
michael@0 705 DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
michael@0 706 DOMStorageCacheBridge* aCache,
michael@0 707 const nsAString& aKey,
michael@0 708 const nsAString& aValue)
michael@0 709 : mType(aType)
michael@0 710 , mCache(aCache)
michael@0 711 , mKey(aKey)
michael@0 712 , mValue(aValue)
michael@0 713 {
michael@0 714 MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
michael@0 715 }
michael@0 716
michael@0 717 DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
michael@0 718 DOMStorageUsageBridge* aUsage)
michael@0 719 : mType(aType)
michael@0 720 , mUsage(aUsage)
michael@0 721 {
michael@0 722 MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
michael@0 723 }
michael@0 724
michael@0 725 DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
michael@0 726 const nsACString& aScope)
michael@0 727 : mType(aType)
michael@0 728 , mCache(nullptr)
michael@0 729 , mScope(aScope)
michael@0 730 {
michael@0 731 MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
michael@0 732 }
michael@0 733
michael@0 734 DOMStorageDBThread::DBOperation::~DBOperation()
michael@0 735 {
michael@0 736 MOZ_COUNT_DTOR(DOMStorageDBThread::DBOperation);
michael@0 737 }
michael@0 738
michael@0 739 const nsCString
michael@0 740 DOMStorageDBThread::DBOperation::Scope()
michael@0 741 {
michael@0 742 if (mCache) {
michael@0 743 return mCache->Scope();
michael@0 744 }
michael@0 745
michael@0 746 return mScope;
michael@0 747 }
michael@0 748
michael@0 749 const nsCString
michael@0 750 DOMStorageDBThread::DBOperation::Target()
michael@0 751 {
michael@0 752 switch (mType) {
michael@0 753 case opAddItem:
michael@0 754 case opUpdateItem:
michael@0 755 case opRemoveItem:
michael@0 756 return Scope() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
michael@0 757
michael@0 758 default:
michael@0 759 return Scope();
michael@0 760 }
michael@0 761 }
michael@0 762
michael@0 763 void
michael@0 764 DOMStorageDBThread::DBOperation::PerformAndFinalize(DOMStorageDBThread* aThread)
michael@0 765 {
michael@0 766 Finalize(Perform(aThread));
michael@0 767 }
michael@0 768
michael@0 769 nsresult
michael@0 770 DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
michael@0 771 {
michael@0 772 nsresult rv;
michael@0 773
michael@0 774 switch (mType) {
michael@0 775 case opPreload:
michael@0 776 case opPreloadUrgent:
michael@0 777 {
michael@0 778 // Already loaded?
michael@0 779 if (mCache->Loaded()) {
michael@0 780 break;
michael@0 781 }
michael@0 782
michael@0 783 StatementCache* statements;
michael@0 784 if (MOZ_UNLIKELY(NS_IsMainThread())) {
michael@0 785 statements = &aThread->mReaderStatements;
michael@0 786 } else {
michael@0 787 statements = &aThread->mWorkerStatements;
michael@0 788 }
michael@0 789
michael@0 790 // OFFSET is an optimization when we have to do a sync load
michael@0 791 // and cache has already loaded some parts asynchronously.
michael@0 792 // It skips keys we have already loaded.
michael@0 793 nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
michael@0 794 "SELECT key, value FROM webappsstore2 "
michael@0 795 "WHERE scope = :scope ORDER BY key "
michael@0 796 "LIMIT -1 OFFSET :offset");
michael@0 797 NS_ENSURE_STATE(stmt);
michael@0 798 mozStorageStatementScoper scope(stmt);
michael@0 799
michael@0 800 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
michael@0 801 mCache->Scope());
michael@0 802 NS_ENSURE_SUCCESS(rv, rv);
michael@0 803
michael@0 804 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
michael@0 805 static_cast<int32_t>(mCache->LoadedCount()));
michael@0 806 NS_ENSURE_SUCCESS(rv, rv);
michael@0 807
michael@0 808 bool exists;
michael@0 809 while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
michael@0 810 nsAutoString key;
michael@0 811 rv = stmt->GetString(0, key);
michael@0 812 NS_ENSURE_SUCCESS(rv, rv);
michael@0 813
michael@0 814 nsAutoString value;
michael@0 815 rv = stmt->GetString(1, value);
michael@0 816 NS_ENSURE_SUCCESS(rv, rv);
michael@0 817
michael@0 818 if (!mCache->LoadItem(key, value)) {
michael@0 819 break;
michael@0 820 }
michael@0 821 }
michael@0 822
michael@0 823 mCache->LoadDone(NS_OK);
michael@0 824 break;
michael@0 825 }
michael@0 826
michael@0 827 case opGetUsage:
michael@0 828 {
michael@0 829 nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
michael@0 830 "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2"
michael@0 831 " WHERE scope LIKE :scope"
michael@0 832 );
michael@0 833 NS_ENSURE_STATE(stmt);
michael@0 834
michael@0 835 mozStorageStatementScoper scope(stmt);
michael@0 836
michael@0 837 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
michael@0 838 mUsage->Scope() + NS_LITERAL_CSTRING("%"));
michael@0 839 NS_ENSURE_SUCCESS(rv, rv);
michael@0 840
michael@0 841 bool exists;
michael@0 842 rv = stmt->ExecuteStep(&exists);
michael@0 843 NS_ENSURE_SUCCESS(rv, rv);
michael@0 844
michael@0 845 int64_t usage = 0;
michael@0 846 if (exists) {
michael@0 847 rv = stmt->GetInt64(0, &usage);
michael@0 848 NS_ENSURE_SUCCESS(rv, rv);
michael@0 849 }
michael@0 850
michael@0 851 mUsage->LoadUsage(usage);
michael@0 852 break;
michael@0 853 }
michael@0 854
michael@0 855 case opAddItem:
michael@0 856 case opUpdateItem:
michael@0 857 {
michael@0 858 MOZ_ASSERT(!NS_IsMainThread());
michael@0 859
michael@0 860 nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
michael@0 861 "INSERT OR REPLACE INTO webappsstore2 (scope, key, value) "
michael@0 862 "VALUES (:scope, :key, :value) "
michael@0 863 );
michael@0 864 NS_ENSURE_STATE(stmt);
michael@0 865
michael@0 866 mozStorageStatementScoper scope(stmt);
michael@0 867
michael@0 868 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
michael@0 869 mCache->Scope());
michael@0 870 NS_ENSURE_SUCCESS(rv, rv);
michael@0 871 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
michael@0 872 mKey);
michael@0 873 NS_ENSURE_SUCCESS(rv, rv);
michael@0 874 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"),
michael@0 875 mValue);
michael@0 876 NS_ENSURE_SUCCESS(rv, rv);
michael@0 877
michael@0 878 rv = stmt->Execute();
michael@0 879 NS_ENSURE_SUCCESS(rv, rv);
michael@0 880
michael@0 881 aThread->mScopesHavingData.PutEntry(Scope());
michael@0 882 break;
michael@0 883 }
michael@0 884
michael@0 885 case opRemoveItem:
michael@0 886 {
michael@0 887 MOZ_ASSERT(!NS_IsMainThread());
michael@0 888
michael@0 889 nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
michael@0 890 "DELETE FROM webappsstore2 "
michael@0 891 "WHERE scope = :scope "
michael@0 892 "AND key = :key "
michael@0 893 );
michael@0 894 NS_ENSURE_STATE(stmt);
michael@0 895 mozStorageStatementScoper scope(stmt);
michael@0 896
michael@0 897 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
michael@0 898 mCache->Scope());
michael@0 899 NS_ENSURE_SUCCESS(rv, rv);
michael@0 900 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
michael@0 901 mKey);
michael@0 902 NS_ENSURE_SUCCESS(rv, rv);
michael@0 903
michael@0 904 rv = stmt->Execute();
michael@0 905 NS_ENSURE_SUCCESS(rv, rv);
michael@0 906
michael@0 907 break;
michael@0 908 }
michael@0 909
michael@0 910 case opClear:
michael@0 911 {
michael@0 912 MOZ_ASSERT(!NS_IsMainThread());
michael@0 913
michael@0 914 nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
michael@0 915 "DELETE FROM webappsstore2 "
michael@0 916 "WHERE scope = :scope"
michael@0 917 );
michael@0 918 NS_ENSURE_STATE(stmt);
michael@0 919 mozStorageStatementScoper scope(stmt);
michael@0 920
michael@0 921 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
michael@0 922 mCache->Scope());
michael@0 923 NS_ENSURE_SUCCESS(rv, rv);
michael@0 924
michael@0 925 rv = stmt->Execute();
michael@0 926 NS_ENSURE_SUCCESS(rv, rv);
michael@0 927
michael@0 928 aThread->mScopesHavingData.RemoveEntry(Scope());
michael@0 929 break;
michael@0 930 }
michael@0 931
michael@0 932 case opClearAll:
michael@0 933 {
michael@0 934 MOZ_ASSERT(!NS_IsMainThread());
michael@0 935
michael@0 936 nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
michael@0 937 "DELETE FROM webappsstore2"
michael@0 938 );
michael@0 939 NS_ENSURE_STATE(stmt);
michael@0 940 mozStorageStatementScoper scope(stmt);
michael@0 941
michael@0 942 rv = stmt->Execute();
michael@0 943 NS_ENSURE_SUCCESS(rv, rv);
michael@0 944
michael@0 945 aThread->mScopesHavingData.Clear();
michael@0 946 break;
michael@0 947 }
michael@0 948
michael@0 949 case opClearMatchingScope:
michael@0 950 {
michael@0 951 MOZ_ASSERT(!NS_IsMainThread());
michael@0 952
michael@0 953 nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
michael@0 954 "DELETE FROM webappsstore2"
michael@0 955 " WHERE scope GLOB :scope"
michael@0 956 );
michael@0 957 NS_ENSURE_STATE(stmt);
michael@0 958 mozStorageStatementScoper scope(stmt);
michael@0 959
michael@0 960 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
michael@0 961 mScope + NS_LITERAL_CSTRING("*"));
michael@0 962 NS_ENSURE_SUCCESS(rv, rv);
michael@0 963
michael@0 964 rv = stmt->Execute();
michael@0 965 NS_ENSURE_SUCCESS(rv, rv);
michael@0 966
michael@0 967 break;
michael@0 968 }
michael@0 969
michael@0 970 default:
michael@0 971 NS_ERROR("Unknown task type");
michael@0 972 break;
michael@0 973 }
michael@0 974
michael@0 975 return NS_OK;
michael@0 976 }
michael@0 977
michael@0 978 void
michael@0 979 DOMStorageDBThread::DBOperation::Finalize(nsresult aRv)
michael@0 980 {
michael@0 981 switch (mType) {
michael@0 982 case opPreloadUrgent:
michael@0 983 case opPreload:
michael@0 984 if (NS_FAILED(aRv)) {
michael@0 985 // When we are here, something failed when loading from the database.
michael@0 986 // Notify that the storage is loaded to prevent deadlock of the main thread,
michael@0 987 // even though it is actually empty or incomplete.
michael@0 988 NS_WARNING("Failed to preload localStorage");
michael@0 989 }
michael@0 990
michael@0 991 mCache->LoadDone(aRv);
michael@0 992 break;
michael@0 993
michael@0 994 case opGetUsage:
michael@0 995 if (NS_FAILED(aRv)) {
michael@0 996 mUsage->LoadUsage(0);
michael@0 997 }
michael@0 998
michael@0 999 break;
michael@0 1000
michael@0 1001 default:
michael@0 1002 if (NS_FAILED(aRv)) {
michael@0 1003 NS_WARNING("localStorage update/clear operation failed,"
michael@0 1004 " data may not persist or clean up");
michael@0 1005 }
michael@0 1006
michael@0 1007 break;
michael@0 1008 }
michael@0 1009 }
michael@0 1010
michael@0 1011 // DOMStorageDBThread::PendingOperations
michael@0 1012
michael@0 1013 DOMStorageDBThread::PendingOperations::PendingOperations()
michael@0 1014 : mFlushFailureCount(0)
michael@0 1015 {
michael@0 1016 }
michael@0 1017
michael@0 1018 bool
michael@0 1019 DOMStorageDBThread::PendingOperations::HasTasks()
michael@0 1020 {
michael@0 1021 return !!mUpdates.Count() || !!mClears.Count();
michael@0 1022 }
michael@0 1023
michael@0 1024 namespace { // anon
michael@0 1025
michael@0 1026 PLDHashOperator
michael@0 1027 ForgetUpdatesForScope(const nsACString& aMapping,
michael@0 1028 nsAutoPtr<DOMStorageDBThread::DBOperation>& aPendingTask,
michael@0 1029 void* aArg)
michael@0 1030 {
michael@0 1031 DOMStorageDBThread::DBOperation* newOp = static_cast<DOMStorageDBThread::DBOperation*>(aArg);
michael@0 1032
michael@0 1033 if (newOp->Type() == DOMStorageDBThread::DBOperation::opClear &&
michael@0 1034 aPendingTask->Scope() != newOp->Scope()) {
michael@0 1035 return PL_DHASH_NEXT;
michael@0 1036 }
michael@0 1037
michael@0 1038 if (newOp->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
michael@0 1039 !StringBeginsWith(aPendingTask->Scope(), newOp->Scope())) {
michael@0 1040 return PL_DHASH_NEXT;
michael@0 1041 }
michael@0 1042
michael@0 1043 return PL_DHASH_REMOVE;
michael@0 1044 }
michael@0 1045
michael@0 1046 } // anon
michael@0 1047
michael@0 1048 bool
michael@0 1049 DOMStorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp,
michael@0 1050 DBOperation::OperationType aPendingType,
michael@0 1051 DBOperation::OperationType aNewType)
michael@0 1052 {
michael@0 1053 if (aNewOp->Type() != aNewType) {
michael@0 1054 return false;
michael@0 1055 }
michael@0 1056
michael@0 1057 DOMStorageDBThread::DBOperation* pendingTask;
michael@0 1058 if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
michael@0 1059 return false;
michael@0 1060 }
michael@0 1061
michael@0 1062 if (pendingTask->Type() != aPendingType) {
michael@0 1063 return false;
michael@0 1064 }
michael@0 1065
michael@0 1066 return true;
michael@0 1067 }
michael@0 1068
michael@0 1069 void
michael@0 1070 DOMStorageDBThread::PendingOperations::Add(DOMStorageDBThread::DBOperation* aOperation)
michael@0 1071 {
michael@0 1072 // Optimize: when a key to remove has never been written to disk
michael@0 1073 // just bypass this operation. A kew is new when an operation scheduled
michael@0 1074 // to write it to the database is of type opAddItem.
michael@0 1075 if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opRemoveItem)) {
michael@0 1076 mUpdates.Remove(aOperation->Target());
michael@0 1077 delete aOperation;
michael@0 1078 return;
michael@0 1079 }
michael@0 1080
michael@0 1081 // Optimize: when changing a key that is new and has never been
michael@0 1082 // written to disk, keep type of the operation to store it at opAddItem.
michael@0 1083 // This allows optimization to just forget adding a new key when
michael@0 1084 // it is removed from the storage before flush.
michael@0 1085 if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opUpdateItem)) {
michael@0 1086 aOperation->mType = DBOperation::opAddItem;
michael@0 1087 }
michael@0 1088
michael@0 1089 // Optimize: to prevent lose of remove operation on a key when doing
michael@0 1090 // remove/set/remove on a previously existing key we have to change
michael@0 1091 // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
michael@0 1092 // pending for the key.
michael@0 1093 if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem, DBOperation::opAddItem)) {
michael@0 1094 aOperation->mType = DBOperation::opUpdateItem;
michael@0 1095 }
michael@0 1096
michael@0 1097 switch (aOperation->Type())
michael@0 1098 {
michael@0 1099 // Operations on single keys
michael@0 1100
michael@0 1101 case DBOperation::opAddItem:
michael@0 1102 case DBOperation::opUpdateItem:
michael@0 1103 case DBOperation::opRemoveItem:
michael@0 1104 // Override any existing operation for the target (=scope+key).
michael@0 1105 mUpdates.Put(aOperation->Target(), aOperation);
michael@0 1106 break;
michael@0 1107
michael@0 1108 // Clear operations
michael@0 1109
michael@0 1110 case DBOperation::opClear:
michael@0 1111 case DBOperation::opClearMatchingScope:
michael@0 1112 // Drop all update (insert/remove) operations for equivavelent or matching scope.
michael@0 1113 // We do this as an optimization as well as a must based on the logic,
michael@0 1114 // if we would not delete the update tasks, changes would have been stored
michael@0 1115 // to the database after clear operations have been executed.
michael@0 1116 mUpdates.Enumerate(ForgetUpdatesForScope, aOperation);
michael@0 1117 mClears.Put(aOperation->Target(), aOperation);
michael@0 1118 break;
michael@0 1119
michael@0 1120 case DBOperation::opClearAll:
michael@0 1121 // Drop simply everything, this is a super-operation.
michael@0 1122 mUpdates.Clear();
michael@0 1123 mClears.Clear();
michael@0 1124 mClears.Put(aOperation->Target(), aOperation);
michael@0 1125 break;
michael@0 1126
michael@0 1127 default:
michael@0 1128 MOZ_ASSERT(false);
michael@0 1129 break;
michael@0 1130 }
michael@0 1131 }
michael@0 1132
michael@0 1133 namespace { // anon
michael@0 1134
michael@0 1135 PLDHashOperator
michael@0 1136 CollectTasks(const nsACString& aMapping, nsAutoPtr<DOMStorageDBThread::DBOperation>& aOperation, void* aArg)
michael@0 1137 {
michael@0 1138 nsTArray<nsAutoPtr<DOMStorageDBThread::DBOperation> >* tasks =
michael@0 1139 static_cast<nsTArray<nsAutoPtr<DOMStorageDBThread::DBOperation> >*>(aArg);
michael@0 1140
michael@0 1141 tasks->AppendElement(aOperation.forget());
michael@0 1142 return PL_DHASH_NEXT;
michael@0 1143 }
michael@0 1144
michael@0 1145 } // anon
michael@0 1146
michael@0 1147 bool
michael@0 1148 DOMStorageDBThread::PendingOperations::Prepare()
michael@0 1149 {
michael@0 1150 // Called under the lock
michael@0 1151
michael@0 1152 // First collect clear operations and then updates, we can
michael@0 1153 // do this since whenever a clear operation for a scope is
michael@0 1154 // scheduled, we drop all updates matching that scope. So,
michael@0 1155 // all scope-related update operations we have here now were
michael@0 1156 // scheduled after the clear operations.
michael@0 1157 mClears.Enumerate(CollectTasks, &mExecList);
michael@0 1158 mClears.Clear();
michael@0 1159
michael@0 1160 mUpdates.Enumerate(CollectTasks, &mExecList);
michael@0 1161 mUpdates.Clear();
michael@0 1162
michael@0 1163 return !!mExecList.Length();
michael@0 1164 }
michael@0 1165
michael@0 1166 nsresult
michael@0 1167 DOMStorageDBThread::PendingOperations::Execute(DOMStorageDBThread* aThread)
michael@0 1168 {
michael@0 1169 // Called outside the lock
michael@0 1170
michael@0 1171 mozStorageTransaction transaction(aThread->mWorkerConnection, false);
michael@0 1172
michael@0 1173 nsresult rv;
michael@0 1174
michael@0 1175 for (uint32_t i = 0; i < mExecList.Length(); ++i) {
michael@0 1176 DOMStorageDBThread::DBOperation* task = mExecList[i];
michael@0 1177 rv = task->Perform(aThread);
michael@0 1178 if (NS_FAILED(rv)) {
michael@0 1179 return rv;
michael@0 1180 }
michael@0 1181 }
michael@0 1182
michael@0 1183 rv = transaction.Commit();
michael@0 1184 if (NS_FAILED(rv)) {
michael@0 1185 return rv;
michael@0 1186 }
michael@0 1187
michael@0 1188 return NS_OK;
michael@0 1189 }
michael@0 1190
michael@0 1191 bool
michael@0 1192 DOMStorageDBThread::PendingOperations::Finalize(nsresult aRv)
michael@0 1193 {
michael@0 1194 // Called under the lock
michael@0 1195
michael@0 1196 // The list is kept on a failure to retry it
michael@0 1197 if (NS_FAILED(aRv)) {
michael@0 1198 // XXX Followup: we may try to reopen the database and flush these
michael@0 1199 // pending tasks, however testing showed that even though I/O is actually
michael@0 1200 // broken some amount of operations is left in sqlite+system buffers and
michael@0 1201 // seems like successfully flushed to disk.
michael@0 1202 // Tested by removing a flash card and disconnecting from network while
michael@0 1203 // using a network drive on Windows system.
michael@0 1204 NS_WARNING("Flush operation on localStorage database failed");
michael@0 1205
michael@0 1206 ++mFlushFailureCount;
michael@0 1207
michael@0 1208 return mFlushFailureCount >= 5;
michael@0 1209 }
michael@0 1210
michael@0 1211 mFlushFailureCount = 0;
michael@0 1212 mExecList.Clear();
michael@0 1213 return true;
michael@0 1214 }
michael@0 1215
michael@0 1216 namespace { // anon
michael@0 1217
michael@0 1218 class FindPendingOperationForScopeData
michael@0 1219 {
michael@0 1220 public:
michael@0 1221 FindPendingOperationForScopeData(const nsACString& aScope) : mScope(aScope), mFound(false) {}
michael@0 1222 nsCString mScope;
michael@0 1223 bool mFound;
michael@0 1224 };
michael@0 1225
michael@0 1226 PLDHashOperator
michael@0 1227 FindPendingClearForScope(const nsACString& aMapping,
michael@0 1228 DOMStorageDBThread::DBOperation* aPendingOperation,
michael@0 1229 void* aArg)
michael@0 1230 {
michael@0 1231 FindPendingOperationForScopeData* data =
michael@0 1232 static_cast<FindPendingOperationForScopeData*>(aArg);
michael@0 1233
michael@0 1234 if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearAll) {
michael@0 1235 data->mFound = true;
michael@0 1236 return PL_DHASH_STOP;
michael@0 1237 }
michael@0 1238
michael@0 1239 if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClear &&
michael@0 1240 data->mScope == aPendingOperation->Scope()) {
michael@0 1241 data->mFound = true;
michael@0 1242 return PL_DHASH_STOP;
michael@0 1243 }
michael@0 1244
michael@0 1245 if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
michael@0 1246 StringBeginsWith(data->mScope, aPendingOperation->Scope())) {
michael@0 1247 data->mFound = true;
michael@0 1248 return PL_DHASH_STOP;
michael@0 1249 }
michael@0 1250
michael@0 1251 return PL_DHASH_NEXT;
michael@0 1252 }
michael@0 1253
michael@0 1254 } // anon
michael@0 1255
michael@0 1256 bool
michael@0 1257 DOMStorageDBThread::PendingOperations::IsScopeClearPending(const nsACString& aScope)
michael@0 1258 {
michael@0 1259 // Called under the lock
michael@0 1260
michael@0 1261 FindPendingOperationForScopeData data(aScope);
michael@0 1262 mClears.EnumerateRead(FindPendingClearForScope, &data);
michael@0 1263 if (data.mFound) {
michael@0 1264 return true;
michael@0 1265 }
michael@0 1266
michael@0 1267 for (uint32_t i = 0; i < mExecList.Length(); ++i) {
michael@0 1268 DOMStorageDBThread::DBOperation* task = mExecList[i];
michael@0 1269 FindPendingClearForScope(EmptyCString(), task, &data);
michael@0 1270
michael@0 1271 if (data.mFound) {
michael@0 1272 return true;
michael@0 1273 }
michael@0 1274 }
michael@0 1275
michael@0 1276 return false;
michael@0 1277 }
michael@0 1278
michael@0 1279 namespace { // anon
michael@0 1280
michael@0 1281 PLDHashOperator
michael@0 1282 FindPendingUpdateForScope(const nsACString& aMapping,
michael@0 1283 DOMStorageDBThread::DBOperation* aPendingOperation,
michael@0 1284 void* aArg)
michael@0 1285 {
michael@0 1286 FindPendingOperationForScopeData* data =
michael@0 1287 static_cast<FindPendingOperationForScopeData*>(aArg);
michael@0 1288
michael@0 1289 if ((aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opAddItem ||
michael@0 1290 aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opUpdateItem ||
michael@0 1291 aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opRemoveItem) &&
michael@0 1292 data->mScope == aPendingOperation->Scope()) {
michael@0 1293 data->mFound = true;
michael@0 1294 return PL_DHASH_STOP;
michael@0 1295 }
michael@0 1296
michael@0 1297 return PL_DHASH_NEXT;
michael@0 1298 }
michael@0 1299
michael@0 1300 } // anon
michael@0 1301
michael@0 1302 bool
michael@0 1303 DOMStorageDBThread::PendingOperations::IsScopeUpdatePending(const nsACString& aScope)
michael@0 1304 {
michael@0 1305 // Called under the lock
michael@0 1306
michael@0 1307 FindPendingOperationForScopeData data(aScope);
michael@0 1308 mUpdates.EnumerateRead(FindPendingUpdateForScope, &data);
michael@0 1309 if (data.mFound) {
michael@0 1310 return true;
michael@0 1311 }
michael@0 1312
michael@0 1313 for (uint32_t i = 0; i < mExecList.Length(); ++i) {
michael@0 1314 DOMStorageDBThread::DBOperation* task = mExecList[i];
michael@0 1315 FindPendingUpdateForScope(EmptyCString(), task, &data);
michael@0 1316
michael@0 1317 if (data.mFound) {
michael@0 1318 return true;
michael@0 1319 }
michael@0 1320 }
michael@0 1321
michael@0 1322 return false;
michael@0 1323 }
michael@0 1324
michael@0 1325 } // ::dom
michael@0 1326 } // ::mozilla

mercurial