StorageDBThread.cpp (50152B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "StorageDBThread.h" 8 9 #include "GeckoProfiler.h" 10 #include "LocalStorageCache.h" 11 #include "LocalStorageManager.h" 12 #include "StorageCommon.h" 13 #include "StorageDBUpdater.h" 14 #include "StorageUtils.h" 15 #include "mozIStorageBindingParams.h" 16 #include "mozIStorageFunction.h" 17 #include "mozIStorageService.h" 18 #include "mozIStorageValueArray.h" 19 #include "mozStorageCID.h" 20 #include "mozStorageHelper.h" 21 #include "mozilla/BasePrincipal.h" 22 #include "mozilla/EventQueue.h" 23 #include "mozilla/IOInterposer.h" 24 #include "mozilla/OriginAttributes.h" 25 #include "mozilla/Services.h" 26 #include "mozilla/ThreadEventQueue.h" 27 #include "mozilla/Tokenizer.h" 28 #include "mozilla/glean/DomStorageMetrics.h" 29 #include "mozilla/ipc/BackgroundParent.h" 30 #include "nsAppDirectoryServiceDefs.h" 31 #include "nsComponentManagerUtils.h" 32 #include "nsDirectoryServiceUtils.h" 33 #include "nsIObserverService.h" 34 #include "nsProxyRelease.h" 35 #include "nsThread.h" 36 #include "nsThreadManager.h" 37 #include "nsThreadUtils.h" 38 #include "nsVariant.h" 39 40 // How long we collect write oprerations 41 // before they are flushed to the database 42 // In milliseconds. 43 #define FLUSHING_INTERVAL_MS 5000 44 45 // Write Ahead Log's maximum size is 512KB 46 #define MAX_WAL_SIZE_BYTES 512 * 1024 47 48 // Current version of the database schema 49 #define CURRENT_SCHEMA_VERSION 2 50 51 namespace mozilla::dom { 52 53 using namespace StorageUtils; 54 55 namespace { // anon 56 57 StorageDBThread* sStorageThread[kPrivateBrowsingIdCount] = {nullptr, nullptr}; 58 59 // False until we shut the storage thread down. 60 bool sStorageThreadDown[kPrivateBrowsingIdCount] = {false, false}; 61 62 } // namespace 63 64 // XXX Fix me! 65 #if 0 66 StorageDBBridge::StorageDBBridge() 67 { 68 } 69 #endif 70 71 class StorageDBThread::InitHelper final : public Runnable { 72 nsCOMPtr<nsIEventTarget> mOwningThread; 73 mozilla::Mutex mMutex MOZ_UNANNOTATED; 74 mozilla::CondVar mCondVar; 75 nsString mProfilePath; 76 nsresult mMainThreadResultCode; 77 bool mWaiting; 78 79 public: 80 InitHelper() 81 : Runnable("dom::StorageDBThread::InitHelper"), 82 mOwningThread(GetCurrentSerialEventTarget()), 83 mMutex("InitHelper::mMutex"), 84 mCondVar(mMutex, "InitHelper::mCondVar"), 85 mMainThreadResultCode(NS_OK), 86 mWaiting(true) {} 87 88 // Because of the `sync Preload` IPC, we need to be able to synchronously 89 // initialize, which includes consulting and initializing 90 // some main-thread-only APIs. Bug 1386441 discusses improving this situation. 91 nsresult SyncDispatchAndReturnProfilePath(nsAString& aProfilePath); 92 93 private: 94 ~InitHelper() override = default; 95 96 nsresult RunOnMainThread(); 97 98 NS_DECL_NSIRUNNABLE 99 }; 100 101 class StorageDBThread::NoteBackgroundThreadRunnable final : public Runnable { 102 // Expected to be only 0 or 1. 103 const uint32_t mPrivateBrowsingId; 104 nsCOMPtr<nsIEventTarget> mOwningThread; 105 106 public: 107 explicit NoteBackgroundThreadRunnable(const uint32_t aPrivateBrowsingId) 108 : Runnable("dom::StorageDBThread::NoteBackgroundThreadRunnable"), 109 mPrivateBrowsingId(aPrivateBrowsingId), 110 mOwningThread(GetCurrentSerialEventTarget()) { 111 MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount); 112 } 113 114 private: 115 ~NoteBackgroundThreadRunnable() override = default; 116 117 NS_DECL_NSIRUNNABLE 118 }; 119 120 StorageDBThread::StorageDBThread(const uint32_t aPrivateBrowsingId) 121 : mThread(nullptr), 122 mThreadObserver(new ThreadObserver()), 123 mStopIOThread(false), 124 mWALModeEnabled(false), 125 mDBReady(false), 126 mStatus(NS_OK), 127 mWorkerStatements(mWorkerConnection), 128 mReaderStatements(mReaderConnection), 129 mFlushImmediately(false), 130 mPrivateBrowsingId(aPrivateBrowsingId), 131 mPriorityCounter(0) { 132 MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount); 133 } 134 135 // static 136 StorageDBThread* StorageDBThread::Get(const uint32_t aPrivateBrowsingId) { 137 ::mozilla::ipc::AssertIsOnBackgroundThread(); 138 MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount); 139 140 return sStorageThread[aPrivateBrowsingId]; 141 } 142 143 // static 144 StorageDBThread* StorageDBThread::GetOrCreate( 145 const nsString& aProfilePath, const uint32_t aPrivateBrowsingId) { 146 ::mozilla::ipc::AssertIsOnBackgroundThread(); 147 MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount); 148 149 StorageDBThread*& storageThread = sStorageThread[aPrivateBrowsingId]; 150 if (storageThread || sStorageThreadDown[aPrivateBrowsingId]) { 151 // When sStorageThreadDown is at true, sStorageThread is null. 152 // Checking sStorageThreadDown flag here prevents reinitialization of 153 // the storage thread after shutdown. 154 return storageThread; 155 } 156 157 auto newStorageThread = MakeUnique<StorageDBThread>(aPrivateBrowsingId); 158 159 nsresult rv = newStorageThread->Init(aProfilePath); 160 if (NS_WARN_IF(NS_FAILED(rv))) { 161 return nullptr; 162 } 163 164 storageThread = newStorageThread.release(); 165 166 return storageThread; 167 } 168 169 // static 170 nsresult StorageDBThread::GetProfilePath(nsString& aProfilePath) { 171 MOZ_ASSERT(XRE_IsParentProcess()); 172 MOZ_ASSERT(NS_IsMainThread()); 173 174 // Need to determine location on the main thread, since 175 // NS_GetSpecialDirectory accesses the atom table that can 176 // only be accessed on the main thread. 177 nsCOMPtr<nsIFile> profileDir; 178 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 179 getter_AddRefs(profileDir)); 180 if (NS_WARN_IF(NS_FAILED(rv))) { 181 return rv; 182 } 183 184 rv = profileDir->GetPath(aProfilePath); 185 if (NS_WARN_IF(NS_FAILED(rv))) { 186 return rv; 187 } 188 189 // This service has to be started on the main thread currently. 190 nsCOMPtr<mozIStorageService> ss = 191 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); 192 if (NS_WARN_IF(NS_FAILED(rv))) { 193 return rv; 194 } 195 196 return NS_OK; 197 } 198 199 nsresult StorageDBThread::Init(const nsString& aProfilePath) { 200 ::mozilla::ipc::AssertIsOnBackgroundThread(); 201 202 if (mPrivateBrowsingId == 0) { 203 nsresult rv; 204 205 nsString profilePath; 206 if (aProfilePath.IsEmpty()) { 207 RefPtr<InitHelper> helper = new InitHelper(); 208 209 rv = helper->SyncDispatchAndReturnProfilePath(profilePath); 210 if (NS_WARN_IF(NS_FAILED(rv))) { 211 return rv; 212 } 213 } else { 214 profilePath = aProfilePath; 215 } 216 217 rv = NS_NewLocalFile(profilePath, getter_AddRefs(mDatabaseFile)); 218 if (NS_WARN_IF(NS_FAILED(rv))) { 219 return rv; 220 } 221 222 rv = mDatabaseFile->Append(u"webappsstore.sqlite"_ns); 223 NS_ENSURE_SUCCESS(rv, rv); 224 } 225 226 // Need to keep the lock to avoid setting mThread later then 227 // the thread body executes. 228 MonitorAutoLock monitor(mThreadObserver->GetMonitor()); 229 230 mThread = PR_CreateThread(PR_USER_THREAD, &StorageDBThread::ThreadFunc, this, 231 PR_PRIORITY_LOW, PR_GLOBAL_THREAD, 232 PR_JOINABLE_THREAD, 262144); 233 if (!mThread) { 234 return NS_ERROR_OUT_OF_MEMORY; 235 } 236 237 RefPtr<NoteBackgroundThreadRunnable> runnable = 238 new NoteBackgroundThreadRunnable(mPrivateBrowsingId); 239 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); 240 241 return NS_OK; 242 } 243 244 nsresult StorageDBThread::Shutdown() { 245 ::mozilla::ipc::AssertIsOnBackgroundThread(); 246 247 if (!mThread) { 248 return NS_ERROR_NOT_INITIALIZED; 249 } 250 251 auto timer = glean::localdomstorage::shutdown_database.Measure(); 252 253 { 254 MonitorAutoLock monitor(mThreadObserver->GetMonitor()); 255 256 // After we stop, no other operations can be accepted 257 mFlushImmediately = true; 258 mStopIOThread = true; 259 monitor.Notify(); 260 } 261 262 PR_JoinThread(mThread); 263 mThread = nullptr; 264 265 return mStatus; 266 } 267 268 void StorageDBThread::SyncPreload(LocalStorageCacheBridge* aCache, 269 bool aForceSync) { 270 AUTO_PROFILER_LABEL("StorageDBThread::SyncPreload", OTHER); 271 if (!aForceSync && aCache->LoadedCount()) { 272 // Preload already started for this cache, just wait for it to finish. 273 // LoadWait will exit after LoadDone on the cache has been called. 274 SetHigherPriority(); 275 aCache->LoadWait(); 276 SetDefaultPriority(); 277 return; 278 } 279 280 // Bypass sync load when an update is pending in the queue to write, we would 281 // get incosistent data in the cache. Also don't allow sync main-thread 282 // preload when DB open and init is still pending on the background thread. 283 if (mDBReady && mWALModeEnabled) { 284 bool pendingTasks; 285 { 286 MonitorAutoLock monitor(mThreadObserver->GetMonitor()); 287 pendingTasks = mPendingTasks.IsOriginUpdatePending( 288 aCache->OriginSuffix(), aCache->OriginNoSuffix()) || 289 mPendingTasks.IsOriginClearPending( 290 aCache->OriginSuffix(), aCache->OriginNoSuffix()); 291 } 292 293 if (!pendingTasks) { 294 // WAL is enabled, thus do the load synchronously on the main thread. 295 DBOperation preload(DBOperation::opPreload, aCache); 296 preload.PerformAndFinalize(this); 297 return; 298 } 299 } 300 301 // Need to go asynchronously since WAL is not allowed or scheduled updates 302 // need to be flushed first. 303 // Schedule preload for this cache as the first operation. 304 nsresult rv = 305 InsertDBOp(MakeUnique<DBOperation>(DBOperation::opPreloadUrgent, aCache)); 306 307 // LoadWait exits after LoadDone of the cache has been called. 308 if (NS_SUCCEEDED(rv)) { 309 aCache->LoadWait(); 310 } 311 } 312 313 void StorageDBThread::AsyncFlush() { 314 MonitorAutoLock monitor(mThreadObserver->GetMonitor()); 315 mFlushImmediately = true; 316 monitor.Notify(); 317 } 318 319 bool StorageDBThread::ShouldPreloadOrigin(const nsACString& aOrigin) { 320 MonitorAutoLock monitor(mThreadObserver->GetMonitor()); 321 return mOriginsHavingData.Contains(aOrigin); 322 } 323 324 void StorageDBThread::GetOriginsHavingData(nsTArray<nsCString>* aOrigins) { 325 MonitorAutoLock monitor(mThreadObserver->GetMonitor()); 326 AppendToArray(*aOrigins, mOriginsHavingData); 327 } 328 329 nsresult StorageDBThread::InsertDBOp( 330 UniquePtr<StorageDBThread::DBOperation> aOperation) { 331 MonitorAutoLock monitor(mThreadObserver->GetMonitor()); 332 333 if (NS_FAILED(mStatus)) { 334 MonitorAutoUnlock unlock(mThreadObserver->GetMonitor()); 335 aOperation->Finalize(mStatus); 336 return mStatus; 337 } 338 339 if (mStopIOThread) { 340 // Thread use after shutdown demanded. 341 MOZ_ASSERT(false); 342 return NS_ERROR_NOT_INITIALIZED; 343 } 344 345 switch (aOperation->Type()) { 346 case DBOperation::opPreload: 347 case DBOperation::opPreloadUrgent: 348 if (mPendingTasks.IsOriginUpdatePending(aOperation->OriginSuffix(), 349 aOperation->OriginNoSuffix())) { 350 // If there is a pending update operation for the scope first do the 351 // flush before we preload the cache. This may happen in an extremely 352 // rare case when a child process throws away its cache before flush on 353 // the parent has finished. If we would preloaded the cache as a 354 // priority operation before the pending flush, we would have got an 355 // inconsistent cache content. 356 mFlushImmediately = true; 357 } else if (mPendingTasks.IsOriginClearPending( 358 aOperation->OriginSuffix(), 359 aOperation->OriginNoSuffix())) { 360 // The scope is scheduled to be cleared, so just quickly load as empty. 361 // We need to do this to prevent load of the DB data before the scope 362 // has actually been cleared from the database. Preloads are processed 363 // immediately before update and clear operations on the database that 364 // are flushed periodically in batches. 365 MonitorAutoUnlock unlock(mThreadObserver->GetMonitor()); 366 aOperation->Finalize(NS_OK); 367 return NS_OK; 368 } 369 [[fallthrough]]; 370 371 case DBOperation::opGetUsage: 372 if (aOperation->Type() == DBOperation::opPreloadUrgent) { 373 SetHigherPriority(); // Dropped back after urgent preload execution 374 mPreloads.InsertElementAt(0, aOperation.release()); 375 } else { 376 mPreloads.AppendElement(aOperation.release()); 377 } 378 379 // Immediately start executing this. 380 monitor.Notify(); 381 break; 382 383 default: 384 // Update operations are first collected, coalesced and then flushed 385 // after a short time. 386 mPendingTasks.Add(std::move(aOperation)); 387 388 ScheduleFlush(); 389 break; 390 } 391 392 return NS_OK; 393 } 394 395 void StorageDBThread::SetHigherPriority() { 396 ++mPriorityCounter; 397 PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT); 398 } 399 400 void StorageDBThread::SetDefaultPriority() { 401 if (--mPriorityCounter <= 0) { 402 PR_SetThreadPriority(mThread, PR_PRIORITY_LOW); 403 } 404 } 405 406 void StorageDBThread::ThreadFunc(void* aArg) { 407 { 408 auto queue = MakeRefPtr<ThreadEventQueue>(MakeUnique<EventQueue>()); 409 (void)nsThreadManager::get().CreateCurrentThread(queue); 410 } 411 412 AUTO_PROFILER_REGISTER_THREAD("localStorage DB"); 413 NS_SetCurrentThreadName("localStorage DB"); 414 mozilla::IOInterposer::RegisterCurrentThread(); 415 416 StorageDBThread* thread = static_cast<StorageDBThread*>(aArg); 417 thread->ThreadFunc(); 418 mozilla::IOInterposer::UnregisterCurrentThread(); 419 } 420 421 void StorageDBThread::ThreadFunc() { 422 nsresult rv = InitDatabase(); 423 424 MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor()); 425 426 if (NS_FAILED(rv)) { 427 mStatus = rv; 428 mStopIOThread = true; 429 return; 430 } 431 432 // Create an nsIThread for the current PRThread, so we can observe runnables 433 // dispatched to it. 434 nsCOMPtr<nsIThread> thread = NS_GetCurrentThread(); 435 nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread); 436 MOZ_ASSERT(threadInternal); // Should always succeed. 437 threadInternal->SetObserver(mThreadObserver); 438 439 while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() || 440 mPendingTasks.HasTasks() || 441 mThreadObserver->HasPendingEvents())) { 442 // Process xpcom events first. 443 while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) { 444 mThreadObserver->ClearPendingEvents(); 445 MonitorAutoUnlock unlock(mThreadObserver->GetMonitor()); 446 bool processedEvent; 447 do { 448 rv = thread->ProcessNextEvent(false, &processedEvent); 449 } while (NS_SUCCEEDED(rv) && processedEvent); 450 } 451 452 TimeDuration timeUntilFlush = TimeUntilFlush(); 453 if (MOZ_UNLIKELY(timeUntilFlush.IsZero())) { 454 // Flush time is up or flush has been forced, do it now. 455 UnscheduleFlush(); 456 if (mPendingTasks.Prepare()) { 457 { 458 MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor()); 459 rv = mPendingTasks.Execute(this); 460 } 461 462 if (!mPendingTasks.Finalize(rv)) { 463 mStatus = rv; 464 NS_WARNING("localStorage DB access broken"); 465 } 466 } 467 NotifyFlushCompletion(); 468 } else if (MOZ_LIKELY(mPreloads.Length())) { 469 UniquePtr<DBOperation> op(mPreloads[0]); 470 mPreloads.RemoveElementAt(0); 471 { 472 MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor()); 473 op->PerformAndFinalize(this); 474 } 475 476 if (op->Type() == DBOperation::opPreloadUrgent) { 477 SetDefaultPriority(); // urgent preload unscheduled 478 } 479 } else if (MOZ_UNLIKELY(!mStopIOThread)) { 480 AUTO_PROFILER_LABEL("StorageDBThread::ThreadFunc::Wait", IDLE); 481 lockMonitor.Wait(timeUntilFlush); 482 } 483 } // thread loop 484 485 mStatus = ShutdownDatabase(); 486 487 if (threadInternal) { 488 threadInternal->SetObserver(nullptr); 489 } 490 } 491 492 NS_IMPL_ISUPPORTS(StorageDBThread::ThreadObserver, nsIThreadObserver) 493 494 NS_IMETHODIMP 495 StorageDBThread::ThreadObserver::OnDispatchedEvent() { 496 MonitorAutoLock lock(mMonitor); 497 mHasPendingEvents = true; 498 lock.Notify(); 499 return NS_OK; 500 } 501 502 NS_IMETHODIMP 503 StorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread, 504 bool mayWait) { 505 return NS_OK; 506 } 507 508 NS_IMETHODIMP 509 StorageDBThread::ThreadObserver::AfterProcessNextEvent( 510 nsIThreadInternal* aThread, bool eventWasProcessed) { 511 return NS_OK; 512 } 513 514 nsresult StorageDBThread::OpenDatabaseConnection() { 515 nsresult rv; 516 517 MOZ_ASSERT(!NS_IsMainThread()); 518 519 nsCOMPtr<mozIStorageService> service = 520 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); 521 NS_ENSURE_SUCCESS(rv, rv); 522 523 if (mPrivateBrowsingId == 0) { 524 MOZ_ASSERT(mDatabaseFile); 525 526 rv = service->OpenUnsharedDatabase(mDatabaseFile, 527 mozIStorageService::CONNECTION_DEFAULT, 528 getter_AddRefs(mWorkerConnection)); 529 if (rv == NS_ERROR_FILE_CORRUPTED) { 530 // delete the db and try opening again 531 rv = mDatabaseFile->Remove(false); 532 NS_ENSURE_SUCCESS(rv, rv); 533 rv = service->OpenUnsharedDatabase(mDatabaseFile, 534 mozIStorageService::CONNECTION_DEFAULT, 535 getter_AddRefs(mWorkerConnection)); 536 } 537 } else { 538 MOZ_ASSERT(mPrivateBrowsingId == 1); 539 540 rv = service->OpenSpecialDatabase(kMozStorageMemoryStorageKey, 541 "lsprivatedb"_ns, 542 mozIStorageService::CONNECTION_DEFAULT, 543 getter_AddRefs(mWorkerConnection)); 544 } 545 NS_ENSURE_SUCCESS(rv, rv); 546 547 return NS_OK; 548 } 549 550 nsresult StorageDBThread::OpenAndUpdateDatabase() { 551 nsresult rv; 552 553 // Here we are on the worker thread. This opens the worker connection. 554 MOZ_ASSERT(!NS_IsMainThread()); 555 556 rv = OpenDatabaseConnection(); 557 NS_ENSURE_SUCCESS(rv, rv); 558 559 // SQLite doesn't support WAL journals for in-memory databases. 560 if (mPrivateBrowsingId == 0) { 561 rv = TryJournalMode(); 562 NS_ENSURE_SUCCESS(rv, rv); 563 } 564 565 return NS_OK; 566 } 567 568 nsresult StorageDBThread::InitDatabase() { 569 nsresult rv; 570 571 // Here we are on the worker thread. This opens the worker connection. 572 MOZ_ASSERT(!NS_IsMainThread()); 573 574 rv = OpenAndUpdateDatabase(); 575 NS_ENSURE_SUCCESS(rv, rv); 576 577 rv = StorageDBUpdater::Update(mWorkerConnection); 578 if (NS_FAILED(rv)) { 579 if (mPrivateBrowsingId == 0) { 580 // Update has failed, rather throw the database away and try 581 // opening and setting it up again. 582 rv = mWorkerConnection->Close(); 583 mWorkerConnection = nullptr; 584 NS_ENSURE_SUCCESS(rv, rv); 585 586 rv = mDatabaseFile->Remove(false); 587 NS_ENSURE_SUCCESS(rv, rv); 588 589 rv = OpenAndUpdateDatabase(); 590 } 591 NS_ENSURE_SUCCESS(rv, rv); 592 } 593 594 // Create a read-only clone 595 (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection)); 596 NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE); 597 598 // Database open and all initiation operation are done. Switching this flag 599 // to true allow main thread to read directly from the database. If we would 600 // allow this sooner, we would have opened a window where main thread read 601 // might operate on a totally broken and incosistent database. 602 mDBReady = true; 603 604 // List scopes having any stored data 605 nsCOMPtr<mozIStorageStatement> stmt; 606 // Note: result of this select must match StorageManager::CreateOrigin() 607 rv = mWorkerConnection->CreateStatement( 608 nsLiteralCString("SELECT DISTINCT originAttributes || ':' || originKey " 609 "FROM webappsstore2"), 610 getter_AddRefs(stmt)); 611 NS_ENSURE_SUCCESS(rv, rv); 612 mozStorageStatementScoper scope(stmt); 613 614 bool exists; 615 while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) { 616 nsAutoCString foundOrigin; 617 rv = stmt->GetUTF8String(0, foundOrigin); 618 NS_ENSURE_SUCCESS(rv, rv); 619 620 MonitorAutoLock monitor(mThreadObserver->GetMonitor()); 621 mOriginsHavingData.Insert(foundOrigin); 622 } 623 624 return NS_OK; 625 } 626 627 nsresult StorageDBThread::SetJournalMode(bool aIsWal) { 628 nsresult rv; 629 630 nsAutoCString stmtString(MOZ_STORAGE_UNIQUIFY_QUERY_STR 631 "PRAGMA journal_mode = "); 632 if (aIsWal) { 633 stmtString.AppendLiteral("wal"); 634 } else { 635 stmtString.AppendLiteral("truncate"); 636 } 637 638 nsCOMPtr<mozIStorageStatement> stmt; 639 rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt)); 640 NS_ENSURE_SUCCESS(rv, rv); 641 mozStorageStatementScoper scope(stmt); 642 643 bool hasResult = false; 644 rv = stmt->ExecuteStep(&hasResult); 645 NS_ENSURE_SUCCESS(rv, rv); 646 if (!hasResult) { 647 return NS_ERROR_FAILURE; 648 } 649 650 nsAutoCString journalMode; 651 rv = stmt->GetUTF8String(0, journalMode); 652 NS_ENSURE_SUCCESS(rv, rv); 653 if ((aIsWal && !journalMode.EqualsLiteral("wal")) || 654 (!aIsWal && !journalMode.EqualsLiteral("truncate"))) { 655 return NS_ERROR_FAILURE; 656 } 657 658 return NS_OK; 659 } 660 661 nsresult StorageDBThread::TryJournalMode() { 662 nsresult rv; 663 664 rv = SetJournalMode(true); 665 if (NS_FAILED(rv)) { 666 mWALModeEnabled = false; 667 668 rv = SetJournalMode(false); 669 NS_ENSURE_SUCCESS(rv, rv); 670 } else { 671 mWALModeEnabled = true; 672 673 rv = ConfigureWALBehavior(); 674 NS_ENSURE_SUCCESS(rv, rv); 675 } 676 677 return NS_OK; 678 } 679 680 nsresult StorageDBThread::ConfigureWALBehavior() { 681 // Get the DB's page size 682 nsCOMPtr<mozIStorageStatement> stmt; 683 nsresult rv = mWorkerConnection->CreateStatement( 684 nsLiteralCString(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"), 685 getter_AddRefs(stmt)); 686 NS_ENSURE_SUCCESS(rv, rv); 687 688 bool hasResult = false; 689 rv = stmt->ExecuteStep(&hasResult); 690 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE); 691 692 int32_t pageSize = 0; 693 rv = stmt->GetInt32(0, &pageSize); 694 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED); 695 696 // Set the threshold for auto-checkpointing the WAL. 697 // We don't want giant logs slowing down reads & shutdown. 698 // Note there is a default journal_size_limit set by mozStorage. 699 int32_t thresholdInPages = 700 static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize); 701 nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = "); 702 thresholdPragma.AppendInt(thresholdInPages); 703 rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma); 704 NS_ENSURE_SUCCESS(rv, rv); 705 706 return NS_OK; 707 } 708 709 nsresult StorageDBThread::ShutdownDatabase() { 710 // Has to be called on the worker thread. 711 MOZ_ASSERT(!NS_IsMainThread()); 712 713 nsresult rv = mStatus; 714 715 mDBReady = false; 716 717 // Finalize the cached statements. 718 mReaderStatements.FinalizeStatements(); 719 mWorkerStatements.FinalizeStatements(); 720 721 if (mReaderConnection) { 722 // No need to sync access to mReaderConnection since the main thread 723 // is right now joining this thread, unable to execute any events. 724 mReaderConnection->Close(); 725 mReaderConnection = nullptr; 726 } 727 728 if (mWorkerConnection) { 729 rv = mWorkerConnection->Close(); 730 mWorkerConnection = nullptr; 731 } 732 733 return rv; 734 } 735 736 void StorageDBThread::ScheduleFlush() { 737 if (mDirtyEpoch) { 738 return; // Already scheduled 739 } 740 741 // Must be non-zero to indicate we are scheduled 742 mDirtyEpoch = TimeStamp::Now(); 743 744 // Wake the monitor from indefinite sleep... 745 (mThreadObserver->GetMonitor()).Notify(); 746 } 747 748 void StorageDBThread::UnscheduleFlush() { 749 // We are just about to do the flush, drop flags 750 mFlushImmediately = false; 751 mDirtyEpoch = TimeStamp(); 752 } 753 754 TimeDuration StorageDBThread::TimeUntilFlush() { 755 if (mFlushImmediately) { 756 return 0; // Do it now regardless the timeout. 757 } 758 759 if (!mDirtyEpoch) { 760 return TimeDuration::Forever(); // No pending task... 761 } 762 763 TimeStamp now = TimeStamp::Now(); 764 TimeDuration age = now - mDirtyEpoch; 765 static const TimeDuration kMaxAge = 766 TimeDuration::FromMilliseconds(FLUSHING_INTERVAL_MS); 767 if (age > kMaxAge) { 768 return 0; // It is time. 769 } 770 771 return kMaxAge - age; // Time left. This is used to sleep the monitor. 772 } 773 774 void StorageDBThread::NotifyFlushCompletion() { 775 #ifdef DOM_STORAGE_TESTS 776 if (!NS_IsMainThread()) { 777 RefPtr<nsRunnableMethod<StorageDBThread, void, false>> event = 778 NewNonOwningRunnableMethod( 779 "dom::StorageDBThread::NotifyFlushCompletion", this, 780 &StorageDBThread::NotifyFlushCompletion); 781 NS_DispatchToMainThread(event); 782 return; 783 } 784 785 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 786 if (obs) { 787 obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr); 788 } 789 #endif 790 } 791 792 // Helper SQL function classes 793 794 namespace { 795 796 class OriginAttrsPatternMatchSQLFunction final : public mozIStorageFunction { 797 NS_DECL_ISUPPORTS 798 NS_DECL_MOZISTORAGEFUNCTION 799 800 explicit OriginAttrsPatternMatchSQLFunction( 801 OriginAttributesPattern const& aPattern) 802 : mPattern(aPattern) {} 803 804 private: 805 OriginAttrsPatternMatchSQLFunction() = delete; 806 ~OriginAttrsPatternMatchSQLFunction() = default; 807 808 OriginAttributesPattern mPattern; 809 }; 810 811 NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchSQLFunction, mozIStorageFunction) 812 813 NS_IMETHODIMP 814 OriginAttrsPatternMatchSQLFunction::OnFunctionCall( 815 mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { 816 nsresult rv; 817 818 nsAutoCString suffix; 819 rv = aFunctionArguments->GetUTF8String(0, suffix); 820 NS_ENSURE_SUCCESS(rv, rv); 821 822 OriginAttributes oa; 823 bool success = oa.PopulateFromSuffix(suffix); 824 NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); 825 bool result = mPattern.Matches(oa); 826 827 RefPtr<nsVariant> outVar(new nsVariant()); 828 rv = outVar->SetAsBool(result); 829 NS_ENSURE_SUCCESS(rv, rv); 830 831 outVar.forget(aResult); 832 return NS_OK; 833 } 834 835 } // namespace 836 837 // StorageDBThread::DBOperation 838 839 StorageDBThread::DBOperation::DBOperation(const OperationType aType, 840 LocalStorageCacheBridge* aCache, 841 const nsAString& aKey, 842 const nsAString& aValue) 843 : mType(aType), mCache(aCache), mKey(aKey), mValue(aValue) { 844 MOZ_ASSERT(mType == opPreload || mType == opPreloadUrgent || 845 mType == opAddItem || mType == opUpdateItem || 846 mType == opRemoveItem || mType == opClear || mType == opClearAll); 847 MOZ_COUNT_CTOR(StorageDBThread::DBOperation); 848 } 849 850 StorageDBThread::DBOperation::DBOperation(const OperationType aType, 851 StorageUsageBridge* aUsage) 852 : mType(aType), mUsage(aUsage) { 853 MOZ_ASSERT(mType == opGetUsage); 854 MOZ_COUNT_CTOR(StorageDBThread::DBOperation); 855 } 856 857 StorageDBThread::DBOperation::DBOperation(const OperationType aType, 858 const nsACString& aOriginNoSuffix) 859 : mType(aType), mCache(nullptr), mOrigin(aOriginNoSuffix) { 860 MOZ_ASSERT(mType == opClearMatchingOrigin); 861 MOZ_COUNT_CTOR(StorageDBThread::DBOperation); 862 } 863 864 StorageDBThread::DBOperation::DBOperation( 865 const OperationType aType, const OriginAttributesPattern& aOriginNoSuffix) 866 : mType(aType), mCache(nullptr), mOriginPattern(aOriginNoSuffix) { 867 MOZ_ASSERT(mType == opClearMatchingOriginAttributes); 868 MOZ_COUNT_CTOR(StorageDBThread::DBOperation); 869 } 870 871 StorageDBThread::DBOperation::~DBOperation() { 872 MOZ_COUNT_DTOR(StorageDBThread::DBOperation); 873 } 874 875 const nsCString StorageDBThread::DBOperation::OriginNoSuffix() const { 876 if (mCache) { 877 return mCache->OriginNoSuffix(); 878 } 879 880 return ""_ns; 881 } 882 883 const nsCString StorageDBThread::DBOperation::OriginSuffix() const { 884 if (mCache) { 885 return mCache->OriginSuffix(); 886 } 887 888 return ""_ns; 889 } 890 891 const nsCString StorageDBThread::DBOperation::Origin() const { 892 if (mCache) { 893 return mCache->Origin(); 894 } 895 896 return mOrigin; 897 } 898 899 const nsCString StorageDBThread::DBOperation::Target() const { 900 switch (mType) { 901 case opAddItem: 902 case opUpdateItem: 903 case opRemoveItem: 904 return Origin() + "|"_ns + NS_ConvertUTF16toUTF8(mKey); 905 906 default: 907 return Origin(); 908 } 909 } 910 911 void StorageDBThread::DBOperation::PerformAndFinalize( 912 StorageDBThread* aThread) { 913 Finalize(Perform(aThread)); 914 } 915 916 nsresult StorageDBThread::DBOperation::Perform(StorageDBThread* aThread) { 917 nsresult rv; 918 919 switch (mType) { 920 case opPreload: 921 case opPreloadUrgent: { 922 // Already loaded? 923 if (mCache->Loaded()) { 924 break; 925 } 926 927 StatementCache* statements; 928 if (MOZ_UNLIKELY(::mozilla::ipc::IsOnBackgroundThread())) { 929 statements = &aThread->mReaderStatements; 930 } else { 931 statements = &aThread->mWorkerStatements; 932 } 933 934 // OFFSET is an optimization when we have to do a sync load 935 // and cache has already loaded some parts asynchronously. 936 // It skips keys we have already loaded. 937 nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement( 938 "SELECT key, value FROM webappsstore2 " 939 "WHERE originAttributes = :originAttributes AND originKey = " 940 ":originKey " 941 "ORDER BY key LIMIT -1 OFFSET :offset"); 942 NS_ENSURE_STATE(stmt); 943 mozStorageStatementScoper scope(stmt); 944 945 rv = stmt->BindUTF8StringByName("originAttributes"_ns, 946 mCache->OriginSuffix()); 947 NS_ENSURE_SUCCESS(rv, rv); 948 949 rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix()); 950 NS_ENSURE_SUCCESS(rv, rv); 951 952 rv = stmt->BindInt32ByName("offset"_ns, 953 static_cast<int32_t>(mCache->LoadedCount())); 954 NS_ENSURE_SUCCESS(rv, rv); 955 956 bool exists; 957 while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) { 958 nsAutoString key; 959 rv = stmt->GetString(0, key); 960 NS_ENSURE_SUCCESS(rv, rv); 961 962 nsAutoString value; 963 rv = stmt->GetString(1, value); 964 NS_ENSURE_SUCCESS(rv, rv); 965 966 if (!mCache->LoadItem(key, value)) { 967 break; 968 } 969 } 970 // The loop condition's call to ExecuteStep() may have terminated because 971 // !NS_SUCCEEDED(), we need an early return to cover that case. This also 972 // covers success cases as well, but that's inductively safe. 973 NS_ENSURE_SUCCESS(rv, rv); 974 break; 975 } 976 977 case opGetUsage: { 978 // Bug 1676410 fixed a regression caused by bug 1165214. However, it 979 // turns out that 100% correct checking of the eTLD+1 usage is not 980 // possible to recover easily, see bug 1683299. 981 #if 0 982 // This is how it should be done, but due to other problems like lack 983 // of usage synchronization between content processes, we temporarily 984 // disabled the matching using "%". 985 986 nsCOMPtr<mozIStorageStatement> stmt = 987 aThread->mWorkerStatements.GetCachedStatement( 988 "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 " 989 "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin " 990 "ESCAPE '\\'"); 991 NS_ENSURE_STATE(stmt); 992 993 mozStorageStatementScoper scope(stmt); 994 995 // The database schema is built around cleverly reversing domain names 996 // (the "originKey") so that we can efficiently group usage by eTLD+1. 997 // "foo.example.org" has an eTLD+1 of ".example.org". They reverse to 998 // "gro.elpmaxe.oof" and "gro.elpmaxe." respectively, noting that the 999 // reversed eTLD+1 is a prefix of its reversed sub-domain. To this end, 1000 // we can calculate all of the usage for an eTLD+1 by summing up all the 1001 // rows which have the reversed eTLD+1 as a prefix. In SQL we can 1002 // accomplish this using LIKE which provides for case-insensitive 1003 // matching with "_" as a single-character wildcard match and "%" any 1004 // sequence of zero or more characters. So by suffixing the reversed 1005 // eTLD+1 and using "%" we get our case-insensitive (domain names are 1006 // case-insensitive) matching. Note that although legal domain names 1007 // don't include "_" or "%", file origins can include them, so we need 1008 // to escape our OriginScope for correctness. 1009 nsAutoCString originScopeEscaped; 1010 rv = stmt->EscapeUTF8StringForLIKE(mUsage->OriginScope(), '\\', 1011 originScopeEscaped); 1012 NS_ENSURE_SUCCESS(rv, rv); 1013 1014 rv = stmt->BindUTF8StringByName("usageOrigin"_ns, 1015 originScopeEscaped + "%"_ns); 1016 NS_ENSURE_SUCCESS(rv, rv); 1017 #else 1018 // This is the code before bug 1676410 and bug 1676973. The returned 1019 // usage will be zero in most of the cases, but due to lack of usage 1020 // synchronization between content processes we have to live with this 1021 // semi-broken behaviour because it causes less harm than the matching 1022 // using "%". 1023 1024 nsCOMPtr<mozIStorageStatement> stmt = 1025 aThread->mWorkerStatements.GetCachedStatement( 1026 "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 " 1027 "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin"); 1028 NS_ENSURE_STATE(stmt); 1029 1030 mozStorageStatementScoper scope(stmt); 1031 1032 rv = stmt->BindUTF8StringByName("usageOrigin"_ns, mUsage->OriginScope()); 1033 NS_ENSURE_SUCCESS(rv, rv); 1034 #endif 1035 1036 bool exists; 1037 rv = stmt->ExecuteStep(&exists); 1038 NS_ENSURE_SUCCESS(rv, rv); 1039 1040 int64_t usage = 0; 1041 if (exists) { 1042 rv = stmt->GetInt64(0, &usage); 1043 NS_ENSURE_SUCCESS(rv, rv); 1044 } 1045 1046 mUsage->LoadUsage(usage); 1047 break; 1048 } 1049 1050 case opAddItem: 1051 case opUpdateItem: { 1052 MOZ_ASSERT(!NS_IsMainThread()); 1053 1054 nsCOMPtr<mozIStorageStatement> stmt = 1055 aThread->mWorkerStatements.GetCachedStatement( 1056 "INSERT OR REPLACE INTO webappsstore2 (originAttributes, " 1057 "originKey, scope, key, value) " 1058 "VALUES (:originAttributes, :originKey, :scope, :key, :value) "); 1059 NS_ENSURE_STATE(stmt); 1060 1061 mozStorageStatementScoper scope(stmt); 1062 1063 rv = stmt->BindUTF8StringByName("originAttributes"_ns, 1064 mCache->OriginSuffix()); 1065 NS_ENSURE_SUCCESS(rv, rv); 1066 rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix()); 1067 NS_ENSURE_SUCCESS(rv, rv); 1068 // Filling the 'scope' column just for downgrade compatibility reasons 1069 rv = stmt->BindUTF8StringByName( 1070 "scope"_ns, 1071 Scheme0Scope(mCache->OriginSuffix(), mCache->OriginNoSuffix())); 1072 NS_ENSURE_SUCCESS(rv, rv); 1073 rv = stmt->BindStringByName("key"_ns, mKey); 1074 NS_ENSURE_SUCCESS(rv, rv); 1075 rv = stmt->BindStringByName("value"_ns, mValue); 1076 NS_ENSURE_SUCCESS(rv, rv); 1077 1078 rv = stmt->Execute(); 1079 NS_ENSURE_SUCCESS(rv, rv); 1080 1081 MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor()); 1082 aThread->mOriginsHavingData.Insert(Origin()); 1083 break; 1084 } 1085 1086 case opRemoveItem: { 1087 MOZ_ASSERT(!NS_IsMainThread()); 1088 1089 nsCOMPtr<mozIStorageStatement> stmt = 1090 aThread->mWorkerStatements.GetCachedStatement( 1091 "DELETE FROM webappsstore2 " 1092 "WHERE originAttributes = :originAttributes AND originKey = " 1093 ":originKey " 1094 "AND key = :key "); 1095 NS_ENSURE_STATE(stmt); 1096 mozStorageStatementScoper scope(stmt); 1097 1098 rv = stmt->BindUTF8StringByName("originAttributes"_ns, 1099 mCache->OriginSuffix()); 1100 NS_ENSURE_SUCCESS(rv, rv); 1101 rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix()); 1102 NS_ENSURE_SUCCESS(rv, rv); 1103 rv = stmt->BindStringByName("key"_ns, mKey); 1104 NS_ENSURE_SUCCESS(rv, rv); 1105 1106 rv = stmt->Execute(); 1107 NS_ENSURE_SUCCESS(rv, rv); 1108 1109 break; 1110 } 1111 1112 case opClear: { 1113 MOZ_ASSERT(!NS_IsMainThread()); 1114 1115 nsCOMPtr<mozIStorageStatement> stmt = 1116 aThread->mWorkerStatements.GetCachedStatement( 1117 "DELETE FROM webappsstore2 " 1118 "WHERE originAttributes = :originAttributes AND originKey = " 1119 ":originKey"); 1120 NS_ENSURE_STATE(stmt); 1121 mozStorageStatementScoper scope(stmt); 1122 1123 rv = stmt->BindUTF8StringByName("originAttributes"_ns, 1124 mCache->OriginSuffix()); 1125 NS_ENSURE_SUCCESS(rv, rv); 1126 rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix()); 1127 NS_ENSURE_SUCCESS(rv, rv); 1128 1129 rv = stmt->Execute(); 1130 NS_ENSURE_SUCCESS(rv, rv); 1131 1132 MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor()); 1133 aThread->mOriginsHavingData.Remove(Origin()); 1134 break; 1135 } 1136 1137 case opClearAll: { 1138 MOZ_ASSERT(!NS_IsMainThread()); 1139 1140 nsCOMPtr<mozIStorageStatement> stmt = 1141 aThread->mWorkerStatements.GetCachedStatement( 1142 "DELETE FROM webappsstore2"); 1143 NS_ENSURE_STATE(stmt); 1144 mozStorageStatementScoper scope(stmt); 1145 1146 rv = stmt->Execute(); 1147 NS_ENSURE_SUCCESS(rv, rv); 1148 1149 MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor()); 1150 aThread->mOriginsHavingData.Clear(); 1151 break; 1152 } 1153 1154 case opClearMatchingOrigin: { 1155 MOZ_ASSERT(!NS_IsMainThread()); 1156 1157 nsCOMPtr<mozIStorageStatement> stmt = 1158 aThread->mWorkerStatements.GetCachedStatement( 1159 "DELETE FROM webappsstore2" 1160 " WHERE originKey GLOB :scope"); 1161 NS_ENSURE_STATE(stmt); 1162 mozStorageStatementScoper scope(stmt); 1163 1164 rv = stmt->BindUTF8StringByName("scope"_ns, mOrigin + "*"_ns); 1165 NS_ENSURE_SUCCESS(rv, rv); 1166 1167 rv = stmt->Execute(); 1168 NS_ENSURE_SUCCESS(rv, rv); 1169 1170 // No need to selectively clear mOriginsHavingData here. That hashtable 1171 // only prevents preload for scopes with no data. Leaving a false record 1172 // in it has a negligible effect on performance. 1173 break; 1174 } 1175 1176 case opClearMatchingOriginAttributes: { 1177 MOZ_ASSERT(!NS_IsMainThread()); 1178 1179 // Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the 1180 // pattern 1181 nsCOMPtr<mozIStorageFunction> patternMatchFunction( 1182 new OriginAttrsPatternMatchSQLFunction(mOriginPattern)); 1183 1184 rv = aThread->mWorkerConnection->CreateFunction( 1185 "ORIGIN_ATTRS_PATTERN_MATCH"_ns, 1, patternMatchFunction); 1186 NS_ENSURE_SUCCESS(rv, rv); 1187 1188 nsCOMPtr<mozIStorageStatement> stmt = 1189 aThread->mWorkerStatements.GetCachedStatement( 1190 "DELETE FROM webappsstore2" 1191 " WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)"); 1192 1193 if (stmt) { 1194 mozStorageStatementScoper scope(stmt); 1195 rv = stmt->Execute(); 1196 } else { 1197 rv = NS_ERROR_UNEXPECTED; 1198 } 1199 1200 // Always remove the function 1201 aThread->mWorkerConnection->RemoveFunction( 1202 "ORIGIN_ATTRS_PATTERN_MATCH"_ns); 1203 1204 NS_ENSURE_SUCCESS(rv, rv); 1205 1206 // No need to selectively clear mOriginsHavingData here. That hashtable 1207 // only prevents preload for scopes with no data. Leaving a false record 1208 // in it has a negligible effect on performance. 1209 break; 1210 } 1211 1212 default: 1213 NS_ERROR("Unknown task type"); 1214 break; 1215 } 1216 1217 return NS_OK; 1218 } 1219 1220 void StorageDBThread::DBOperation::Finalize(nsresult aRv) { 1221 switch (mType) { 1222 case opPreloadUrgent: 1223 case opPreload: 1224 if (NS_FAILED(aRv)) { 1225 // When we are here, something failed when loading from the database. 1226 // Notify that the storage is loaded to prevent deadlock of the main 1227 // thread, even though it is actually empty or incomplete. 1228 NS_WARNING("Failed to preload localStorage"); 1229 } 1230 1231 mCache->LoadDone(aRv); 1232 break; 1233 1234 case opGetUsage: 1235 if (NS_FAILED(aRv)) { 1236 mUsage->LoadUsage(0); 1237 } 1238 1239 break; 1240 1241 default: 1242 if (NS_FAILED(aRv)) { 1243 NS_WARNING( 1244 "localStorage update/clear operation failed," 1245 " data may not persist or clean up"); 1246 } 1247 1248 break; 1249 } 1250 } 1251 1252 // StorageDBThread::PendingOperations 1253 1254 StorageDBThread::PendingOperations::PendingOperations() 1255 : mFlushFailureCount(0) {} 1256 1257 bool StorageDBThread::PendingOperations::HasTasks() const { 1258 return !!mUpdates.Count() || !!mClears.Count(); 1259 } 1260 1261 namespace { 1262 1263 bool OriginPatternMatches(const nsACString& aOriginSuffix, 1264 const OriginAttributesPattern& aPattern) { 1265 OriginAttributes oa; 1266 DebugOnly<bool> rv = oa.PopulateFromSuffix(aOriginSuffix); 1267 MOZ_ASSERT(rv); 1268 return aPattern.Matches(oa); 1269 } 1270 1271 } // namespace 1272 1273 bool StorageDBThread::PendingOperations::CheckForCoalesceOpportunity( 1274 DBOperation* aNewOp, DBOperation::OperationType aPendingType, 1275 DBOperation::OperationType aNewType) { 1276 if (aNewOp->Type() != aNewType) { 1277 return false; 1278 } 1279 1280 StorageDBThread::DBOperation* pendingTask; 1281 if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) { 1282 return false; 1283 } 1284 1285 if (pendingTask->Type() != aPendingType) { 1286 return false; 1287 } 1288 1289 return true; 1290 } 1291 1292 void StorageDBThread::PendingOperations::Add( 1293 UniquePtr<StorageDBThread::DBOperation> aOperation) { 1294 // Optimize: when a key to remove has never been written to disk 1295 // just bypass this operation. A key is new when an operation scheduled 1296 // to write it to the database is of type opAddItem. 1297 if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opAddItem, 1298 DBOperation::opRemoveItem)) { 1299 mUpdates.Remove(aOperation->Target()); 1300 return; 1301 } 1302 1303 // Optimize: when changing a key that is new and has never been 1304 // written to disk, keep type of the operation to store it at opAddItem. 1305 // This allows optimization to just forget adding a new key when 1306 // it is removed from the storage before flush. 1307 if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opAddItem, 1308 DBOperation::opUpdateItem)) { 1309 aOperation->mType = DBOperation::opAddItem; 1310 } 1311 1312 // Optimize: to prevent lose of remove operation on a key when doing 1313 // remove/set/remove on a previously existing key we have to change 1314 // opAddItem to opUpdateItem on the new operation when there is opRemoveItem 1315 // pending for the key. 1316 if (CheckForCoalesceOpportunity(aOperation.get(), DBOperation::opRemoveItem, 1317 DBOperation::opAddItem)) { 1318 aOperation->mType = DBOperation::opUpdateItem; 1319 } 1320 1321 switch (aOperation->Type()) { 1322 // Operations on single keys 1323 1324 case DBOperation::opAddItem: 1325 case DBOperation::opUpdateItem: 1326 case DBOperation::opRemoveItem: 1327 // Override any existing operation for the target (=scope+key). 1328 mUpdates.InsertOrUpdate(aOperation->Target(), std::move(aOperation)); 1329 break; 1330 1331 // Clear operations 1332 1333 case DBOperation::opClear: 1334 case DBOperation::opClearMatchingOrigin: 1335 case DBOperation::opClearMatchingOriginAttributes: 1336 // Drop all update (insert/remove) operations for equivavelent or matching 1337 // scope. We do this as an optimization as well as a must based on the 1338 // logic, if we would not delete the update tasks, changes would have been 1339 // stored to the database after clear operations have been executed. 1340 for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) { 1341 const auto& pendingTask = iter.Data(); 1342 1343 if (aOperation->Type() == DBOperation::opClear && 1344 (pendingTask->OriginNoSuffix() != aOperation->OriginNoSuffix() || 1345 pendingTask->OriginSuffix() != aOperation->OriginSuffix())) { 1346 continue; 1347 } 1348 1349 if (aOperation->Type() == DBOperation::opClearMatchingOrigin && 1350 !StringBeginsWith(pendingTask->OriginNoSuffix(), 1351 aOperation->Origin())) { 1352 continue; 1353 } 1354 1355 if (aOperation->Type() == 1356 DBOperation::opClearMatchingOriginAttributes && 1357 !OriginPatternMatches(pendingTask->OriginSuffix(), 1358 aOperation->OriginPattern())) { 1359 continue; 1360 } 1361 1362 iter.Remove(); 1363 } 1364 1365 mClears.InsertOrUpdate(aOperation->Target(), std::move(aOperation)); 1366 break; 1367 1368 case DBOperation::opClearAll: 1369 // Drop simply everything, this is a super-operation. 1370 mUpdates.Clear(); 1371 mClears.Clear(); 1372 mClears.InsertOrUpdate(aOperation->Target(), std::move(aOperation)); 1373 break; 1374 1375 default: 1376 MOZ_ASSERT(false); 1377 break; 1378 } 1379 } 1380 1381 bool StorageDBThread::PendingOperations::Prepare() { 1382 // Called under the lock 1383 1384 // First collect clear operations and then updates, we can 1385 // do this since whenever a clear operation for a scope is 1386 // scheduled, we drop all updates matching that scope. So, 1387 // all scope-related update operations we have here now were 1388 // scheduled after the clear operations. 1389 for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) { 1390 mExecList.AppendElement(std::move(iter.Data())); 1391 } 1392 mClears.Clear(); 1393 1394 for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) { 1395 mExecList.AppendElement(std::move(iter.Data())); 1396 } 1397 mUpdates.Clear(); 1398 1399 return !!mExecList.Length(); 1400 } 1401 1402 nsresult StorageDBThread::PendingOperations::Execute(StorageDBThread* aThread) { 1403 // Called outside the lock 1404 1405 mozStorageTransaction transaction(aThread->mWorkerConnection, false); 1406 1407 nsresult rv = transaction.Start(); 1408 if (NS_FAILED(rv)) { 1409 return rv; 1410 } 1411 1412 for (uint32_t i = 0; i < mExecList.Length(); ++i) { 1413 const auto& task = mExecList[i]; 1414 rv = task->Perform(aThread); 1415 if (NS_FAILED(rv)) { 1416 return rv; 1417 } 1418 } 1419 1420 rv = transaction.Commit(); 1421 if (NS_FAILED(rv)) { 1422 return rv; 1423 } 1424 1425 return NS_OK; 1426 } 1427 1428 bool StorageDBThread::PendingOperations::Finalize(nsresult aRv) { 1429 // Called under the lock 1430 1431 // The list is kept on a failure to retry it 1432 if (NS_FAILED(aRv)) { 1433 // XXX Followup: we may try to reopen the database and flush these 1434 // pending tasks, however testing showed that even though I/O is actually 1435 // broken some amount of operations is left in sqlite+system buffers and 1436 // seems like successfully flushed to disk. 1437 // Tested by removing a flash card and disconnecting from network while 1438 // using a network drive on Windows system. 1439 NS_WARNING("Flush operation on localStorage database failed"); 1440 1441 ++mFlushFailureCount; 1442 1443 return mFlushFailureCount >= 5; 1444 } 1445 1446 mFlushFailureCount = 0; 1447 mExecList.Clear(); 1448 return true; 1449 } 1450 1451 namespace { 1452 1453 bool FindPendingClearForOrigin( 1454 const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix, 1455 StorageDBThread::DBOperation* aPendingOperation) { 1456 if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearAll) { 1457 return true; 1458 } 1459 1460 if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClear && 1461 aOriginNoSuffix == aPendingOperation->OriginNoSuffix() && 1462 aOriginSuffix == aPendingOperation->OriginSuffix()) { 1463 return true; 1464 } 1465 1466 if (aPendingOperation->Type() == 1467 StorageDBThread::DBOperation::opClearMatchingOrigin && 1468 StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) { 1469 return true; 1470 } 1471 1472 if (aPendingOperation->Type() == 1473 StorageDBThread::DBOperation::opClearMatchingOriginAttributes && 1474 OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) { 1475 return true; 1476 } 1477 1478 return false; 1479 } 1480 1481 } // namespace 1482 1483 bool StorageDBThread::PendingOperations::IsOriginClearPending( 1484 const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const { 1485 // Called under the lock 1486 1487 for (const auto& clear : mClears.Values()) { 1488 if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, 1489 clear.get())) { 1490 return true; 1491 } 1492 } 1493 1494 for (uint32_t i = 0; i < mExecList.Length(); ++i) { 1495 if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, 1496 mExecList[i].get())) { 1497 return true; 1498 } 1499 } 1500 1501 return false; 1502 } 1503 1504 namespace { 1505 1506 bool FindPendingUpdateForOrigin( 1507 const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix, 1508 StorageDBThread::DBOperation* aPendingOperation) { 1509 if ((aPendingOperation->Type() == StorageDBThread::DBOperation::opAddItem || 1510 aPendingOperation->Type() == 1511 StorageDBThread::DBOperation::opUpdateItem || 1512 aPendingOperation->Type() == 1513 StorageDBThread::DBOperation::opRemoveItem) && 1514 aOriginNoSuffix == aPendingOperation->OriginNoSuffix() && 1515 aOriginSuffix == aPendingOperation->OriginSuffix()) { 1516 return true; 1517 } 1518 1519 return false; 1520 } 1521 1522 } // namespace 1523 1524 bool StorageDBThread::PendingOperations::IsOriginUpdatePending( 1525 const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const { 1526 // Called under the lock 1527 1528 for (const auto& update : mUpdates.Values()) { 1529 if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, 1530 update.get())) { 1531 return true; 1532 } 1533 } 1534 1535 for (uint32_t i = 0; i < mExecList.Length(); ++i) { 1536 if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, 1537 mExecList[i].get())) { 1538 return true; 1539 } 1540 } 1541 1542 return false; 1543 } 1544 1545 nsresult StorageDBThread::InitHelper::SyncDispatchAndReturnProfilePath( 1546 nsAString& aProfilePath) { 1547 ::mozilla::ipc::AssertIsOnBackgroundThread(); 1548 1549 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); 1550 1551 mozilla::MutexAutoLock autolock(mMutex); 1552 while (mWaiting) { 1553 mCondVar.Wait(); 1554 } 1555 1556 if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) { 1557 return mMainThreadResultCode; 1558 } 1559 1560 aProfilePath = mProfilePath; 1561 return NS_OK; 1562 } 1563 1564 NS_IMETHODIMP 1565 StorageDBThread::InitHelper::Run() { 1566 MOZ_ASSERT(NS_IsMainThread()); 1567 1568 nsresult rv = GetProfilePath(mProfilePath); 1569 if (NS_WARN_IF(NS_FAILED(rv))) { 1570 mMainThreadResultCode = rv; 1571 } 1572 1573 mozilla::MutexAutoLock lock(mMutex); 1574 MOZ_ASSERT(mWaiting); 1575 1576 mWaiting = false; 1577 mCondVar.Notify(); 1578 1579 return NS_OK; 1580 } 1581 1582 NS_IMETHODIMP 1583 StorageDBThread::NoteBackgroundThreadRunnable::Run() { 1584 MOZ_ASSERT(NS_IsMainThread()); 1585 1586 StorageObserver* observer = StorageObserver::Self(); 1587 MOZ_ASSERT(observer); 1588 1589 observer->NoteBackgroundThread(mPrivateBrowsingId, mOwningThread); 1590 1591 return NS_OK; 1592 } 1593 1594 NS_IMETHODIMP 1595 StorageDBThread::ShutdownRunnable::Run() { 1596 if (NS_IsMainThread()) { 1597 mDone = true; 1598 1599 return NS_OK; 1600 } 1601 1602 ::mozilla::ipc::AssertIsOnBackgroundThread(); 1603 MOZ_RELEASE_ASSERT(mPrivateBrowsingId < kPrivateBrowsingIdCount); 1604 1605 StorageDBThread*& storageThread = sStorageThread[mPrivateBrowsingId]; 1606 if (storageThread) { 1607 sStorageThreadDown[mPrivateBrowsingId] = true; 1608 1609 storageThread->Shutdown(); 1610 1611 delete storageThread; 1612 storageThread = nullptr; 1613 } 1614 1615 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); 1616 1617 return NS_OK; 1618 } 1619 1620 } // namespace mozilla::dom