Manager.cpp (77853B)
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 "mozilla/dom/cache/Manager.h" 8 9 #include "QuotaClientImpl.h" 10 #include "Types.h" 11 #include "mozStorageHelper.h" 12 #include "mozilla/AppShutdown.h" 13 #include "mozilla/Assertions.h" 14 #include "mozilla/AutoRestore.h" 15 #include "mozilla/Mutex.h" 16 #include "mozilla/StaticMutex.h" 17 #include "mozilla/StaticPtr.h" 18 #include "mozilla/dom/cache/CacheTypes.h" 19 #include "mozilla/dom/cache/Context.h" 20 #include "mozilla/dom/cache/DBAction.h" 21 #include "mozilla/dom/cache/DBSchema.h" 22 #include "mozilla/dom/cache/FileUtils.h" 23 #include "mozilla/dom/cache/ManagerId.h" 24 #include "mozilla/dom/cache/SavedTypes.h" 25 #include "mozilla/dom/cache/StreamList.h" 26 #include "mozilla/dom/cache/Types.h" 27 #include "mozilla/dom/quota/Client.h" 28 #include "mozilla/dom/quota/ClientDirectoryLock.h" 29 #include "mozilla/dom/quota/ClientImpl.h" 30 #include "mozilla/dom/quota/QuotaManager.h" 31 #include "mozilla/dom/quota/StringifyUtils.h" 32 #include "mozilla/ipc/BackgroundParent.h" 33 #include "nsID.h" 34 #include "nsIFile.h" 35 #include "nsIInputStream.h" 36 #include "nsIThread.h" 37 #include "nsIUUIDGenerator.h" 38 #include "nsTObserverArray.h" 39 #include "nsThreadUtils.h" 40 41 namespace mozilla::dom::cache { 42 43 using mozilla::dom::quota::ClientDirectoryLock; 44 using mozilla::dom::quota::CloneFileAndAppend; 45 46 namespace { 47 48 /** 49 * Note: The aCommitHook argument will be invoked while a lock is held. Callers 50 * should be careful not to pass a hook that might lock on something else and 51 * trigger a deadlock. 52 */ 53 template <typename Callable> 54 nsresult MaybeUpdatePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn, 55 const int64_t aIncreaseSize, 56 const int64_t aDecreaseSize, 57 Callable aCommitHook) { 58 MOZ_ASSERT(!NS_IsMainThread()); 59 MOZ_DIAGNOSTIC_ASSERT(aBaseDir); 60 MOZ_DIAGNOSTIC_ASSERT(aConn); 61 MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0); 62 MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0); 63 64 RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get(); 65 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient); 66 67 QM_TRY(MOZ_TO_RESULT(cacheQuotaClient->MaybeUpdatePaddingFileInternal( 68 *aBaseDir, *aConn, aIncreaseSize, aDecreaseSize, aCommitHook))); 69 70 return NS_OK; 71 } 72 73 Maybe<CipherKey> GetOrCreateCipherKey(NotNull<Context*> aContext, 74 const nsID& aBodyId, bool aCreate) { 75 const auto& maybeMetadata = aContext->MaybeCacheDirectoryMetadataRef(); 76 MOZ_DIAGNOSTIC_ASSERT(maybeMetadata); 77 78 auto privateOrigin = maybeMetadata->mIsPrivate; 79 if (!privateOrigin) { 80 return Nothing{}; 81 } 82 83 nsCString bodyIdStr{aBodyId.ToString().get()}; 84 85 auto& cipherKeyManager = aContext->MutableCipherKeyManagerRef(); 86 87 return aCreate ? Some(cipherKeyManager.Ensure(bodyIdStr)) 88 : cipherKeyManager.Get(bodyIdStr); 89 } 90 91 // An Action that is executed when a Context is first created. It ensures that 92 // the directory and database are setup properly. This lets other actions 93 // not worry about these details. 94 class SetupAction final : public SyncDBAction { 95 public: 96 SetupAction() : SyncDBAction(DBAction::Create) {} 97 98 virtual nsresult RunSyncWithDBOnTarget( 99 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 100 mozIStorageConnection* aConn) override { 101 MOZ_DIAGNOSTIC_ASSERT(aDBDir); 102 103 QM_TRY(MOZ_TO_RESULT(BodyCreateDir(*aDBDir))); 104 105 // executes in its own transaction 106 QM_TRY(MOZ_TO_RESULT(db::CreateOrMigrateSchema(*aDBDir, *aConn))); 107 108 // If the Context marker file exists, then the last session was 109 // not cleanly shutdown. In these cases sqlite will ensure that 110 // the database is valid, but we might still orphan data. Both 111 // Cache objects and body files can be referenced by DOM objects 112 // after they are "removed" from their parent. So we need to 113 // look and see if any of these late access objects have been 114 // orphaned. 115 // 116 // Note, this must be done after any schema version updates to 117 // ensure our DBSchema methods work correctly. 118 if (MarkerFileExists(aDirectoryMetadata)) { 119 NS_WARNING("Cache not shutdown cleanly! Cleaning up stale data..."); 120 mozStorageTransaction trans(aConn, false, 121 mozIStorageConnection::TRANSACTION_IMMEDIATE); 122 123 QM_TRY(MOZ_TO_RESULT(trans.Start())); 124 125 // Clean up orphaned Cache objects 126 QM_TRY_INSPECT(const auto& orphanedCacheIdList, 127 db::FindOrphanedCacheIds(*aConn)); 128 129 QM_TRY_INSPECT( 130 const CheckedInt64& overallDeletedPaddingSize, 131 Reduce( 132 orphanedCacheIdList, CheckedInt64(0), 133 [aConn, &aDirectoryMetadata, &aDBDir]( 134 CheckedInt64 oldValue, const Maybe<const CacheId&>& element) 135 -> Result<CheckedInt64, nsresult> { 136 QM_TRY_INSPECT(const auto& deletionInfo, 137 db::DeleteCacheId(*aConn, *element)); 138 139 QM_TRY(MOZ_TO_RESULT( 140 BodyDeleteFiles(aDirectoryMetadata, *aDBDir, 141 deletionInfo.mDeletedBodyIdList))); 142 143 if (deletionInfo.mDeletedPaddingSize > 0) { 144 DecreaseUsageForDirectoryMetadata( 145 aDirectoryMetadata, deletionInfo.mDeletedPaddingSize); 146 } 147 148 return oldValue + deletionInfo.mDeletedPaddingSize; 149 })); 150 151 // Clean up orphaned body objects. 152 QM_TRY_UNWRAP(auto knownBodyIds, db::GetKnownBodyIds(*aConn)); 153 154 // Note that this causes a scan of all cached files. See bug 1952550 that 155 // wants to reduce the probability to find the marker file above. 156 QM_TRY(MOZ_TO_RESULT( 157 BodyDeleteOrphanedFiles(aDirectoryMetadata, *aDBDir, knownBodyIds))); 158 159 // Commit() explicitly here, because we want to ensure the padding file 160 // has the correct content. 161 // We'll restore padding file below, so just warn here if failure happens. 162 // 163 // XXX Before, if MaybeUpdatePaddingFile failed but we didn't enter the if 164 // body below, we would have propagated the MaybeUpdatePaddingFile 165 // failure, but if we entered it and RestorePaddingFile succeeded, we 166 // would have returned NS_OK. Now, we will never propagate a 167 // MaybeUpdatePaddingFile failure. 168 QM_WARNONLY_TRY(QM_TO_RESULT( 169 MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0, 170 overallDeletedPaddingSize.value(), 171 [&trans]() { return trans.Commit(); }))); 172 } 173 174 if (DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::TMP_FILE) || 175 !DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::FILE)) { 176 QM_TRY(MOZ_TO_RESULT(RestorePaddingFile(aDBDir, aConn))); 177 } 178 179 return NS_OK; 180 } 181 }; 182 183 // ---------------------------------------------------------------------------- 184 185 // Action that is executed when we determine that content has stopped using 186 // a body file that has been orphaned. 187 class DeleteOrphanedBodyAction final : public Action { 188 public: 189 using DeletedBodyIdList = AutoTArray<nsID, 64>; 190 191 explicit DeleteOrphanedBodyAction(DeletedBodyIdList&& aDeletedBodyIdList) 192 : mDeletedBodyIdList(std::move(aDeletedBodyIdList)) {} 193 194 explicit DeleteOrphanedBodyAction(const nsID& aBodyId) 195 : mDeletedBodyIdList{aBodyId} {} 196 197 void RunOnTarget(SafeRefPtr<Resolver> aResolver, 198 const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata, 199 Data*, 200 const Maybe<CipherKey>& /*aMaybeCipherKey*/) override { 201 MOZ_DIAGNOSTIC_ASSERT(aResolver); 202 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata); 203 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir); 204 205 // Note that since DeleteOrphanedBodyAction isn't used while the context is 206 // being initialized, we don't need to check for cancellation here. 207 208 const auto resolve = [&aResolver](const nsresult rv) { 209 aResolver->Resolve(rv); 210 }; 211 212 QM_TRY_INSPECT(const auto& dbDir, 213 CloneFileAndAppend(*aDirectoryMetadata->mDir, u"cache"_ns), 214 QM_VOID, resolve); 215 216 QM_TRY(MOZ_TO_RESULT(BodyDeleteFiles(*aDirectoryMetadata, *dbDir, 217 mDeletedBodyIdList)), 218 QM_VOID, resolve); 219 220 aResolver->Resolve(NS_OK); 221 } 222 223 private: 224 DeletedBodyIdList mDeletedBodyIdList; 225 }; 226 227 bool IsHeadRequest(const CacheRequest& aRequest, 228 const CacheQueryParams& aParams) { 229 return !aParams.ignoreMethod() && 230 aRequest.method().LowerCaseEqualsLiteral("head"); 231 } 232 233 bool IsHeadRequest(const Maybe<CacheRequest>& aRequest, 234 const CacheQueryParams& aParams) { 235 if (aRequest.isSome()) { 236 return !aParams.ignoreMethod() && 237 aRequest.ref().method().LowerCaseEqualsLiteral("head"); 238 } 239 return false; 240 } 241 242 auto MatchByCacheId(CacheId aCacheId) { 243 return [aCacheId](const auto& entry) { return entry.mCacheId == aCacheId; }; 244 } 245 246 auto MatchByBodyId(const nsID& aBodyId) { 247 return [&aBodyId](const auto& entry) { return entry.mBodyId == aBodyId; }; 248 } 249 250 } // namespace 251 252 // ---------------------------------------------------------------------------- 253 254 // Singleton class to track Manager instances and ensure there is only 255 // one for each unique ManagerId. 256 class Manager::Factory { 257 public: 258 friend class StaticAutoPtr<Manager::Factory>; 259 260 static Result<SafeRefPtr<Manager>, nsresult> AcquireCreateIfNonExistent( 261 const SafeRefPtr<ManagerId>& aManagerId) { 262 mozilla::ipc::AssertIsOnBackgroundThread(); 263 264 // If we get here during/after quota manager shutdown, we bail out. 265 MOZ_ASSERT(AppShutdown::GetCurrentShutdownPhase() < 266 ShutdownPhase::AppShutdownQM); 267 if (AppShutdown::GetCurrentShutdownPhase() >= 268 ShutdownPhase::AppShutdownQM) { 269 NS_WARNING( 270 "Attempt to AcquireCreateIfNonExistent a Manager during QM " 271 "shutdown."); 272 return Err(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); 273 } 274 275 // Ensure there is a factory instance. This forces the Acquire() call 276 // below to use the same factory. 277 QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance())); 278 279 SafeRefPtr<Manager> ref = Acquire(*aManagerId); 280 if (!ref) { 281 // TODO: replace this with a thread pool (bug 1119864) 282 // XXX Can't use QM_TRY_INSPECT because that causes a clang-plugin 283 // error of the NoNewThreadsChecker. 284 nsCOMPtr<nsIThread> ioThread; 285 QM_TRY(MOZ_TO_RESULT( 286 NS_NewNamedThread("DOMCacheThread", getter_AddRefs(ioThread)))); 287 288 ref = MakeSafeRefPtr<Manager>(aManagerId.clonePtr(), ioThread, 289 ConstructorGuard{}); 290 291 // There may be an old manager for this origin in the process of 292 // cleaning up. We need to tell the new manager about this so 293 // that it won't actually start until the old manager is done. 294 const SafeRefPtr<Manager> oldManager = Acquire(*aManagerId, Closing); 295 ref->Init(oldManager.maybeDeref()); 296 297 MOZ_ASSERT(!sFactory->mManagerList.Contains(ref)); 298 sFactory->mManagerList.AppendElement( 299 WrapNotNullUnchecked(ref.unsafeGetRawPtr())); 300 } 301 302 return ref; 303 } 304 305 static void Remove(Manager& aManager) { 306 mozilla::ipc::AssertIsOnBackgroundThread(); 307 MOZ_DIAGNOSTIC_ASSERT(sFactory); 308 309 MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(&aManager)); 310 311 // This might both happen in late shutdown such that this event 312 // is executed even after the QuotaManager singleton passed away 313 // or if the QuotaManager has not yet been created. 314 quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep( 315 quota::Client::DOMCACHE, "Manager removed"_ns); 316 317 // clean up the factory singleton if there are no more managers 318 MaybeDestroyInstance(); 319 } 320 321 static void Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) { 322 mozilla::ipc::AssertIsOnBackgroundThread(); 323 324 AbortMatching([&aDirectoryLockIds](const auto& manager) { 325 // Check if the Manager holds an acquired DirectoryLock. Origin clearing 326 // can't be blocked by this Manager if there is no acquired DirectoryLock. 327 // If there is an acquired DirectoryLock, check if the table contains the 328 // lock for the Manager. 329 return Client::IsLockForObjectAcquiredAndContainedInLockTable( 330 manager, aDirectoryLockIds); 331 }); 332 } 333 334 static void AbortAll() { 335 mozilla::ipc::AssertIsOnBackgroundThread(); 336 337 AbortMatching([](const auto&) { return true; }); 338 } 339 340 static void ShutdownAll() { 341 mozilla::ipc::AssertIsOnBackgroundThread(); 342 343 if (!sFactory) { 344 return; 345 } 346 347 MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty()); 348 349 { 350 // Note that we are synchronously calling shutdown code here. If any 351 // of the shutdown code synchronously decides to delete the Factory 352 // we need to delay that delete until the end of this method. 353 AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown); 354 sFactory->mInSyncAbortOrShutdown = true; 355 356 for (const auto& manager : sFactory->mManagerList.ForwardRange()) { 357 auto pinnedManager = 358 SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}}; 359 pinnedManager->Shutdown(); 360 } 361 } 362 363 MaybeDestroyInstance(); 364 } 365 366 static bool IsShutdownAllComplete() { 367 mozilla::ipc::AssertIsOnBackgroundThread(); 368 return !sFactory; 369 } 370 371 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 372 static void RecordMayNotDeleteCSCP( 373 mozilla::ipc::ActorId aCacheStreamControlParentId) { 374 if (sFactory) { 375 sFactory->mPotentiallyUnreleasedCSCP.AppendElement( 376 aCacheStreamControlParentId); 377 } 378 } 379 380 static void RecordHaveDeletedCSCP( 381 mozilla::ipc::ActorId aCacheStreamControlParentId) { 382 if (sFactory) { 383 sFactory->mPotentiallyUnreleasedCSCP.RemoveElement( 384 aCacheStreamControlParentId); 385 } 386 } 387 #endif 388 static nsCString GetShutdownStatus() { 389 mozilla::ipc::AssertIsOnBackgroundThread(); 390 391 nsCString data; 392 393 if (sFactory && !sFactory->mManagerList.IsEmpty()) { 394 data.Append( 395 "ManagerList: "_ns + 396 IntToCString(static_cast<uint64_t>(sFactory->mManagerList.Length())) + 397 kStringifyStartSet); 398 399 for (const auto& manager : sFactory->mManagerList.NonObservingRange()) { 400 manager->Stringify(data); 401 } 402 403 data.Append(kStringifyEndSet); 404 if (sFactory->mPotentiallyUnreleasedCSCP.Length() > 0) { 405 data.Append( 406 "There have been CSCP instances whose" 407 "Send__delete__ might not have freed them."); 408 } 409 } 410 411 return data; 412 } 413 414 private: 415 Factory() : mInSyncAbortOrShutdown(false) { 416 MOZ_COUNT_CTOR(cache::Manager::Factory); 417 } 418 419 ~Factory() { 420 MOZ_COUNT_DTOR(cache::Manager::Factory); 421 MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty()); 422 MOZ_DIAGNOSTIC_ASSERT(!mInSyncAbortOrShutdown); 423 } 424 425 static nsresult MaybeCreateInstance() { 426 mozilla::ipc::AssertIsOnBackgroundThread(); 427 428 if (!sFactory) { 429 // We cannot use ClearOnShutdown() here because we're not on the main 430 // thread. Instead, we delete sFactory in Factory::Remove() after the 431 // last manager is removed. ShutdownObserver ensures this happens 432 // before shutdown. 433 sFactory = new Factory(); 434 } 435 436 // Never return sFactory to code outside Factory. We need to delete it 437 // out from under ourselves just before we return from Remove(). This 438 // would be (even more) dangerous if other code had a pointer to the 439 // factory itself. 440 441 return NS_OK; 442 } 443 444 static void MaybeDestroyInstance() { 445 mozilla::ipc::AssertIsOnBackgroundThread(); 446 MOZ_DIAGNOSTIC_ASSERT(sFactory); 447 448 // If the factory is is still in use then we cannot delete yet. This 449 // could be due to managers still existing or because we are in the 450 // middle of aborting or shutting down. We need to be careful not to delete 451 // ourself synchronously during shutdown. 452 if (!sFactory->mManagerList.IsEmpty() || sFactory->mInSyncAbortOrShutdown) { 453 return; 454 } 455 456 sFactory = nullptr; 457 } 458 459 static SafeRefPtr<Manager> Acquire(const ManagerId& aManagerId, 460 State aState = Open) { 461 mozilla::ipc::AssertIsOnBackgroundThread(); 462 463 QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()), nullptr); 464 465 // Iterate in reverse to find the most recent, matching Manager. This 466 // is important when looking for a Closing Manager. If a new Manager 467 // chains to an old Manager we want it to be the most recent one. 468 const auto range = Reversed(sFactory->mManagerList.NonObservingRange()); 469 const auto foundIt = std::find_if( 470 range.begin(), range.end(), [aState, &aManagerId](const auto& manager) { 471 return aState == manager->GetState() && 472 *manager->mManagerId == aManagerId; 473 }); 474 return foundIt != range.end() 475 ? SafeRefPtr{foundIt->get(), AcquireStrongRefFromRawPtr{}} 476 : nullptr; 477 } 478 479 template <typename Condition> 480 static void AbortMatching(const Condition& aCondition) { 481 mozilla::ipc::AssertIsOnBackgroundThread(); 482 483 if (!sFactory) { 484 return; 485 } 486 487 MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty()); 488 489 { 490 // Note that we are synchronously calling abort code here. If any 491 // of the shutdown code synchronously decides to delete the Factory 492 // we need to delay that delete until the end of this method. 493 AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown); 494 sFactory->mInSyncAbortOrShutdown = true; 495 496 for (const auto& manager : sFactory->mManagerList.ForwardRange()) { 497 if (aCondition(*manager)) { 498 auto pinnedManager = 499 SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}}; 500 pinnedManager->Abort(); 501 } 502 } 503 } 504 505 MaybeDestroyInstance(); 506 } 507 508 // Singleton created on demand and deleted when last Manager is cleared 509 // in Remove(). 510 // PBackground thread only. 511 static StaticAutoPtr<Factory> sFactory; 512 513 // Weak references as we don't want to keep Manager objects alive forever. 514 // When a Manager is destroyed it calls Factory::Remove() to clear itself. 515 // PBackground thread only. 516 nsTObserverArray<NotNull<Manager*>> mManagerList; 517 518 // This flag is set when we are looping through the list and calling Abort() 519 // or Shutdown() on each Manager. We need to be careful not to synchronously 520 // trigger the deletion of the factory while still executing this loop. 521 bool mInSyncAbortOrShutdown; 522 523 nsTArray<mozilla::ipc::ActorId> mPotentiallyUnreleasedCSCP; 524 }; 525 526 // static 527 StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory; 528 529 // ---------------------------------------------------------------------------- 530 531 // Abstract class to help implement the various Actions. The vast majority 532 // of Actions are synchronous and need to report back to a Listener on the 533 // Manager. 534 class Manager::BaseAction : public SyncDBAction { 535 protected: 536 BaseAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId) 537 : SyncDBAction(DBAction::Existing), 538 mManager(std::move(aManager)), 539 mListenerId(aListenerId) {} 540 541 virtual void Complete(Listener* aListener, ErrorResult&& aRv) = 0; 542 543 virtual void CompleteOnInitiatingThread(nsresult aRv) override { 544 NS_ASSERT_OWNINGTHREAD(Manager::BaseAction); 545 Listener* listener = mManager->GetListener(mListenerId); 546 if (listener) { 547 Complete(listener, ErrorResult(aRv)); 548 } 549 550 // ensure we release the manager on the initiating thread 551 mManager = nullptr; 552 } 553 554 SafeRefPtr<Manager> mManager; 555 const ListenerId mListenerId; 556 }; 557 558 // ---------------------------------------------------------------------------- 559 560 // Action that is executed when we determine that content has stopped using 561 // a Cache object that has been orphaned. 562 class Manager::DeleteOrphanedCacheAction final : public SyncDBAction { 563 public: 564 DeleteOrphanedCacheAction(SafeRefPtr<Manager> aManager, CacheId aCacheId) 565 : SyncDBAction(DBAction::Existing), 566 mManager(std::move(aManager)), 567 mCacheId(aCacheId) {} 568 569 virtual nsresult RunSyncWithDBOnTarget( 570 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 571 mozIStorageConnection* aConn) override { 572 mDirectoryMetadata.emplace(aDirectoryMetadata); 573 574 mozStorageTransaction trans(aConn, false, 575 mozIStorageConnection::TRANSACTION_IMMEDIATE); 576 577 QM_TRY(MOZ_TO_RESULT(trans.Start())); 578 579 QM_TRY_UNWRAP(mDeletionInfo, db::DeleteCacheId(*aConn, mCacheId)); 580 581 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile( 582 aDBDir, aConn, /* aIncreaceSize */ 0, mDeletionInfo.mDeletedPaddingSize, 583 [&trans]() mutable { return trans.Commit(); }))); 584 585 return NS_OK; 586 } 587 588 virtual void CompleteOnInitiatingThread(nsresult aRv) override { 589 // If the transaction fails, we shouldn't delete the body files and decrease 590 // their padding size. 591 if (NS_FAILED(aRv)) { 592 mDeletionInfo.mDeletedBodyIdList.Clear(); 593 mDeletionInfo.mDeletedPaddingSize = 0; 594 } 595 596 mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList); 597 598 if (mDeletionInfo.mDeletedPaddingSize > 0) { 599 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata, 600 mDeletionInfo.mDeletedPaddingSize); 601 } 602 603 // ensure we release the manager on the initiating thread 604 mManager = nullptr; 605 } 606 607 private: 608 SafeRefPtr<Manager> mManager; 609 const CacheId mCacheId; 610 DeletionInfo mDeletionInfo; 611 Maybe<CacheDirectoryMetadata> mDirectoryMetadata; 612 }; 613 614 // ---------------------------------------------------------------------------- 615 616 class Manager::CacheMatchAction final : public Manager::BaseAction { 617 public: 618 CacheMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 619 CacheId aCacheId, const CacheMatchArgs& aArgs, 620 SafeRefPtr<StreamList> aStreamList) 621 : BaseAction(std::move(aManager), aListenerId), 622 mCacheId(aCacheId), 623 mArgs(aArgs), 624 mStreamList(std::move(aStreamList)), 625 mFoundResponse(false) {} 626 627 virtual nsresult RunSyncWithDBOnTarget( 628 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 629 mozIStorageConnection* aConn) override { 630 MOZ_DIAGNOSTIC_ASSERT(aDBDir); 631 632 QM_TRY_INSPECT( 633 const auto& maybeResponse, 634 db::CacheMatch(*aConn, mCacheId, mArgs.request(), mArgs.params())); 635 636 mFoundResponse = maybeResponse.isSome(); 637 if (mFoundResponse) { 638 mResponse = std::move(maybeResponse.ref()); 639 } 640 641 if (!mFoundResponse || !mResponse.mHasBodyId || 642 IsHeadRequest(mArgs.request(), mArgs.params())) { 643 mResponse.mHasBodyId = false; 644 return NS_OK; 645 } 646 647 const auto& bodyId = mResponse.mBodyId; 648 649 nsCOMPtr<nsIInputStream> stream; 650 if (mArgs.openMode() == OpenMode::Eager) { 651 QM_TRY_UNWRAP( 652 stream, 653 BodyOpen(aDirectoryMetadata, *aDBDir, bodyId, 654 GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId, 655 /* aCreate */ false))); 656 } 657 658 // If we entered shutdown on the main thread while we were doing IO, 659 // bail out now. 660 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { 661 if (stream) { 662 stream->Close(); 663 } 664 return NS_ERROR_ABORT; 665 } 666 667 mStreamList->Add(mResponse.mBodyId, std::move(stream)); 668 669 return NS_OK; 670 } 671 672 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 673 if (!mFoundResponse) { 674 aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing())); 675 } else { 676 mStreamList->Activate(mCacheId); 677 aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()), 678 mResponse, *mStreamList); 679 } 680 mStreamList = nullptr; 681 } 682 683 virtual bool MatchesCacheId(CacheId aCacheId) const override { 684 return aCacheId == mCacheId; 685 } 686 687 private: 688 const CacheId mCacheId; 689 const CacheMatchArgs mArgs; 690 SafeRefPtr<StreamList> mStreamList; 691 bool mFoundResponse; 692 SavedResponse mResponse; 693 }; 694 695 // ---------------------------------------------------------------------------- 696 697 class Manager::CacheMatchAllAction final : public Manager::BaseAction { 698 public: 699 CacheMatchAllAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 700 CacheId aCacheId, const CacheMatchAllArgs& aArgs, 701 SafeRefPtr<StreamList> aStreamList) 702 : BaseAction(std::move(aManager), aListenerId), 703 mCacheId(aCacheId), 704 mArgs(aArgs), 705 mStreamList(std::move(aStreamList)) {} 706 707 virtual nsresult RunSyncWithDBOnTarget( 708 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 709 mozIStorageConnection* aConn) override { 710 MOZ_DIAGNOSTIC_ASSERT(aDBDir); 711 712 QM_TRY_UNWRAP(mSavedResponses, 713 db::CacheMatchAll(*aConn, mCacheId, mArgs.maybeRequest(), 714 mArgs.params())); 715 716 for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) { 717 if (!mSavedResponses[i].mHasBodyId || 718 IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) { 719 mSavedResponses[i].mHasBodyId = false; 720 continue; 721 } 722 723 const auto& bodyId = mSavedResponses[i].mBodyId; 724 725 nsCOMPtr<nsIInputStream> stream; 726 if (mArgs.openMode() == OpenMode::Eager) { 727 QM_TRY_UNWRAP(stream, 728 BodyOpen(aDirectoryMetadata, *aDBDir, bodyId, 729 GetOrCreateCipherKey( 730 WrapNotNull(mManager->mContext), bodyId, 731 /* aCreate */ false))); 732 } 733 734 // If we entered shutdown on the main thread while we were doing IO, 735 // bail out now. 736 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { 737 if (stream) { 738 stream->Close(); 739 } 740 return NS_ERROR_ABORT; 741 } 742 743 mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream)); 744 } 745 746 return NS_OK; 747 } 748 749 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 750 mStreamList->Activate(mCacheId); 751 aListener->OnOpComplete(std::move(aRv), CacheMatchAllResult(), 752 mSavedResponses, *mStreamList); 753 mStreamList = nullptr; 754 } 755 756 virtual bool MatchesCacheId(CacheId aCacheId) const override { 757 return aCacheId == mCacheId; 758 } 759 760 private: 761 const CacheId mCacheId; 762 const CacheMatchAllArgs mArgs; 763 SafeRefPtr<StreamList> mStreamList; 764 nsTArray<SavedResponse> mSavedResponses; 765 }; 766 767 // ---------------------------------------------------------------------------- 768 769 // This is the most complex Action. It puts a request/response pair into the 770 // Cache. It does not complete until all of the body data has been saved to 771 // disk. This means its an asynchronous Action. 772 class Manager::CachePutAllAction final : public DBAction { 773 public: 774 CachePutAllAction( 775 SafeRefPtr<Manager> aManager, ListenerId aListenerId, CacheId aCacheId, 776 const nsTArray<CacheRequestResponse>& aPutList, 777 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList, 778 const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList) 779 : DBAction(DBAction::Existing), 780 mManager(std::move(aManager)), 781 mListenerId(aListenerId), 782 mCacheId(aCacheId), 783 mList(aPutList.Length()), 784 mExpectedAsyncCopyCompletions(1), 785 mAsyncResult(NS_OK), 786 mMutex("cache::Manager::CachePutAllAction"), 787 mUpdatedPaddingSize(0), 788 mDeletedPaddingSize(0) { 789 MOZ_DIAGNOSTIC_ASSERT(!aPutList.IsEmpty()); 790 MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aRequestStreamList.Length()); 791 MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aResponseStreamList.Length()); 792 793 for (uint32_t i = 0; i < aPutList.Length(); ++i) { 794 Entry* entry = mList.AppendElement(); 795 entry->mRequest = aPutList[i].request(); 796 entry->mRequestStream = aRequestStreamList[i]; 797 entry->mResponse = aPutList[i].response(); 798 entry->mResponseStream = aResponseStreamList[i]; 799 } 800 } 801 802 private: 803 ~CachePutAllAction() = default; 804 805 virtual void RunWithDBOnTarget( 806 SafeRefPtr<Resolver> aResolver, 807 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 808 mozIStorageConnection* aConn) override { 809 MOZ_DIAGNOSTIC_ASSERT(aResolver); 810 MOZ_DIAGNOSTIC_ASSERT(aDBDir); 811 MOZ_DIAGNOSTIC_ASSERT(aConn); 812 MOZ_DIAGNOSTIC_ASSERT(!mResolver); 813 MOZ_DIAGNOSTIC_ASSERT(!mDBDir); 814 MOZ_DIAGNOSTIC_ASSERT(!mConn); 815 816 MOZ_DIAGNOSTIC_ASSERT(!mTarget); 817 mTarget = GetCurrentSerialEventTarget(); 818 MOZ_DIAGNOSTIC_ASSERT(mTarget); 819 820 // We should be pre-initialized to expect one async completion. This is 821 // the "manual" completion we call at the end of this method in all 822 // cases. 823 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions == 1); 824 825 mResolver = std::move(aResolver); 826 mDBDir = aDBDir; 827 mConn = aConn; 828 mDirectoryMetadata.emplace(aDirectoryMetadata); 829 830 // File bodies are streamed to disk via asynchronous copying. Start 831 // this copying now. Each copy will eventually result in a call 832 // to OnAsyncCopyComplete(). 833 const nsresult rv = [this, &aDirectoryMetadata]() -> nsresult { 834 QM_TRY(CollectEachInRange( 835 mList, [this, &aDirectoryMetadata](auto& entry) -> nsresult { 836 QM_TRY(MOZ_TO_RESULT( 837 StartStreamCopy(aDirectoryMetadata, entry, RequestStream, 838 &mExpectedAsyncCopyCompletions))); 839 840 QM_TRY(MOZ_TO_RESULT( 841 StartStreamCopy(aDirectoryMetadata, entry, ResponseStream, 842 &mExpectedAsyncCopyCompletions))); 843 844 return NS_OK; 845 })); 846 847 return NS_OK; 848 }(); 849 850 // Always call OnAsyncCopyComplete() manually here. This covers the 851 // case where there is no async copying and also reports any startup 852 // errors correctly. If we hit an error, then OnAsyncCopyComplete() 853 // will cancel any async copying. 854 OnAsyncCopyComplete(rv); 855 } 856 857 // Called once for each asynchronous file copy whether it succeeds or 858 // fails. If a file copy is canceled, it still calls this method with 859 // an error code. 860 void OnAsyncCopyComplete(nsresult aRv) { 861 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 862 MOZ_DIAGNOSTIC_ASSERT(mConn); 863 MOZ_DIAGNOSTIC_ASSERT(mResolver); 864 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions > 0); 865 866 // Explicitly check for cancellation here to catch a race condition. 867 // Consider: 868 // 869 // 1) NS_AsyncCopy() executes on IO thread, but has not saved its 870 // copy context yet. 871 // 2) CancelAllStreamCopying() occurs on PBackground thread 872 // 3) Copy context from (1) is saved on IO thread. 873 // 874 // Checking for cancellation here catches this condition when we 875 // first call OnAsyncCopyComplete() manually from RunWithDBOnTarget(). 876 // 877 // This explicit cancellation check also handles the case where we 878 // are canceled just after all stream copying completes. We should 879 // abort the synchronous DB operations in this case if we have not 880 // started them yet. 881 if (NS_SUCCEEDED(aRv) && IsCanceled()) { 882 aRv = NS_ERROR_ABORT; 883 } 884 885 // If any of the async copies fail, we need to still wait for them all to 886 // complete. Cancel any other streams still working and remember the 887 // error. All canceled streams will call OnAsyncCopyComplete(). 888 if (NS_FAILED(aRv) && NS_SUCCEEDED(mAsyncResult)) { 889 CancelAllStreamCopying(); 890 mAsyncResult = aRv; 891 } 892 893 // Check to see if async copying is still on-going. If so, then simply 894 // return for now. We must wait for a later OnAsyncCopyComplete() call. 895 mExpectedAsyncCopyCompletions -= 1; 896 if (mExpectedAsyncCopyCompletions > 0) { 897 return; 898 } 899 900 // We have finished with all async copying. Indicate this by clearing all 901 // our copy contexts. 902 { 903 MutexAutoLock lock(mMutex); 904 mCopyContextList.Clear(); 905 } 906 907 // An error occurred while async copying. Terminate the Action. 908 // DoResolve() will clean up any files we may have written. 909 if (NS_FAILED(mAsyncResult)) { 910 DoResolve(mAsyncResult); 911 return; 912 } 913 914 mozStorageTransaction trans(mConn, false, 915 mozIStorageConnection::TRANSACTION_IMMEDIATE); 916 917 QM_TRY(MOZ_TO_RESULT(trans.Start()), QM_VOID); 918 919 const nsresult rv = [this, &trans]() -> nsresult { 920 QM_TRY(CollectEachInRange(mList, [this](Entry& e) -> nsresult { 921 if (e.mRequestStream) { 922 QM_TRY_UNWRAP(int64_t bodyDiskSize, 923 BodyFinalizeWrite(*mDBDir, e.mRequestBodyId)); 924 e.mRequest.bodyDiskSize() = bodyDiskSize; 925 } else { 926 e.mRequest.bodyDiskSize() = 0; 927 } 928 if (e.mResponseStream) { 929 // Gerenate padding size for opaque response if needed. 930 if (e.mResponse.type() == ResponseType::Opaque) { 931 // It'll generate padding if we've not set it yet. 932 QM_TRY(MOZ_TO_RESULT(BodyMaybeUpdatePaddingSize( 933 *mDirectoryMetadata, *mDBDir, e.mResponseBodyId, 934 e.mResponse.paddingInfo(), &e.mResponse.paddingSize()))); 935 936 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - e.mResponse.paddingSize() >= 937 mUpdatedPaddingSize); 938 mUpdatedPaddingSize += e.mResponse.paddingSize(); 939 } 940 941 QM_TRY_UNWRAP(int64_t bodyDiskSize, 942 BodyFinalizeWrite(*mDBDir, e.mResponseBodyId)); 943 e.mResponse.bodyDiskSize() = bodyDiskSize; 944 } else { 945 e.mResponse.bodyDiskSize() = 0; 946 } 947 948 QM_TRY_UNWRAP( 949 auto deletionInfo, 950 db::CachePut(*mConn, mCacheId, e.mRequest, 951 e.mRequestStream ? &e.mRequestBodyId : nullptr, 952 e.mResponse, 953 e.mResponseStream ? &e.mResponseBodyId : nullptr)); 954 955 const int64_t deletedPaddingSize = deletionInfo.mDeletedPaddingSize; 956 mDeletedBodyIdList = std::move(deletionInfo.mDeletedBodyIdList); 957 958 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - mDeletedPaddingSize >= 959 deletedPaddingSize); 960 mDeletedPaddingSize += deletedPaddingSize; 961 962 return NS_OK; 963 })); 964 965 // Update padding file when it's necessary 966 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile( 967 mDBDir, mConn, mUpdatedPaddingSize, mDeletedPaddingSize, 968 [&trans]() mutable { return trans.Commit(); }))); 969 970 return NS_OK; 971 }(); 972 973 DoResolve(rv); 974 } 975 976 virtual void CompleteOnInitiatingThread(nsresult aRv) override { 977 NS_ASSERT_OWNINGTHREAD(Action); 978 979 for (uint32_t i = 0; i < mList.Length(); ++i) { 980 mList[i].mRequestStream = nullptr; 981 mList[i].mResponseStream = nullptr; 982 } 983 984 // If the transaction fails, we shouldn't delete the body files and decrease 985 // their padding size. 986 if (NS_FAILED(aRv)) { 987 mDeletedBodyIdList.Clear(); 988 mDeletedPaddingSize = 0; 989 } 990 991 mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList); 992 993 if (mDeletedPaddingSize > 0) { 994 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata, 995 mDeletedPaddingSize); 996 } 997 998 Listener* listener = mManager->GetListener(mListenerId); 999 mManager = nullptr; 1000 if (listener) { 1001 listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult()); 1002 } 1003 } 1004 1005 virtual void CancelOnInitiatingThread() override { 1006 NS_ASSERT_OWNINGTHREAD(Action); 1007 Action::CancelOnInitiatingThread(); 1008 CancelAllStreamCopying(); 1009 } 1010 1011 virtual bool MatchesCacheId(CacheId aCacheId) const override { 1012 NS_ASSERT_OWNINGTHREAD(Action); 1013 return aCacheId == mCacheId; 1014 } 1015 1016 struct Entry { 1017 CacheRequest mRequest; 1018 nsCOMPtr<nsIInputStream> mRequestStream; 1019 nsID mRequestBodyId{}; 1020 nsCOMPtr<nsISupports> mRequestCopyContext; 1021 1022 CacheResponse mResponse; 1023 nsCOMPtr<nsIInputStream> mResponseStream; 1024 nsID mResponseBodyId{}; 1025 nsCOMPtr<nsISupports> mResponseCopyContext; 1026 }; 1027 1028 enum StreamId { RequestStream, ResponseStream }; 1029 1030 nsresult StartStreamCopy(const CacheDirectoryMetadata& aDirectoryMetadata, 1031 Entry& aEntry, StreamId aStreamId, 1032 uint32_t* aCopyCountOut) { 1033 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 1034 MOZ_DIAGNOSTIC_ASSERT(aCopyCountOut); 1035 1036 if (IsCanceled()) { 1037 return NS_ERROR_ABORT; 1038 } 1039 1040 MOZ_DIAGNOSTIC_ASSERT(aStreamId == RequestStream || 1041 aStreamId == ResponseStream); 1042 1043 const auto& source = aStreamId == RequestStream ? aEntry.mRequestStream 1044 : aEntry.mResponseStream; 1045 1046 if (!source) { 1047 return NS_OK; 1048 } 1049 QM_TRY_INSPECT(const auto& idGen, 1050 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIUUIDGenerator>, 1051 MOZ_SELECT_OVERLOAD(do_GetService), 1052 "@mozilla.org/uuid-generator;1")); 1053 1054 nsID bodyId{}; 1055 QM_TRY(MOZ_TO_RESULT(idGen->GenerateUUIDInPlace(&bodyId))); 1056 1057 Maybe<CipherKey> maybeKey = 1058 GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId, 1059 /* aCreate */ true); 1060 1061 QM_TRY_INSPECT( 1062 const auto& copyContext, 1063 BodyStartWriteStream(aDirectoryMetadata, *mDBDir, bodyId, maybeKey, 1064 *source, this, AsyncCopyCompleteFunc)); 1065 1066 if (aStreamId == RequestStream) { 1067 aEntry.mRequestBodyId = bodyId; 1068 } else { 1069 aEntry.mResponseBodyId = bodyId; 1070 } 1071 1072 mBodyIdWrittenList.AppendElement(bodyId); 1073 1074 if (copyContext) { 1075 MutexAutoLock lock(mMutex); 1076 mCopyContextList.AppendElement(copyContext); 1077 } 1078 1079 *aCopyCountOut += 1; 1080 1081 return NS_OK; 1082 } 1083 1084 void CancelAllStreamCopying() { 1085 // May occur on either owning thread or target thread 1086 MutexAutoLock lock(mMutex); 1087 for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) { 1088 MOZ_DIAGNOSTIC_ASSERT(mCopyContextList[i]); 1089 BodyCancelWrite(*mCopyContextList[i]); 1090 } 1091 mCopyContextList.Clear(); 1092 } 1093 1094 static void AsyncCopyCompleteFunc(void* aClosure, nsresult aRv) { 1095 // May be on any thread, including STS event target. 1096 MOZ_DIAGNOSTIC_ASSERT(aClosure); 1097 // Weak ref as we are guaranteed to the action is alive until 1098 // CompleteOnInitiatingThread is called. 1099 CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure); 1100 action->CallOnAsyncCopyCompleteOnTargetThread(aRv); 1101 } 1102 1103 void CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv) { 1104 // May be on any thread, including STS event target. Non-owning runnable 1105 // here since we are guaranteed the Action will survive until 1106 // CompleteOnInitiatingThread is called. 1107 nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod<nsresult>( 1108 "dom::cache::Manager::CachePutAllAction::OnAsyncCopyComplete", this, 1109 &CachePutAllAction::OnAsyncCopyComplete, aRv); 1110 MOZ_ALWAYS_SUCCEEDS( 1111 mTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL)); 1112 } 1113 1114 void DoResolve(nsresult aRv) { 1115 MOZ_ASSERT(mTarget->IsOnCurrentThread()); 1116 1117 // DoResolve() must not be called until all async copying has completed. 1118 #ifdef DEBUG 1119 { 1120 MutexAutoLock lock(mMutex); 1121 MOZ_ASSERT(mCopyContextList.IsEmpty()); 1122 } 1123 #endif 1124 1125 // Clean up any files we might have written before hitting the error. 1126 if (NS_FAILED(aRv)) { 1127 BodyDeleteFiles(*mDirectoryMetadata, *mDBDir, mBodyIdWrittenList); 1128 if (mUpdatedPaddingSize > 0) { 1129 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata, 1130 mUpdatedPaddingSize); 1131 } 1132 } 1133 1134 // Must be released on the target thread where it was opened. 1135 mConn = nullptr; 1136 1137 // Drop our ref to the target thread as we are done with this thread. 1138 // Also makes our thread assertions catch any incorrect method calls 1139 // after resolve. 1140 mTarget = nullptr; 1141 1142 // Make sure to de-ref the resolver per the Action API contract. 1143 SafeRefPtr<Action::Resolver> resolver = std::move(mResolver); 1144 resolver->Resolve(aRv); 1145 } 1146 1147 // initiating thread only 1148 SafeRefPtr<Manager> mManager; 1149 const ListenerId mListenerId; 1150 1151 // Set on initiating thread, read on target thread. State machine guarantees 1152 // these are not modified while being read by the target thread. 1153 const CacheId mCacheId; 1154 nsTArray<Entry> mList; 1155 uint32_t mExpectedAsyncCopyCompletions; 1156 1157 // target thread only 1158 SafeRefPtr<Resolver> mResolver; 1159 nsCOMPtr<nsIFile> mDBDir; 1160 nsCOMPtr<mozIStorageConnection> mConn; 1161 nsCOMPtr<nsISerialEventTarget> mTarget; 1162 nsresult mAsyncResult; 1163 nsTArray<nsID> mBodyIdWrittenList; 1164 1165 // Written to on target thread, accessed on initiating thread after target 1166 // thread activity is guaranteed complete 1167 nsTArray<nsID> mDeletedBodyIdList; 1168 1169 // accessed from any thread while mMutex locked 1170 Mutex mMutex MOZ_UNANNOTATED; 1171 nsTArray<nsCOMPtr<nsISupports>> mCopyContextList; 1172 1173 Maybe<CacheDirectoryMetadata> mDirectoryMetadata; 1174 // Track how much pad amount has been added for new entries so that it can be 1175 // removed if an error occurs. 1176 int64_t mUpdatedPaddingSize; 1177 // Track any pad amount associated with overwritten entries. 1178 int64_t mDeletedPaddingSize; 1179 }; 1180 1181 // ---------------------------------------------------------------------------- 1182 1183 class Manager::CacheDeleteAction final : public Manager::BaseAction { 1184 public: 1185 CacheDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 1186 CacheId aCacheId, const CacheDeleteArgs& aArgs) 1187 : BaseAction(std::move(aManager), aListenerId), 1188 mCacheId(aCacheId), 1189 mArgs(aArgs), 1190 mSuccess(false) {} 1191 1192 virtual nsresult RunSyncWithDBOnTarget( 1193 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 1194 mozIStorageConnection* aConn) override { 1195 mDirectoryMetadata.emplace(aDirectoryMetadata); 1196 1197 mozStorageTransaction trans(aConn, false, 1198 mozIStorageConnection::TRANSACTION_IMMEDIATE); 1199 1200 QM_TRY(MOZ_TO_RESULT(trans.Start())); 1201 1202 QM_TRY_UNWRAP( 1203 auto maybeDeletionInfo, 1204 db::CacheDelete(*aConn, mCacheId, mArgs.request(), mArgs.params())); 1205 1206 mSuccess = maybeDeletionInfo.isSome(); 1207 if (mSuccess) { 1208 mDeletionInfo = std::move(maybeDeletionInfo.ref()); 1209 } 1210 1211 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile( 1212 aDBDir, aConn, /* aIncreaceSize */ 0, 1213 mDeletionInfo.mDeletedPaddingSize, 1214 [&trans]() mutable { return trans.Commit(); })), 1215 QM_PROPAGATE, [this](const nsresult) { mSuccess = false; }); 1216 1217 return NS_OK; 1218 } 1219 1220 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 1221 // If the transaction fails, we shouldn't delete the body files and decrease 1222 // their padding size. 1223 if (aRv.Failed()) { 1224 mDeletionInfo.mDeletedBodyIdList.Clear(); 1225 mDeletionInfo.mDeletedPaddingSize = 0; 1226 } 1227 1228 mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList); 1229 1230 if (mDeletionInfo.mDeletedPaddingSize > 0) { 1231 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata, 1232 mDeletionInfo.mDeletedPaddingSize); 1233 } 1234 1235 aListener->OnOpComplete(std::move(aRv), CacheDeleteResult(mSuccess)); 1236 } 1237 1238 virtual bool MatchesCacheId(CacheId aCacheId) const override { 1239 return aCacheId == mCacheId; 1240 } 1241 1242 private: 1243 const CacheId mCacheId; 1244 const CacheDeleteArgs mArgs; 1245 bool mSuccess; 1246 DeletionInfo mDeletionInfo; 1247 Maybe<CacheDirectoryMetadata> mDirectoryMetadata; 1248 }; 1249 1250 // ---------------------------------------------------------------------------- 1251 1252 class Manager::CacheKeysAction final : public Manager::BaseAction { 1253 public: 1254 CacheKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 1255 CacheId aCacheId, const CacheKeysArgs& aArgs, 1256 SafeRefPtr<StreamList> aStreamList) 1257 : BaseAction(std::move(aManager), aListenerId), 1258 mCacheId(aCacheId), 1259 mArgs(aArgs), 1260 mStreamList(std::move(aStreamList)) {} 1261 1262 virtual nsresult RunSyncWithDBOnTarget( 1263 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 1264 mozIStorageConnection* aConn) override { 1265 MOZ_DIAGNOSTIC_ASSERT(aDBDir); 1266 1267 QM_TRY_UNWRAP( 1268 mSavedRequests, 1269 db::CacheKeys(*aConn, mCacheId, mArgs.maybeRequest(), mArgs.params())); 1270 1271 for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) { 1272 if (!mSavedRequests[i].mHasBodyId || 1273 IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) { 1274 mSavedRequests[i].mHasBodyId = false; 1275 continue; 1276 } 1277 1278 const auto& bodyId = mSavedRequests[i].mBodyId; 1279 1280 nsCOMPtr<nsIInputStream> stream; 1281 if (mArgs.openMode() == OpenMode::Eager) { 1282 QM_TRY_UNWRAP(stream, 1283 BodyOpen(aDirectoryMetadata, *aDBDir, bodyId, 1284 GetOrCreateCipherKey( 1285 WrapNotNull(mManager->mContext), bodyId, 1286 /* aCreate */ false))); 1287 } 1288 1289 // If we entered shutdown on the main thread while we were doing IO, 1290 // bail out now. 1291 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { 1292 if (stream) { 1293 stream->Close(); 1294 } 1295 return NS_ERROR_ABORT; 1296 } 1297 1298 mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream)); 1299 } 1300 1301 return NS_OK; 1302 } 1303 1304 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 1305 mStreamList->Activate(mCacheId); 1306 aListener->OnOpComplete(std::move(aRv), CacheKeysResult(), mSavedRequests, 1307 *mStreamList); 1308 mStreamList = nullptr; 1309 } 1310 1311 virtual bool MatchesCacheId(CacheId aCacheId) const override { 1312 return aCacheId == mCacheId; 1313 } 1314 1315 private: 1316 const CacheId mCacheId; 1317 const CacheKeysArgs mArgs; 1318 SafeRefPtr<StreamList> mStreamList; 1319 nsTArray<SavedRequest> mSavedRequests; 1320 }; 1321 1322 // ---------------------------------------------------------------------------- 1323 1324 class Manager::StorageMatchAction final : public Manager::BaseAction { 1325 public: 1326 StorageMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 1327 Namespace aNamespace, const StorageMatchArgs& aArgs, 1328 SafeRefPtr<StreamList> aStreamList) 1329 : BaseAction(std::move(aManager), aListenerId), 1330 mNamespace(aNamespace), 1331 mArgs(aArgs), 1332 mStreamList(std::move(aStreamList)), 1333 mFoundResponse(false) {} 1334 1335 virtual nsresult RunSyncWithDBOnTarget( 1336 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 1337 mozIStorageConnection* aConn) override { 1338 MOZ_DIAGNOSTIC_ASSERT(aDBDir); 1339 1340 auto maybeResponse = 1341 db::StorageMatch(*aConn, mNamespace, mArgs.request(), mArgs.params()); 1342 if (NS_WARN_IF(maybeResponse.isErr())) { 1343 return maybeResponse.unwrapErr(); 1344 } 1345 1346 mFoundResponse = maybeResponse.inspect().isSome(); 1347 if (mFoundResponse) { 1348 mSavedResponse = maybeResponse.unwrap().ref(); 1349 } 1350 1351 if (!mFoundResponse || !mSavedResponse.mHasBodyId || 1352 IsHeadRequest(mArgs.request(), mArgs.params())) { 1353 mSavedResponse.mHasBodyId = false; 1354 return NS_OK; 1355 } 1356 1357 const auto& bodyId = mSavedResponse.mBodyId; 1358 1359 nsCOMPtr<nsIInputStream> stream; 1360 if (mArgs.openMode() == OpenMode::Eager) { 1361 QM_TRY_UNWRAP( 1362 stream, 1363 BodyOpen(aDirectoryMetadata, *aDBDir, bodyId, 1364 GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId, 1365 /* aCreate */ false))); 1366 } 1367 1368 // If we entered shutdown on the main thread while we were doing IO, 1369 // bail out now. 1370 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) { 1371 if (stream) { 1372 stream->Close(); 1373 } 1374 return NS_ERROR_ABORT; 1375 } 1376 1377 mStreamList->Add(mSavedResponse.mBodyId, std::move(stream)); 1378 1379 return NS_OK; 1380 } 1381 1382 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 1383 if (!mFoundResponse) { 1384 aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing())); 1385 } else { 1386 mStreamList->Activate(mSavedResponse.mCacheId); 1387 aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()), 1388 mSavedResponse, *mStreamList); 1389 } 1390 mStreamList = nullptr; 1391 } 1392 1393 private: 1394 const Namespace mNamespace; 1395 const StorageMatchArgs mArgs; 1396 SafeRefPtr<StreamList> mStreamList; 1397 bool mFoundResponse; 1398 SavedResponse mSavedResponse; 1399 }; 1400 1401 // ---------------------------------------------------------------------------- 1402 1403 class Manager::StorageHasAction final : public Manager::BaseAction { 1404 public: 1405 StorageHasAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 1406 Namespace aNamespace, const StorageHasArgs& aArgs) 1407 : BaseAction(std::move(aManager), aListenerId), 1408 mNamespace(aNamespace), 1409 mArgs(aArgs), 1410 mCacheFound(false) {} 1411 1412 virtual nsresult RunSyncWithDBOnTarget( 1413 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 1414 mozIStorageConnection* aConn) override { 1415 QM_TRY_INSPECT(const auto& maybeCacheId, 1416 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key())); 1417 1418 mCacheFound = maybeCacheId.isSome(); 1419 1420 return NS_OK; 1421 } 1422 1423 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 1424 aListener->OnOpComplete(std::move(aRv), StorageHasResult(mCacheFound)); 1425 } 1426 1427 private: 1428 const Namespace mNamespace; 1429 const StorageHasArgs mArgs; 1430 bool mCacheFound; 1431 }; 1432 1433 // ---------------------------------------------------------------------------- 1434 1435 class Manager::StorageOpenAction final : public Manager::BaseAction { 1436 public: 1437 StorageOpenAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 1438 Namespace aNamespace, const StorageOpenArgs& aArgs) 1439 : BaseAction(std::move(aManager), aListenerId), 1440 mNamespace(aNamespace), 1441 mArgs(aArgs), 1442 mCacheId(INVALID_CACHE_ID) {} 1443 1444 virtual nsresult RunSyncWithDBOnTarget( 1445 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 1446 mozIStorageConnection* aConn) override { 1447 // Cache does not exist, create it instead 1448 mozStorageTransaction trans(aConn, false, 1449 mozIStorageConnection::TRANSACTION_IMMEDIATE); 1450 1451 QM_TRY(MOZ_TO_RESULT(trans.Start())); 1452 1453 // Look for existing cache 1454 QM_TRY_INSPECT(const auto& maybeCacheId, 1455 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key())); 1456 1457 if (maybeCacheId.isSome()) { 1458 mCacheId = maybeCacheId.ref(); 1459 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID); 1460 return NS_OK; 1461 } 1462 1463 QM_TRY_UNWRAP(mCacheId, db::CreateCacheId(*aConn)); 1464 1465 QM_TRY(MOZ_TO_RESULT( 1466 db::StoragePutCache(*aConn, mNamespace, mArgs.key(), mCacheId))); 1467 1468 QM_TRY(MOZ_TO_RESULT(trans.Commit())); 1469 1470 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID); 1471 return NS_OK; 1472 } 1473 1474 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 1475 MOZ_DIAGNOSTIC_ASSERT(aRv.Failed() || mCacheId != INVALID_CACHE_ID); 1476 aListener->OnOpComplete( 1477 std::move(aRv), StorageOpenResult((PCacheParent*)nullptr, mNamespace), 1478 mCacheId); 1479 } 1480 1481 private: 1482 const Namespace mNamespace; 1483 const StorageOpenArgs mArgs; 1484 CacheId mCacheId; 1485 }; 1486 1487 // ---------------------------------------------------------------------------- 1488 1489 class Manager::StorageDeleteAction final : public Manager::BaseAction { 1490 public: 1491 StorageDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 1492 Namespace aNamespace, const StorageDeleteArgs& aArgs) 1493 : BaseAction(std::move(aManager), aListenerId), 1494 mNamespace(aNamespace), 1495 mArgs(aArgs), 1496 mCacheDeleted(false), 1497 mCacheId(INVALID_CACHE_ID) {} 1498 1499 virtual nsresult RunSyncWithDBOnTarget( 1500 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 1501 mozIStorageConnection* aConn) override { 1502 mozStorageTransaction trans(aConn, false, 1503 mozIStorageConnection::TRANSACTION_IMMEDIATE); 1504 1505 QM_TRY(MOZ_TO_RESULT(trans.Start())); 1506 1507 QM_TRY_INSPECT(const auto& maybeCacheId, 1508 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key())); 1509 1510 if (maybeCacheId.isNothing()) { 1511 mCacheDeleted = false; 1512 return NS_OK; 1513 } 1514 mCacheId = maybeCacheId.ref(); 1515 1516 // Don't delete the removing padding size here, we'll delete it on 1517 // DeleteOrphanedCacheAction. 1518 QM_TRY( 1519 MOZ_TO_RESULT(db::StorageForgetCache(*aConn, mNamespace, mArgs.key()))); 1520 1521 QM_TRY(MOZ_TO_RESULT(trans.Commit())); 1522 1523 mCacheDeleted = true; 1524 return NS_OK; 1525 } 1526 1527 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 1528 if (mCacheDeleted) { 1529 // If content is referencing this cache, mark it orphaned to be 1530 // deleted later. 1531 if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) { 1532 // no outstanding references, delete immediately 1533 const auto pinnedContext = 1534 SafeRefPtr{mManager->mContext, AcquireStrongRefFromRawPtr{}}; 1535 1536 if (pinnedContext->IsCanceled()) { 1537 pinnedContext->NoteOrphanedData(); 1538 } else { 1539 pinnedContext->CancelForCacheId(mCacheId); 1540 pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>( 1541 mManager.clonePtr(), mCacheId)); 1542 } 1543 } 1544 } 1545 1546 aListener->OnOpComplete(std::move(aRv), StorageDeleteResult(mCacheDeleted)); 1547 } 1548 1549 private: 1550 const Namespace mNamespace; 1551 const StorageDeleteArgs mArgs; 1552 bool mCacheDeleted; 1553 CacheId mCacheId; 1554 }; 1555 1556 // ---------------------------------------------------------------------------- 1557 1558 class Manager::StorageKeysAction final : public Manager::BaseAction { 1559 public: 1560 StorageKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 1561 Namespace aNamespace) 1562 : BaseAction(std::move(aManager), aListenerId), mNamespace(aNamespace) {} 1563 1564 virtual nsresult RunSyncWithDBOnTarget( 1565 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 1566 mozIStorageConnection* aConn) override { 1567 QM_TRY_UNWRAP(mKeys, db::StorageGetKeys(*aConn, mNamespace)); 1568 1569 return NS_OK; 1570 } 1571 1572 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 1573 if (aRv.Failed()) { 1574 mKeys.Clear(); 1575 } 1576 aListener->OnOpComplete(std::move(aRv), StorageKeysResult(mKeys)); 1577 } 1578 1579 private: 1580 const Namespace mNamespace; 1581 nsTArray<nsString> mKeys; 1582 }; 1583 1584 // ---------------------------------------------------------------------------- 1585 1586 class Manager::OpenStreamAction final : public Manager::BaseAction { 1587 public: 1588 OpenStreamAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId, 1589 InputStreamResolver&& aResolver, const nsID& aBodyId) 1590 : BaseAction(std::move(aManager), aListenerId), 1591 mResolver(std::move(aResolver)), 1592 mBodyId(aBodyId) {} 1593 1594 virtual nsresult RunSyncWithDBOnTarget( 1595 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir, 1596 mozIStorageConnection* aConn) override { 1597 MOZ_DIAGNOSTIC_ASSERT(aDBDir); 1598 1599 QM_TRY_UNWRAP( 1600 mBodyStream, 1601 BodyOpen(aDirectoryMetadata, *aDBDir, mBodyId, 1602 GetOrCreateCipherKey(WrapNotNull(mManager->mContext), mBodyId, 1603 /* aCreate */ false))); 1604 1605 return NS_OK; 1606 } 1607 1608 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { 1609 if (aRv.Failed()) { 1610 // Ignore the reason for fail and just pass a null input stream to let it 1611 // fail. 1612 aRv.SuppressException(); 1613 mResolver(nullptr); 1614 } else { 1615 mResolver(std::move(mBodyStream)); 1616 } 1617 1618 mResolver = nullptr; 1619 } 1620 1621 private: 1622 InputStreamResolver mResolver; 1623 const nsID mBodyId; 1624 nsCOMPtr<nsIInputStream> mBodyStream; 1625 }; 1626 1627 // ---------------------------------------------------------------------------- 1628 1629 // static 1630 Manager::ListenerId Manager::sNextListenerId = 0; 1631 1632 void Manager::Listener::OnOpComplete(ErrorResult&& aRv, 1633 const CacheOpResult& aResult) { 1634 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID, Nothing()); 1635 } 1636 1637 void Manager::Listener::OnOpComplete(ErrorResult&& aRv, 1638 const CacheOpResult& aResult, 1639 CacheId aOpenedCacheId) { 1640 OnOpComplete(std::move(aRv), aResult, aOpenedCacheId, Nothing()); 1641 } 1642 1643 void Manager::Listener::OnOpComplete(ErrorResult&& aRv, 1644 const CacheOpResult& aResult, 1645 const SavedResponse& aSavedResponse, 1646 StreamList& aStreamList) { 1647 AutoTArray<SavedResponse, 1> responseList; 1648 responseList.AppendElement(aSavedResponse); 1649 OnOpComplete( 1650 std::move(aRv), aResult, INVALID_CACHE_ID, 1651 Some(StreamInfo{responseList, nsTArray<SavedRequest>(), aStreamList})); 1652 } 1653 1654 void Manager::Listener::OnOpComplete( 1655 ErrorResult&& aRv, const CacheOpResult& aResult, 1656 const nsTArray<SavedResponse>& aSavedResponseList, 1657 StreamList& aStreamList) { 1658 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID, 1659 Some(StreamInfo{aSavedResponseList, nsTArray<SavedRequest>(), 1660 aStreamList})); 1661 } 1662 1663 void Manager::Listener::OnOpComplete( 1664 ErrorResult&& aRv, const CacheOpResult& aResult, 1665 const nsTArray<SavedRequest>& aSavedRequestList, StreamList& aStreamList) { 1666 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID, 1667 Some(StreamInfo{nsTArray<SavedResponse>(), aSavedRequestList, 1668 aStreamList})); 1669 } 1670 1671 // static 1672 Result<SafeRefPtr<Manager>, nsresult> Manager::AcquireCreateIfNonExistent( 1673 const SafeRefPtr<ManagerId>& aManagerId) { 1674 mozilla::ipc::AssertIsOnBackgroundThread(); 1675 return Factory::AcquireCreateIfNonExistent(aManagerId); 1676 } 1677 1678 // static 1679 void Manager::InitiateShutdown() { 1680 mozilla::ipc::AssertIsOnBackgroundThread(); 1681 1682 Factory::AbortAll(); 1683 1684 Factory::ShutdownAll(); 1685 } 1686 1687 // static 1688 bool Manager::IsShutdownAllComplete() { 1689 mozilla::ipc::AssertIsOnBackgroundThread(); 1690 1691 return Factory::IsShutdownAllComplete(); 1692 } 1693 1694 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 1695 void Manager::RecordMayNotDeleteCSCP( 1696 mozilla::ipc::ActorId aCacheStreamControlParentId) { 1697 Factory::RecordMayNotDeleteCSCP(aCacheStreamControlParentId); 1698 } 1699 1700 void Manager::RecordHaveDeletedCSCP( 1701 mozilla::ipc::ActorId aCacheStreamControlParentId) { 1702 Factory::RecordHaveDeletedCSCP(aCacheStreamControlParentId); 1703 } 1704 #endif 1705 1706 // static 1707 nsCString Manager::GetShutdownStatus() { 1708 mozilla::ipc::AssertIsOnBackgroundThread(); 1709 1710 return Factory::GetShutdownStatus(); 1711 } 1712 1713 // static 1714 void Manager::Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) { 1715 mozilla::ipc::AssertIsOnBackgroundThread(); 1716 1717 Factory::Abort(aDirectoryLockIds); 1718 } 1719 1720 // static 1721 void Manager::AbortAll() { 1722 mozilla::ipc::AssertIsOnBackgroundThread(); 1723 1724 Factory::AbortAll(); 1725 } 1726 1727 void Manager::RemoveListener(Listener* aListener) { 1728 NS_ASSERT_OWNINGTHREAD(Manager); 1729 // There may not be a listener here in the case where an actor is killed 1730 // before it can perform any actual async requests on Manager. 1731 mListeners.RemoveElement(aListener, ListenerEntryListenerComparator()); 1732 MOZ_ASSERT( 1733 !mListeners.Contains(aListener, ListenerEntryListenerComparator())); 1734 MaybeAllowContextToClose(); 1735 } 1736 1737 void Manager::RemoveContext(Context& aContext) { 1738 NS_ASSERT_OWNINGTHREAD(Manager); 1739 MOZ_DIAGNOSTIC_ASSERT(mContext); 1740 MOZ_DIAGNOSTIC_ASSERT(mContext == &aContext); 1741 1742 // Whether the Context destruction was triggered from the Manager going 1743 // idle or the underlying storage being invalidated, we should know we 1744 // are closing before the Context is destroyed. 1745 MOZ_DIAGNOSTIC_ASSERT(mState == Closing); 1746 1747 // Before forgetting the Context, check to see if we have any outstanding 1748 // cache or body objects waiting for deletion. If so, note that we've 1749 // orphaned data so it will be cleaned up on the next open. 1750 if (std::any_of( 1751 mCacheIdRefs.cbegin(), mCacheIdRefs.cend(), 1752 [](const auto& cacheIdRef) { return cacheIdRef.mOrphaned; }) || 1753 std::any_of(mBodyIdRefs.cbegin(), mBodyIdRefs.cend(), 1754 [](const auto& bodyIdRef) { return bodyIdRef.mOrphaned; })) { 1755 aContext.NoteOrphanedData(); 1756 } 1757 1758 mContext = nullptr; 1759 1760 // Once the context is gone, we can immediately remove ourself from the 1761 // Factory list. We don't need to block shutdown by staying in the list 1762 // any more. 1763 Factory::Remove(*this); 1764 } 1765 1766 void Manager::NoteClosing() { 1767 NS_ASSERT_OWNINGTHREAD(Manager); 1768 // This can be called more than once legitimately through different paths. 1769 mState = Closing; 1770 } 1771 1772 Manager::State Manager::GetState() const { 1773 NS_ASSERT_OWNINGTHREAD(Manager); 1774 return mState; 1775 } 1776 1777 void Manager::AddRefCacheId(CacheId aCacheId) { 1778 NS_ASSERT_OWNINGTHREAD(Manager); 1779 1780 const auto end = mCacheIdRefs.end(); 1781 const auto foundIt = 1782 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId)); 1783 if (foundIt != end) { 1784 foundIt->mCount += 1; 1785 return; 1786 } 1787 1788 mCacheIdRefs.AppendElement(CacheIdRefCounter{aCacheId, 1, false}); 1789 } 1790 1791 void Manager::ReleaseCacheId(CacheId aCacheId) { 1792 NS_ASSERT_OWNINGTHREAD(Manager); 1793 1794 const auto end = mCacheIdRefs.end(); 1795 const auto foundIt = 1796 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId)); 1797 if (foundIt != end) { 1798 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 1799 const uint32_t oldRef = foundIt->mCount; 1800 #endif 1801 foundIt->mCount -= 1; 1802 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef); 1803 if (foundIt->mCount == 0) { 1804 const bool orphaned = foundIt->mOrphaned; 1805 mCacheIdRefs.RemoveElementAt(foundIt); 1806 const auto pinnedContext = 1807 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 1808 // If the context is already gone, then orphan flag should have been 1809 // set in RemoveContext(). 1810 if (orphaned && pinnedContext) { 1811 if (pinnedContext->IsCanceled()) { 1812 pinnedContext->NoteOrphanedData(); 1813 } else { 1814 pinnedContext->CancelForCacheId(aCacheId); 1815 pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>( 1816 SafeRefPtrFromThis(), aCacheId)); 1817 } 1818 } 1819 } 1820 MaybeAllowContextToClose(); 1821 return; 1822 } 1823 1824 MOZ_ASSERT_UNREACHABLE("Attempt to release CacheId that is not referenced!"); 1825 } 1826 1827 void Manager::AddRefBodyId(const nsID& aBodyId) { 1828 NS_ASSERT_OWNINGTHREAD(Manager); 1829 1830 const auto end = mBodyIdRefs.end(); 1831 const auto foundIt = 1832 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId)); 1833 if (foundIt != end) { 1834 foundIt->mCount += 1; 1835 return; 1836 } 1837 1838 mBodyIdRefs.AppendElement(BodyIdRefCounter{aBodyId, 1, false}); 1839 } 1840 1841 void Manager::ReleaseBodyId(const nsID& aBodyId) { 1842 NS_ASSERT_OWNINGTHREAD(Manager); 1843 1844 const auto end = mBodyIdRefs.end(); 1845 const auto foundIt = 1846 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId)); 1847 if (foundIt != end) { 1848 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 1849 const uint32_t oldRef = foundIt->mCount; 1850 #endif 1851 foundIt->mCount -= 1; 1852 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef); 1853 if (foundIt->mCount < 1) { 1854 const bool orphaned = foundIt->mOrphaned; 1855 mBodyIdRefs.RemoveElementAt(foundIt); 1856 const auto pinnedContext = 1857 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 1858 // If the context is already gone, then orphan flag should have been 1859 // set in RemoveContext(). 1860 if (orphaned && pinnedContext) { 1861 if (pinnedContext->IsCanceled()) { 1862 pinnedContext->NoteOrphanedData(); 1863 } else { 1864 pinnedContext->Dispatch( 1865 MakeSafeRefPtr<DeleteOrphanedBodyAction>(aBodyId)); 1866 } 1867 } 1868 } 1869 MaybeAllowContextToClose(); 1870 return; 1871 } 1872 1873 MOZ_ASSERT_UNREACHABLE("Attempt to release BodyId that is not referenced!"); 1874 } 1875 1876 const ManagerId& Manager::GetManagerId() const { return *mManagerId; } 1877 1878 void Manager::AddStreamList(StreamList& aStreamList) { 1879 NS_ASSERT_OWNINGTHREAD(Manager); 1880 mStreamLists.AppendElement(WrapNotNullUnchecked(&aStreamList)); 1881 } 1882 1883 void Manager::RemoveStreamList(StreamList& aStreamList) { 1884 NS_ASSERT_OWNINGTHREAD(Manager); 1885 mStreamLists.RemoveElement(&aStreamList); 1886 } 1887 1888 void Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId, 1889 const CacheOpArgs& aOpArgs) { 1890 NS_ASSERT_OWNINGTHREAD(Manager); 1891 MOZ_DIAGNOSTIC_ASSERT(aListener); 1892 MOZ_DIAGNOSTIC_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs); 1893 1894 if (NS_WARN_IF(mState == Closing)) { 1895 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t()); 1896 return; 1897 } 1898 1899 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 1900 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled()); 1901 1902 auto action = [this, aListener, aCacheId, &aOpArgs, 1903 &pinnedContext]() -> SafeRefPtr<Action> { 1904 const ListenerId listenerId = SaveListener(aListener); 1905 1906 if (CacheOpArgs::TCacheDeleteArgs == aOpArgs.type()) { 1907 return MakeSafeRefPtr<CacheDeleteAction>(SafeRefPtrFromThis(), listenerId, 1908 aCacheId, 1909 aOpArgs.get_CacheDeleteArgs()); 1910 } 1911 1912 auto streamList = MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(), 1913 pinnedContext.clonePtr()); 1914 1915 switch (aOpArgs.type()) { 1916 case CacheOpArgs::TCacheMatchArgs: 1917 return MakeSafeRefPtr<CacheMatchAction>( 1918 SafeRefPtrFromThis(), listenerId, aCacheId, 1919 aOpArgs.get_CacheMatchArgs(), std::move(streamList)); 1920 case CacheOpArgs::TCacheMatchAllArgs: 1921 return MakeSafeRefPtr<CacheMatchAllAction>( 1922 SafeRefPtrFromThis(), listenerId, aCacheId, 1923 aOpArgs.get_CacheMatchAllArgs(), std::move(streamList)); 1924 case CacheOpArgs::TCacheKeysArgs: 1925 return MakeSafeRefPtr<CacheKeysAction>( 1926 SafeRefPtrFromThis(), listenerId, aCacheId, 1927 aOpArgs.get_CacheKeysArgs(), std::move(streamList)); 1928 default: 1929 MOZ_CRASH("Unknown Cache operation!"); 1930 } 1931 }(); 1932 1933 pinnedContext->Dispatch(std::move(action)); 1934 } 1935 1936 void Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace, 1937 const CacheOpArgs& aOpArgs) { 1938 NS_ASSERT_OWNINGTHREAD(Manager); 1939 MOZ_DIAGNOSTIC_ASSERT(aListener); 1940 1941 if (NS_WARN_IF(mState == Closing)) { 1942 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t()); 1943 return; 1944 } 1945 1946 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 1947 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled()); 1948 1949 auto action = [this, aListener, aNamespace, &aOpArgs, 1950 &pinnedContext]() -> SafeRefPtr<Action> { 1951 const ListenerId listenerId = SaveListener(aListener); 1952 1953 switch (aOpArgs.type()) { 1954 case CacheOpArgs::TStorageMatchArgs: 1955 return MakeSafeRefPtr<StorageMatchAction>( 1956 SafeRefPtrFromThis(), listenerId, aNamespace, 1957 aOpArgs.get_StorageMatchArgs(), 1958 MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(), 1959 pinnedContext.clonePtr())); 1960 case CacheOpArgs::TStorageHasArgs: 1961 return MakeSafeRefPtr<StorageHasAction>(SafeRefPtrFromThis(), 1962 listenerId, aNamespace, 1963 aOpArgs.get_StorageHasArgs()); 1964 case CacheOpArgs::TStorageOpenArgs: 1965 return MakeSafeRefPtr<StorageOpenAction>(SafeRefPtrFromThis(), 1966 listenerId, aNamespace, 1967 aOpArgs.get_StorageOpenArgs()); 1968 case CacheOpArgs::TStorageDeleteArgs: 1969 return MakeSafeRefPtr<StorageDeleteAction>( 1970 SafeRefPtrFromThis(), listenerId, aNamespace, 1971 aOpArgs.get_StorageDeleteArgs()); 1972 case CacheOpArgs::TStorageKeysArgs: 1973 return MakeSafeRefPtr<StorageKeysAction>(SafeRefPtrFromThis(), 1974 listenerId, aNamespace); 1975 default: 1976 MOZ_CRASH("Unknown CacheStorage operation!"); 1977 } 1978 }(); 1979 1980 pinnedContext->Dispatch(std::move(action)); 1981 } 1982 1983 void Manager::ExecuteOpenStream(Listener* aListener, 1984 InputStreamResolver&& aResolver, 1985 const nsID& aBodyId) { 1986 NS_ASSERT_OWNINGTHREAD(Manager); 1987 MOZ_DIAGNOSTIC_ASSERT(aListener); 1988 MOZ_DIAGNOSTIC_ASSERT(aResolver); 1989 1990 if (NS_WARN_IF(mState == Closing)) { 1991 aResolver(nullptr); 1992 return; 1993 } 1994 1995 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 1996 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled()); 1997 1998 // We save the listener simply to track the existence of the caller here. 1999 // Our returned value will really be passed to the resolver when the 2000 // operation completes. In the future we should remove the Listener 2001 // mechanism in favor of std::function or MozPromise. 2002 ListenerId listenerId = SaveListener(aListener); 2003 2004 pinnedContext->Dispatch(MakeSafeRefPtr<OpenStreamAction>( 2005 SafeRefPtrFromThis(), listenerId, std::move(aResolver), aBodyId)); 2006 } 2007 2008 void Manager::ExecutePutAll( 2009 Listener* aListener, CacheId aCacheId, 2010 const nsTArray<CacheRequestResponse>& aPutList, 2011 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList, 2012 const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList) { 2013 NS_ASSERT_OWNINGTHREAD(Manager); 2014 MOZ_DIAGNOSTIC_ASSERT(aListener); 2015 2016 if (NS_WARN_IF(mState == Closing)) { 2017 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult()); 2018 return; 2019 } 2020 2021 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 2022 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled()); 2023 2024 ListenerId listenerId = SaveListener(aListener); 2025 pinnedContext->Dispatch(MakeSafeRefPtr<CachePutAllAction>( 2026 SafeRefPtrFromThis(), listenerId, aCacheId, aPutList, aRequestStreamList, 2027 aResponseStreamList)); 2028 } 2029 2030 Manager::Manager(SafeRefPtr<ManagerId> aManagerId, nsIThread* aIOThread, 2031 const ConstructorGuard&) 2032 : mManagerId(std::move(aManagerId)), 2033 mIOThread(aIOThread), 2034 mContext(nullptr), 2035 mShuttingDown(false), 2036 mState(Open) { 2037 MOZ_DIAGNOSTIC_ASSERT(mManagerId); 2038 MOZ_DIAGNOSTIC_ASSERT(mIOThread); 2039 } 2040 2041 Manager::~Manager() { 2042 NS_ASSERT_OWNINGTHREAD(Manager); 2043 MOZ_DIAGNOSTIC_ASSERT(mState == Closing); 2044 MOZ_DIAGNOSTIC_ASSERT(!mContext); 2045 2046 nsCOMPtr<nsIThread> ioThread; 2047 mIOThread.swap(ioThread); 2048 2049 // Don't spin the event loop in the destructor waiting for the thread to 2050 // shutdown. Defer this to the main thread, instead. 2051 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod( 2052 "nsIThread::AsyncShutdown", ioThread, &nsIThread::AsyncShutdown))); 2053 } 2054 2055 void Manager::Init(Maybe<Manager&> aOldManager) { 2056 NS_ASSERT_OWNINGTHREAD(Manager); 2057 2058 // Create the context immediately. Since there can at most be one Context 2059 // per Manager now, this lets us cleanly call Factory::Remove() once the 2060 // Context goes away. 2061 SafeRefPtr<Context> ref = Context::Create( 2062 SafeRefPtrFromThis(), mIOThread, MakeSafeRefPtr<SetupAction>(), 2063 aOldManager ? SomeRef(*aOldManager->mContext) : Nothing()); 2064 mContext = ref.unsafeGetRawPtr(); 2065 } 2066 2067 void Manager::Shutdown() { 2068 NS_ASSERT_OWNINGTHREAD(Manager); 2069 2070 // Ignore duplicate attempts to shutdown. This can occur when we start 2071 // a browser initiated shutdown and then run ~Manager() which also 2072 // calls Shutdown(). 2073 if (mShuttingDown) { 2074 return; 2075 } 2076 2077 mShuttingDown = true; 2078 2079 // Note that we are closing to prevent any new requests from coming in and 2080 // creating a new Context. We must ensure all Contexts and IO operations are 2081 // complete before shutdown proceeds. 2082 NoteClosing(); 2083 2084 // If there is a context, then cancel and only note that we are done after 2085 // its cleaned up. 2086 if (mContext) { 2087 const auto pinnedContext = 2088 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 2089 pinnedContext->CancelAll(); 2090 return; 2091 } 2092 } 2093 2094 Maybe<ClientDirectoryLock&> Manager::MaybeDirectoryLockRef() const { 2095 NS_ASSERT_OWNINGTHREAD(Manager); 2096 MOZ_DIAGNOSTIC_ASSERT(mContext); 2097 2098 return mContext->MaybeDirectoryLockRef(); 2099 } 2100 2101 void Manager::Abort() { 2102 NS_ASSERT_OWNINGTHREAD(Manager); 2103 MOZ_DIAGNOSTIC_ASSERT(mContext); 2104 2105 // Note that we are closing to prevent any new requests from coming in and 2106 // creating a new Context. We must ensure all Contexts and IO operations are 2107 // complete before origin clear proceeds. 2108 NoteClosing(); 2109 2110 // Cancel and only note that we are done after the context is cleaned up. 2111 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 2112 pinnedContext->CancelAll(); 2113 } 2114 2115 Manager::ListenerId Manager::SaveListener(Listener* aListener) { 2116 NS_ASSERT_OWNINGTHREAD(Manager); 2117 2118 // Once a Listener is added, we keep a reference to it until its 2119 // removed. Since the same Listener might make multiple requests, 2120 // ensure we only have a single reference in our list. 2121 ListenerList::index_type index = 2122 mListeners.IndexOf(aListener, 0, ListenerEntryListenerComparator()); 2123 if (index != ListenerList::NoIndex) { 2124 return mListeners[index].mId; 2125 } 2126 2127 ListenerId id = sNextListenerId; 2128 sNextListenerId += 1; 2129 2130 mListeners.AppendElement(ListenerEntry(id, aListener)); 2131 return id; 2132 } 2133 2134 Manager::Listener* Manager::GetListener(ListenerId aListenerId) const { 2135 NS_ASSERT_OWNINGTHREAD(Manager); 2136 ListenerList::index_type index = 2137 mListeners.IndexOf(aListenerId, 0, ListenerEntryIdComparator()); 2138 if (index != ListenerList::NoIndex) { 2139 return mListeners[index].mListener; 2140 } 2141 2142 // This can legitimately happen if the actor is deleted while a request is 2143 // in process. For example, the child process OOMs. 2144 return nullptr; 2145 } 2146 2147 bool Manager::SetCacheIdOrphanedIfRefed(CacheId aCacheId) { 2148 NS_ASSERT_OWNINGTHREAD(Manager); 2149 2150 const auto end = mCacheIdRefs.end(); 2151 const auto foundIt = 2152 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId)); 2153 if (foundIt != end) { 2154 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0); 2155 MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned); 2156 foundIt->mOrphaned = true; 2157 return true; 2158 } 2159 2160 return false; 2161 } 2162 2163 // TODO: provide way to set body non-orphaned if its added back to a cache (bug 2164 // 1110479) 2165 2166 bool Manager::SetBodyIdOrphanedIfRefed(const nsID& aBodyId) { 2167 NS_ASSERT_OWNINGTHREAD(Manager); 2168 2169 const auto end = mBodyIdRefs.end(); 2170 const auto foundIt = 2171 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId)); 2172 if (foundIt != end) { 2173 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0); 2174 MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned); 2175 foundIt->mOrphaned = true; 2176 return true; 2177 } 2178 2179 return false; 2180 } 2181 2182 void Manager::NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList) { 2183 NS_ASSERT_OWNINGTHREAD(Manager); 2184 2185 // XXX TransformIfIntoNewArray might be generalized to allow specifying the 2186 // type of nsTArray to create, so that it can create an AutoTArray as well; an 2187 // TransformIf (without AbortOnErr) might be added, which could be used here. 2188 DeleteOrphanedBodyAction::DeletedBodyIdList deleteNowList; 2189 deleteNowList.SetCapacity(aDeletedBodyIdList.Length()); 2190 2191 std::copy_if(aDeletedBodyIdList.cbegin(), aDeletedBodyIdList.cend(), 2192 MakeBackInserter(deleteNowList), [&](const auto& deletedBodyId) { 2193 return !SetBodyIdOrphanedIfRefed(deletedBodyId); 2194 }); 2195 2196 // TODO: note that we need to check these bodies for staleness on startup (bug 2197 // 1110446) 2198 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 2199 if (!deleteNowList.IsEmpty() && pinnedContext && 2200 !pinnedContext->IsCanceled()) { 2201 pinnedContext->Dispatch( 2202 MakeSafeRefPtr<DeleteOrphanedBodyAction>(std::move(deleteNowList))); 2203 } 2204 } 2205 2206 void Manager::MaybeAllowContextToClose() { 2207 NS_ASSERT_OWNINGTHREAD(Manager); 2208 2209 // If we have an active context, but we have no more users of the Manager, 2210 // then let it shut itself down. We must wait for all possible users of 2211 // Cache state information to complete before doing this. Once we allow 2212 // the Context to close we may not reliably get notified of storage 2213 // invalidation. 2214 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}}; 2215 if (pinnedContext && mListeners.IsEmpty() && mCacheIdRefs.IsEmpty() && 2216 mBodyIdRefs.IsEmpty()) { 2217 // Mark this Manager as invalid so that it won't get used again. We don't 2218 // want to start any new operations once we allow the Context to close since 2219 // it may race with the underlying storage getting invalidated. 2220 NoteClosing(); 2221 2222 pinnedContext->AllowToClose(); 2223 } 2224 } 2225 2226 void Manager::DoStringify(nsACString& aData) { 2227 aData.Append("Manager "_ns + kStringifyStartInstance + 2228 // 2229 "Origin:"_ns + 2230 quota::AnonymizedOriginString(GetManagerId().QuotaOrigin()) + 2231 kStringifyDelimiter + 2232 // 2233 "State:"_ns + IntToCString(mState) + kStringifyDelimiter); 2234 2235 aData.AppendLiteral("Context:"); 2236 if (mContext) { 2237 mContext->Stringify(aData); 2238 } else { 2239 aData.AppendLiteral("0"); 2240 } 2241 2242 aData.Append(kStringifyEndInstance); 2243 } 2244 2245 } // namespace mozilla::dom::cache