forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			2128 lines
		
	
	
	
		
			72 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2128 lines
		
	
	
	
		
			72 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/dom/cache/Manager.h"
 | |
| 
 | |
| #include "mozilla/AppShutdown.h"
 | |
| #include "mozilla/AutoRestore.h"
 | |
| #include "mozilla/Mutex.h"
 | |
| #include "mozilla/StaticMutex.h"
 | |
| #include "mozilla/StaticPtr.h"
 | |
| #include "mozilla/Unused.h"
 | |
| #include "mozilla/dom/cache/Context.h"
 | |
| #include "mozilla/dom/cache/DBAction.h"
 | |
| #include "mozilla/dom/cache/DBSchema.h"
 | |
| #include "mozilla/dom/cache/FileUtils.h"
 | |
| #include "mozilla/dom/cache/ManagerId.h"
 | |
| #include "mozilla/dom/cache/CacheTypes.h"
 | |
| #include "mozilla/dom/cache/SavedTypes.h"
 | |
| #include "mozilla/dom/cache/StreamList.h"
 | |
| #include "mozilla/dom/cache/Types.h"
 | |
| #include "mozilla/dom/quota/Client.h"
 | |
| #include "mozilla/dom/quota/ClientImpl.h"
 | |
| #include "mozilla/dom/quota/QuotaManager.h"
 | |
| #include "mozilla/ipc/BackgroundParent.h"
 | |
| #include "mozStorageHelper.h"
 | |
| #include "nsIInputStream.h"
 | |
| #include "nsID.h"
 | |
| #include "nsIFile.h"
 | |
| #include "nsIThread.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "nsTObserverArray.h"
 | |
| #include "QuotaClientImpl.h"
 | |
| 
 | |
| namespace mozilla::dom::cache {
 | |
| 
 | |
| using mozilla::dom::quota::Client;
 | |
| using mozilla::dom::quota::CloneFileAndAppend;
 | |
| using mozilla::dom::quota::DirectoryLock;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| /**
 | |
|  * Note: The aCommitHook argument will be invoked while a lock is held. Callers
 | |
|  * should be careful not to pass a hook that might lock on something else and
 | |
|  * trigger a deadlock.
 | |
|  */
 | |
| template <typename Callable>
 | |
| nsresult MaybeUpdatePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn,
 | |
|                                 const int64_t aIncreaseSize,
 | |
|                                 const int64_t aDecreaseSize,
 | |
|                                 Callable aCommitHook) {
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aConn);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
 | |
| 
 | |
|   RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
 | |
|   MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
 | |
| 
 | |
|   QM_TRY(MOZ_TO_RESULT(cacheQuotaClient->MaybeUpdatePaddingFileInternal(
 | |
|       *aBaseDir, *aConn, aIncreaseSize, aDecreaseSize, aCommitHook)));
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // An Action that is executed when a Context is first created.  It ensures that
 | |
| // the directory and database are setup properly.  This lets other actions
 | |
| // not worry about these details.
 | |
| class SetupAction final : public SyncDBAction {
 | |
|  public:
 | |
|   SetupAction() : SyncDBAction(DBAction::Create) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(BodyCreateDir(*aDBDir)));
 | |
| 
 | |
|     // executes in its own transaction
 | |
|     QM_TRY(MOZ_TO_RESULT(db::CreateOrMigrateSchema(*aConn)));
 | |
| 
 | |
|     // If the Context marker file exists, then the last session was
 | |
|     // not cleanly shutdown.  In these cases sqlite will ensure that
 | |
|     // the database is valid, but we might still orphan data.  Both
 | |
|     // Cache objects and body files can be referenced by DOM objects
 | |
|     // after they are "removed" from their parent.  So we need to
 | |
|     // look and see if any of these late access objects have been
 | |
|     // orphaned.
 | |
|     //
 | |
|     // Note, this must be done after any schema version updates to
 | |
|     // ensure our DBSchema methods work correctly.
 | |
|     if (MarkerFileExists(aDirectoryMetadata)) {
 | |
|       NS_WARNING("Cache not shutdown cleanly! Cleaning up stale data...");
 | |
|       mozStorageTransaction trans(aConn, false,
 | |
|                                   mozIStorageConnection::TRANSACTION_IMMEDIATE);
 | |
| 
 | |
|       QM_TRY(MOZ_TO_RESULT(trans.Start()));
 | |
| 
 | |
|       // Clean up orphaned Cache objects
 | |
|       QM_TRY_INSPECT(const auto& orphanedCacheIdList,
 | |
|                      db::FindOrphanedCacheIds(*aConn));
 | |
| 
 | |
|       QM_TRY_INSPECT(
 | |
|           const CheckedInt64& overallDeletedPaddingSize,
 | |
|           Reduce(
 | |
|               orphanedCacheIdList, CheckedInt64(0),
 | |
|               [aConn, &aDirectoryMetadata, &aDBDir](
 | |
|                   CheckedInt64 oldValue, const Maybe<const CacheId&>& element)
 | |
|                   -> Result<CheckedInt64, nsresult> {
 | |
|                 QM_TRY_INSPECT(const auto& deletionInfo,
 | |
|                                db::DeleteCacheId(*aConn, *element));
 | |
| 
 | |
|                 QM_TRY(MOZ_TO_RESULT(
 | |
|                     BodyDeleteFiles(aDirectoryMetadata, *aDBDir,
 | |
|                                     deletionInfo.mDeletedBodyIdList)));
 | |
| 
 | |
|                 if (deletionInfo.mDeletedPaddingSize > 0) {
 | |
|                   DecreaseUsageForDirectoryMetadata(
 | |
|                       aDirectoryMetadata, deletionInfo.mDeletedPaddingSize);
 | |
|                 }
 | |
| 
 | |
|                 return oldValue + deletionInfo.mDeletedPaddingSize;
 | |
|               }));
 | |
| 
 | |
|       // Clean up orphaned body objects
 | |
|       QM_TRY_INSPECT(const auto& knownBodyIdList, db::GetKnownBodyIds(*aConn));
 | |
| 
 | |
|       QM_TRY(MOZ_TO_RESULT(BodyDeleteOrphanedFiles(aDirectoryMetadata, *aDBDir,
 | |
|                                                    knownBodyIdList)));
 | |
| 
 | |
|       // Commit() explicitly here, because we want to ensure the padding file
 | |
|       // has the correct content.
 | |
|       // We'll restore padding file below, so just warn here if failure happens.
 | |
|       //
 | |
|       // XXX Before, if MaybeUpdatePaddingFile failed but we didn't enter the if
 | |
|       // body below, we would have propagated the MaybeUpdatePaddingFile
 | |
|       // failure, but if we entered it and RestorePaddingFile succeeded, we
 | |
|       // would have returned NS_OK. Now, we will never propagate a
 | |
|       // MaybeUpdatePaddingFile failure.
 | |
|       QM_WARNONLY_TRY(QM_TO_RESULT(
 | |
|           MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0,
 | |
|                                  overallDeletedPaddingSize.value(),
 | |
|                                  [&trans]() { return trans.Commit(); })));
 | |
|     }
 | |
| 
 | |
|     if (DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::TMP_FILE) ||
 | |
|         !DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::FILE)) {
 | |
|       QM_TRY(MOZ_TO_RESULT(RestorePaddingFile(aDBDir, aConn)));
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // Action that is executed when we determine that content has stopped using
 | |
| // a body file that has been orphaned.
 | |
| class DeleteOrphanedBodyAction final : public Action {
 | |
|  public:
 | |
|   using DeletedBodyIdList = AutoTArray<nsID, 64>;
 | |
| 
 | |
|   explicit DeleteOrphanedBodyAction(DeletedBodyIdList&& aDeletedBodyIdList)
 | |
|       : mDeletedBodyIdList(std::move(aDeletedBodyIdList)) {}
 | |
| 
 | |
|   explicit DeleteOrphanedBodyAction(const nsID& aBodyId)
 | |
|       : mDeletedBodyIdList{aBodyId} {}
 | |
| 
 | |
|   void RunOnTarget(SafeRefPtr<Resolver> aResolver,
 | |
|                    const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
 | |
|                    Data*) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aResolver);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir);
 | |
| 
 | |
|     // Note that since DeleteOrphanedBodyAction isn't used while the context is
 | |
|     // being initialized, we don't need to check for cancellation here.
 | |
| 
 | |
|     const auto resolve = [&aResolver](const nsresult rv) {
 | |
|       aResolver->Resolve(rv);
 | |
|     };
 | |
| 
 | |
|     QM_TRY_INSPECT(const auto& dbDir,
 | |
|                    CloneFileAndAppend(*aDirectoryMetadata->mDir, u"cache"_ns),
 | |
|                    QM_VOID, resolve);
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(BodyDeleteFiles(*aDirectoryMetadata, *dbDir,
 | |
|                                          mDeletedBodyIdList)),
 | |
|            QM_VOID, resolve);
 | |
| 
 | |
|     aResolver->Resolve(NS_OK);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   DeletedBodyIdList mDeletedBodyIdList;
 | |
| };
 | |
| 
 | |
| bool IsHeadRequest(const CacheRequest& aRequest,
 | |
|                    const CacheQueryParams& aParams) {
 | |
|   return !aParams.ignoreMethod() &&
 | |
|          aRequest.method().LowerCaseEqualsLiteral("head");
 | |
| }
 | |
| 
 | |
| bool IsHeadRequest(const Maybe<CacheRequest>& aRequest,
 | |
|                    const CacheQueryParams& aParams) {
 | |
|   if (aRequest.isSome()) {
 | |
|     return !aParams.ignoreMethod() &&
 | |
|            aRequest.ref().method().LowerCaseEqualsLiteral("head");
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| auto MatchByCacheId(CacheId aCacheId) {
 | |
|   return [aCacheId](const auto& entry) { return entry.mCacheId == aCacheId; };
 | |
| }
 | |
| 
 | |
| auto MatchByBodyId(const nsID& aBodyId) {
 | |
|   return [&aBodyId](const auto& entry) { return entry.mBodyId == aBodyId; };
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // Singleton class to track Manager instances and ensure there is only
 | |
| // one for each unique ManagerId.
 | |
| class Manager::Factory {
 | |
|  public:
 | |
|   friend class StaticAutoPtr<Manager::Factory>;
 | |
| 
 | |
|   static Result<SafeRefPtr<Manager>, nsresult> AcquireCreateIfNonExistent(
 | |
|       const SafeRefPtr<ManagerId>& aManagerId) {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|     // If we get here during/after quota manager shutdown, we bail out.
 | |
|     MOZ_ASSERT(AppShutdown::GetCurrentShutdownPhase() <
 | |
|                ShutdownPhase::AppShutdownQM);
 | |
|     if (AppShutdown::GetCurrentShutdownPhase() >=
 | |
|         ShutdownPhase::AppShutdownQM) {
 | |
|       NS_WARNING(
 | |
|           "Attempt to AcquireCreateIfNonExistent a Manager during QM "
 | |
|           "shutdown.");
 | |
|       return Err(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
 | |
|     }
 | |
| 
 | |
|     // Ensure there is a factory instance.  This forces the Acquire() call
 | |
|     // below to use the same factory.
 | |
|     QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()));
 | |
| 
 | |
|     SafeRefPtr<Manager> ref = Acquire(*aManagerId);
 | |
|     if (!ref) {
 | |
|       // TODO: replace this with a thread pool (bug 1119864)
 | |
|       // XXX Can't use QM_TRY_INSPECT because that causes a clang-plugin
 | |
|       // error of the NoNewThreadsChecker.
 | |
|       nsCOMPtr<nsIThread> ioThread;
 | |
|       QM_TRY(MOZ_TO_RESULT(
 | |
|           NS_NewNamedThread("DOMCacheThread", getter_AddRefs(ioThread))));
 | |
| 
 | |
|       ref = MakeSafeRefPtr<Manager>(aManagerId.clonePtr(), ioThread,
 | |
|                                     ConstructorGuard{});
 | |
| 
 | |
|       // There may be an old manager for this origin in the process of
 | |
|       // cleaning up.  We need to tell the new manager about this so
 | |
|       // that it won't actually start until the old manager is done.
 | |
|       const SafeRefPtr<Manager> oldManager = Acquire(*aManagerId, Closing);
 | |
|       ref->Init(oldManager.maybeDeref());
 | |
| 
 | |
|       MOZ_ASSERT(!sFactory->mManagerList.Contains(ref));
 | |
|       sFactory->mManagerList.AppendElement(
 | |
|           WrapNotNullUnchecked(ref.unsafeGetRawPtr()));
 | |
|     }
 | |
| 
 | |
|     return ref;
 | |
|   }
 | |
| 
 | |
|   static void Remove(Manager& aManager) {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
|     MOZ_DIAGNOSTIC_ASSERT(sFactory);
 | |
| 
 | |
|     MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(&aManager));
 | |
| 
 | |
|     // This might both happen in late shutdown such that this event
 | |
|     // is executed even after the QuotaManager singleton passed away
 | |
|     // or if the QuotaManager has not yet been created.
 | |
|     quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
 | |
|         quota::Client::DOMCACHE, "Manager removed"_ns);
 | |
| 
 | |
|     // clean up the factory singleton if there are no more managers
 | |
|     MaybeDestroyInstance();
 | |
|   }
 | |
| 
 | |
|   static void Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|     AbortMatching([&aDirectoryLockIds](const auto& manager) {
 | |
|       // Check if the Manager holds an acquired DirectoryLock. Origin clearing
 | |
|       // can't be blocked by this Manager if there is no acquired DirectoryLock.
 | |
|       // If there is an acquired DirectoryLock, check if the table contains the
 | |
|       // lock for the Manager.
 | |
|       return Client::IsLockForObjectAcquiredAndContainedInLockTable(
 | |
|           manager, aDirectoryLockIds);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   static void AbortAll() {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|     AbortMatching([](const auto&) { return true; });
 | |
|   }
 | |
| 
 | |
|   static void ShutdownAll() {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|     if (!sFactory) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
 | |
| 
 | |
|     {
 | |
|       // Note that we are synchronously calling shutdown code here.  If any
 | |
|       // of the shutdown code synchronously decides to delete the Factory
 | |
|       // we need to delay that delete until the end of this method.
 | |
|       AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
 | |
|       sFactory->mInSyncAbortOrShutdown = true;
 | |
| 
 | |
|       for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
 | |
|         auto pinnedManager =
 | |
|             SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
 | |
|         pinnedManager->Shutdown();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     MaybeDestroyInstance();
 | |
|   }
 | |
| 
 | |
|   static bool IsShutdownAllComplete() {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
|     return !sFactory;
 | |
|   }
 | |
| 
 | |
| #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|   static void RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId) {
 | |
|     if (sFactory) {
 | |
|       sFactory->mPotentiallyUnreleasedCSCP.AppendElement(
 | |
|           aCacheStreamControlParentId);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static void RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId) {
 | |
|     if (sFactory) {
 | |
|       sFactory->mPotentiallyUnreleasedCSCP.RemoveElement(
 | |
|           aCacheStreamControlParentId);
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
|   static nsCString GetShutdownStatus() {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|     nsCString data;
 | |
| 
 | |
|     if (sFactory && !sFactory->mManagerList.IsEmpty()) {
 | |
|       data.Append(
 | |
|           "Managers: "_ns +
 | |
|           IntToCString(static_cast<uint64_t>(sFactory->mManagerList.Length())) +
 | |
|           " ("_ns);
 | |
| 
 | |
|       for (const auto& manager : sFactory->mManagerList.NonObservingRange()) {
 | |
|         data.Append(quota::AnonymizedOriginString(
 | |
|             manager->GetManagerId().QuotaOrigin()));
 | |
| 
 | |
|         data.AppendLiteral(": ");
 | |
| 
 | |
|         data.Append(manager->GetState() == State::Open ? "Open"_ns
 | |
|                                                        : "Closing"_ns);
 | |
| 
 | |
|         data.AppendLiteral(", ");
 | |
|       }
 | |
| 
 | |
|       data.AppendLiteral(" ) ");
 | |
|       if (sFactory->mPotentiallyUnreleasedCSCP.Length() > 0) {
 | |
|         data.Append(
 | |
|             "There have been CSCP instances whose"
 | |
|             "Send__delete__ might not have freed them.");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return data;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   Factory() : mInSyncAbortOrShutdown(false) {
 | |
|     MOZ_COUNT_CTOR(cache::Manager::Factory);
 | |
|   }
 | |
| 
 | |
|   ~Factory() {
 | |
|     MOZ_COUNT_DTOR(cache::Manager::Factory);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mInSyncAbortOrShutdown);
 | |
|   }
 | |
| 
 | |
|   static nsresult MaybeCreateInstance() {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|     if (!sFactory) {
 | |
|       // We cannot use ClearOnShutdown() here because we're not on the main
 | |
|       // thread.  Instead, we delete sFactory in Factory::Remove() after the
 | |
|       // last manager is removed.  ShutdownObserver ensures this happens
 | |
|       // before shutdown.
 | |
|       sFactory = new Factory();
 | |
|     }
 | |
| 
 | |
|     // Never return sFactory to code outside Factory.  We need to delete it
 | |
|     // out from under ourselves just before we return from Remove().  This
 | |
|     // would be (even more) dangerous if other code had a pointer to the
 | |
|     // factory itself.
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   static void MaybeDestroyInstance() {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
|     MOZ_DIAGNOSTIC_ASSERT(sFactory);
 | |
| 
 | |
|     // If the factory is is still in use then we cannot delete yet.  This
 | |
|     // could be due to managers still existing or because we are in the
 | |
|     // middle of aborting or shutting down.  We need to be careful not to delete
 | |
|     // ourself synchronously during shutdown.
 | |
|     if (!sFactory->mManagerList.IsEmpty() || sFactory->mInSyncAbortOrShutdown) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     sFactory = nullptr;
 | |
|   }
 | |
| 
 | |
|   static SafeRefPtr<Manager> Acquire(const ManagerId& aManagerId,
 | |
|                                      State aState = Open) {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()), nullptr);
 | |
| 
 | |
|     // Iterate in reverse to find the most recent, matching Manager.  This
 | |
|     // is important when looking for a Closing Manager.  If a new Manager
 | |
|     // chains to an old Manager we want it to be the most recent one.
 | |
|     const auto range = Reversed(sFactory->mManagerList.NonObservingRange());
 | |
|     const auto foundIt = std::find_if(
 | |
|         range.begin(), range.end(), [aState, &aManagerId](const auto& manager) {
 | |
|           return aState == manager->GetState() &&
 | |
|                  *manager->mManagerId == aManagerId;
 | |
|         });
 | |
|     return foundIt != range.end()
 | |
|                ? SafeRefPtr{foundIt->get(), AcquireStrongRefFromRawPtr{}}
 | |
|                : nullptr;
 | |
|   }
 | |
| 
 | |
|   template <typename Condition>
 | |
|   static void AbortMatching(const Condition& aCondition) {
 | |
|     mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|     if (!sFactory) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
 | |
| 
 | |
|     {
 | |
|       // Note that we are synchronously calling abort code here.  If any
 | |
|       // of the shutdown code synchronously decides to delete the Factory
 | |
|       // we need to delay that delete until the end of this method.
 | |
|       AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
 | |
|       sFactory->mInSyncAbortOrShutdown = true;
 | |
| 
 | |
|       for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
 | |
|         if (aCondition(*manager)) {
 | |
|           auto pinnedManager =
 | |
|               SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
 | |
|           pinnedManager->Abort();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     MaybeDestroyInstance();
 | |
|   }
 | |
| 
 | |
|   // Singleton created on demand and deleted when last Manager is cleared
 | |
|   // in Remove().
 | |
|   // PBackground thread only.
 | |
|   static StaticAutoPtr<Factory> sFactory;
 | |
| 
 | |
|   // Weak references as we don't want to keep Manager objects alive forever.
 | |
|   // When a Manager is destroyed it calls Factory::Remove() to clear itself.
 | |
|   // PBackground thread only.
 | |
|   nsTObserverArray<NotNull<Manager*>> mManagerList;
 | |
| 
 | |
|   // This flag is set when we are looping through the list and calling Abort()
 | |
|   // or Shutdown() on each Manager.  We need to be careful not to synchronously
 | |
|   // trigger the deletion of the factory while still executing this loop.
 | |
|   bool mInSyncAbortOrShutdown;
 | |
| 
 | |
|   nsTArray<int32_t> mPotentiallyUnreleasedCSCP;
 | |
| };
 | |
| 
 | |
| // static
 | |
| StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // Abstract class to help implement the various Actions.  The vast majority
 | |
| // of Actions are synchronous and need to report back to a Listener on the
 | |
| // Manager.
 | |
| class Manager::BaseAction : public SyncDBAction {
 | |
|  protected:
 | |
|   BaseAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId)
 | |
|       : SyncDBAction(DBAction::Existing),
 | |
|         mManager(std::move(aManager)),
 | |
|         mListenerId(aListenerId) {}
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) = 0;
 | |
| 
 | |
|   virtual void CompleteOnInitiatingThread(nsresult aRv) override {
 | |
|     NS_ASSERT_OWNINGTHREAD(Manager::BaseAction);
 | |
|     Listener* listener = mManager->GetListener(mListenerId);
 | |
|     if (listener) {
 | |
|       Complete(listener, ErrorResult(aRv));
 | |
|     }
 | |
| 
 | |
|     // ensure we release the manager on the initiating thread
 | |
|     mManager = nullptr;
 | |
|   }
 | |
| 
 | |
|   SafeRefPtr<Manager> mManager;
 | |
|   const ListenerId mListenerId;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // Action that is executed when we determine that content has stopped using
 | |
| // a Cache object that has been orphaned.
 | |
| class Manager::DeleteOrphanedCacheAction final : public SyncDBAction {
 | |
|  public:
 | |
|   DeleteOrphanedCacheAction(SafeRefPtr<Manager> aManager, CacheId aCacheId)
 | |
|       : SyncDBAction(DBAction::Existing),
 | |
|         mManager(std::move(aManager)),
 | |
|         mCacheId(aCacheId) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     mDirectoryMetadata.emplace(aDirectoryMetadata);
 | |
| 
 | |
|     mozStorageTransaction trans(aConn, false,
 | |
|                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(trans.Start()));
 | |
| 
 | |
|     QM_TRY_UNWRAP(mDeletionInfo, db::DeleteCacheId(*aConn, mCacheId));
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
 | |
|         aDBDir, aConn, /* aIncreaceSize */ 0, mDeletionInfo.mDeletedPaddingSize,
 | |
|         [&trans]() mutable { return trans.Commit(); })));
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void CompleteOnInitiatingThread(nsresult aRv) override {
 | |
|     // If the transaction fails, we shouldn't delete the body files and decrease
 | |
|     // their padding size.
 | |
|     if (NS_FAILED(aRv)) {
 | |
|       mDeletionInfo.mDeletedBodyIdList.Clear();
 | |
|       mDeletionInfo.mDeletedPaddingSize = 0;
 | |
|     }
 | |
| 
 | |
|     mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
 | |
| 
 | |
|     if (mDeletionInfo.mDeletedPaddingSize > 0) {
 | |
|       DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
 | |
|                                         mDeletionInfo.mDeletedPaddingSize);
 | |
|     }
 | |
| 
 | |
|     // ensure we release the manager on the initiating thread
 | |
|     mManager = nullptr;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   SafeRefPtr<Manager> mManager;
 | |
|   const CacheId mCacheId;
 | |
|   DeletionInfo mDeletionInfo;
 | |
|   Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::CacheMatchAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   CacheMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                    CacheId aCacheId, const CacheMatchArgs& aArgs,
 | |
|                    SafeRefPtr<StreamList> aStreamList)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mCacheId(aCacheId),
 | |
|         mArgs(aArgs),
 | |
|         mStreamList(std::move(aStreamList)),
 | |
|         mFoundResponse(false) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
 | |
| 
 | |
|     QM_TRY_INSPECT(
 | |
|         const auto& maybeResponse,
 | |
|         db::CacheMatch(*aConn, mCacheId, mArgs.request(), mArgs.params()));
 | |
| 
 | |
|     mFoundResponse = maybeResponse.isSome();
 | |
|     if (mFoundResponse) {
 | |
|       mResponse = std::move(maybeResponse.ref());
 | |
|     }
 | |
| 
 | |
|     if (!mFoundResponse || !mResponse.mHasBodyId ||
 | |
|         IsHeadRequest(mArgs.request(), mArgs.params())) {
 | |
|       mResponse.mHasBodyId = false;
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIInputStream> stream;
 | |
|     if (mArgs.openMode() == OpenMode::Eager) {
 | |
|       QM_TRY_UNWRAP(stream,
 | |
|                     BodyOpen(aDirectoryMetadata, *aDBDir, mResponse.mBodyId));
 | |
|     }
 | |
| 
 | |
|     mStreamList->Add(mResponse.mBodyId, std::move(stream));
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     if (!mFoundResponse) {
 | |
|       aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()));
 | |
|     } else {
 | |
|       mStreamList->Activate(mCacheId);
 | |
|       aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()),
 | |
|                               mResponse, *mStreamList);
 | |
|     }
 | |
|     mStreamList = nullptr;
 | |
|   }
 | |
| 
 | |
|   virtual bool MatchesCacheId(CacheId aCacheId) const override {
 | |
|     return aCacheId == mCacheId;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const CacheId mCacheId;
 | |
|   const CacheMatchArgs mArgs;
 | |
|   SafeRefPtr<StreamList> mStreamList;
 | |
|   bool mFoundResponse;
 | |
|   SavedResponse mResponse;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::CacheMatchAllAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   CacheMatchAllAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                       CacheId aCacheId, const CacheMatchAllArgs& aArgs,
 | |
|                       SafeRefPtr<StreamList> aStreamList)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mCacheId(aCacheId),
 | |
|         mArgs(aArgs),
 | |
|         mStreamList(std::move(aStreamList)) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
 | |
| 
 | |
|     QM_TRY_UNWRAP(mSavedResponses,
 | |
|                   db::CacheMatchAll(*aConn, mCacheId, mArgs.maybeRequest(),
 | |
|                                     mArgs.params()));
 | |
| 
 | |
|     for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) {
 | |
|       if (!mSavedResponses[i].mHasBodyId ||
 | |
|           IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
 | |
|         mSavedResponses[i].mHasBodyId = false;
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsIInputStream> stream;
 | |
|       if (mArgs.openMode() == OpenMode::Eager) {
 | |
|         QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
 | |
|                                        mSavedResponses[i].mBodyId));
 | |
|       }
 | |
| 
 | |
|       mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream));
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     mStreamList->Activate(mCacheId);
 | |
|     aListener->OnOpComplete(std::move(aRv), CacheMatchAllResult(),
 | |
|                             mSavedResponses, *mStreamList);
 | |
|     mStreamList = nullptr;
 | |
|   }
 | |
| 
 | |
|   virtual bool MatchesCacheId(CacheId aCacheId) const override {
 | |
|     return aCacheId == mCacheId;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const CacheId mCacheId;
 | |
|   const CacheMatchAllArgs mArgs;
 | |
|   SafeRefPtr<StreamList> mStreamList;
 | |
|   nsTArray<SavedResponse> mSavedResponses;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // This is the most complex Action.  It puts a request/response pair into the
 | |
| // Cache.  It does not complete until all of the body data has been saved to
 | |
| // disk.  This means its an asynchronous Action.
 | |
| class Manager::CachePutAllAction final : public DBAction {
 | |
|  public:
 | |
|   CachePutAllAction(
 | |
|       SafeRefPtr<Manager> aManager, ListenerId aListenerId, CacheId aCacheId,
 | |
|       const nsTArray<CacheRequestResponse>& aPutList,
 | |
|       const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
 | |
|       const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
 | |
|       : DBAction(DBAction::Existing),
 | |
|         mManager(std::move(aManager)),
 | |
|         mListenerId(aListenerId),
 | |
|         mCacheId(aCacheId),
 | |
|         mList(aPutList.Length()),
 | |
|         mExpectedAsyncCopyCompletions(1),
 | |
|         mAsyncResult(NS_OK),
 | |
|         mMutex("cache::Manager::CachePutAllAction"),
 | |
|         mUpdatedPaddingSize(0),
 | |
|         mDeletedPaddingSize(0) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!aPutList.IsEmpty());
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aRequestStreamList.Length());
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aResponseStreamList.Length());
 | |
| 
 | |
|     for (uint32_t i = 0; i < aPutList.Length(); ++i) {
 | |
|       Entry* entry = mList.AppendElement();
 | |
|       entry->mRequest = aPutList[i].request();
 | |
|       entry->mRequestStream = aRequestStreamList[i];
 | |
|       entry->mResponse = aPutList[i].response();
 | |
|       entry->mResponseStream = aResponseStreamList[i];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~CachePutAllAction() = default;
 | |
| 
 | |
|   virtual void RunWithDBOnTarget(
 | |
|       SafeRefPtr<Resolver> aResolver,
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aResolver);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aConn);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mResolver);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mDBDir);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mConn);
 | |
| 
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mTarget);
 | |
|     mTarget = GetCurrentSerialEventTarget();
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mTarget);
 | |
| 
 | |
|     // We should be pre-initialized to expect one async completion.  This is
 | |
|     // the "manual" completion we call at the end of this method in all
 | |
|     // cases.
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions == 1);
 | |
| 
 | |
|     mResolver = std::move(aResolver);
 | |
|     mDBDir = aDBDir;
 | |
|     mConn = aConn;
 | |
|     mDirectoryMetadata.emplace(aDirectoryMetadata);
 | |
| 
 | |
|     // File bodies are streamed to disk via asynchronous copying.  Start
 | |
|     // this copying now.  Each copy will eventually result in a call
 | |
|     // to OnAsyncCopyComplete().
 | |
|     const nsresult rv = [this, &aDirectoryMetadata]() -> nsresult {
 | |
|       QM_TRY(CollectEachInRange(
 | |
|           mList, [this, &aDirectoryMetadata](auto& entry) -> nsresult {
 | |
|             QM_TRY(MOZ_TO_RESULT(
 | |
|                 StartStreamCopy(aDirectoryMetadata, entry, RequestStream,
 | |
|                                 &mExpectedAsyncCopyCompletions)));
 | |
| 
 | |
|             QM_TRY(MOZ_TO_RESULT(
 | |
|                 StartStreamCopy(aDirectoryMetadata, entry, ResponseStream,
 | |
|                                 &mExpectedAsyncCopyCompletions)));
 | |
| 
 | |
|             return NS_OK;
 | |
|           }));
 | |
| 
 | |
|       return NS_OK;
 | |
|     }();
 | |
| 
 | |
|     // Always call OnAsyncCopyComplete() manually here.  This covers the
 | |
|     // case where there is no async copying and also reports any startup
 | |
|     // errors correctly.  If we hit an error, then OnAsyncCopyComplete()
 | |
|     // will cancel any async copying.
 | |
|     OnAsyncCopyComplete(rv);
 | |
|   }
 | |
| 
 | |
|   // Called once for each asynchronous file copy whether it succeeds or
 | |
|   // fails.  If a file copy is canceled, it still calls this method with
 | |
|   // an error code.
 | |
|   void OnAsyncCopyComplete(nsresult aRv) {
 | |
|     MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mConn);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mResolver);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions > 0);
 | |
| 
 | |
|     // Explicitly check for cancellation here to catch a race condition.
 | |
|     // Consider:
 | |
|     //
 | |
|     // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
 | |
|     //    copy context yet.
 | |
|     // 2) CancelAllStreamCopying() occurs on PBackground thread
 | |
|     // 3) Copy context from (1) is saved on IO thread.
 | |
|     //
 | |
|     // Checking for cancellation here catches this condition when we
 | |
|     // first call OnAsyncCopyComplete() manually from RunWithDBOnTarget().
 | |
|     //
 | |
|     // This explicit cancellation check also handles the case where we
 | |
|     // are canceled just after all stream copying completes.  We should
 | |
|     // abort the synchronous DB operations in this case if we have not
 | |
|     // started them yet.
 | |
|     if (NS_SUCCEEDED(aRv) && IsCanceled()) {
 | |
|       aRv = NS_ERROR_ABORT;
 | |
|     }
 | |
| 
 | |
|     // If any of the async copies fail, we need to still wait for them all to
 | |
|     // complete.  Cancel any other streams still working and remember the
 | |
|     // error.  All canceled streams will call OnAsyncCopyComplete().
 | |
|     if (NS_FAILED(aRv) && NS_SUCCEEDED(mAsyncResult)) {
 | |
|       CancelAllStreamCopying();
 | |
|       mAsyncResult = aRv;
 | |
|     }
 | |
| 
 | |
|     // Check to see if async copying is still on-going.  If so, then simply
 | |
|     // return for now.  We must wait for a later OnAsyncCopyComplete() call.
 | |
|     mExpectedAsyncCopyCompletions -= 1;
 | |
|     if (mExpectedAsyncCopyCompletions > 0) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // We have finished with all async copying.  Indicate this by clearing all
 | |
|     // our copy contexts.
 | |
|     {
 | |
|       MutexAutoLock lock(mMutex);
 | |
|       mCopyContextList.Clear();
 | |
|     }
 | |
| 
 | |
|     // An error occurred while async copying.  Terminate the Action.
 | |
|     // DoResolve() will clean up any files we may have written.
 | |
|     if (NS_FAILED(mAsyncResult)) {
 | |
|       DoResolve(mAsyncResult);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     mozStorageTransaction trans(mConn, false,
 | |
|                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(trans.Start()), QM_VOID);
 | |
| 
 | |
|     const nsresult rv = [this, &trans]() -> nsresult {
 | |
|       QM_TRY(CollectEachInRange(mList, [this](Entry& e) -> nsresult {
 | |
|         if (e.mRequestStream) {
 | |
|           QM_TRY(MOZ_TO_RESULT(BodyFinalizeWrite(*mDBDir, e.mRequestBodyId)));
 | |
|         }
 | |
|         if (e.mResponseStream) {
 | |
|           // Gerenate padding size for opaque response if needed.
 | |
|           if (e.mResponse.type() == ResponseType::Opaque) {
 | |
|             // It'll generate padding if we've not set it yet.
 | |
|             QM_TRY(MOZ_TO_RESULT(BodyMaybeUpdatePaddingSize(
 | |
|                 *mDirectoryMetadata, *mDBDir, e.mResponseBodyId,
 | |
|                 e.mResponse.paddingInfo(), &e.mResponse.paddingSize())));
 | |
| 
 | |
|             MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - e.mResponse.paddingSize() >=
 | |
|                                   mUpdatedPaddingSize);
 | |
|             mUpdatedPaddingSize += e.mResponse.paddingSize();
 | |
|           }
 | |
| 
 | |
|           QM_TRY(MOZ_TO_RESULT(BodyFinalizeWrite(*mDBDir, e.mResponseBodyId)));
 | |
|         }
 | |
| 
 | |
|         QM_TRY_UNWRAP(
 | |
|             auto deletionInfo,
 | |
|             db::CachePut(*mConn, mCacheId, e.mRequest,
 | |
|                          e.mRequestStream ? &e.mRequestBodyId : nullptr,
 | |
|                          e.mResponse,
 | |
|                          e.mResponseStream ? &e.mResponseBodyId : nullptr));
 | |
| 
 | |
|         const int64_t deletedPaddingSize = deletionInfo.mDeletedPaddingSize;
 | |
|         mDeletedBodyIdList = std::move(deletionInfo.mDeletedBodyIdList);
 | |
| 
 | |
|         MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - mDeletedPaddingSize >=
 | |
|                               deletedPaddingSize);
 | |
|         mDeletedPaddingSize += deletedPaddingSize;
 | |
| 
 | |
|         return NS_OK;
 | |
|       }));
 | |
| 
 | |
|       // Update padding file when it's necessary
 | |
|       QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
 | |
|           mDBDir, mConn, mUpdatedPaddingSize, mDeletedPaddingSize,
 | |
|           [&trans]() mutable { return trans.Commit(); })));
 | |
| 
 | |
|       return NS_OK;
 | |
|     }();
 | |
| 
 | |
|     DoResolve(rv);
 | |
|   }
 | |
| 
 | |
|   virtual void CompleteOnInitiatingThread(nsresult aRv) override {
 | |
|     NS_ASSERT_OWNINGTHREAD(Action);
 | |
| 
 | |
|     for (uint32_t i = 0; i < mList.Length(); ++i) {
 | |
|       mList[i].mRequestStream = nullptr;
 | |
|       mList[i].mResponseStream = nullptr;
 | |
|     }
 | |
| 
 | |
|     // If the transaction fails, we shouldn't delete the body files and decrease
 | |
|     // their padding size.
 | |
|     if (NS_FAILED(aRv)) {
 | |
|       mDeletedBodyIdList.Clear();
 | |
|       mDeletedPaddingSize = 0;
 | |
|     }
 | |
| 
 | |
|     mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
 | |
| 
 | |
|     if (mDeletedPaddingSize > 0) {
 | |
|       DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
 | |
|                                         mDeletedPaddingSize);
 | |
|     }
 | |
| 
 | |
|     Listener* listener = mManager->GetListener(mListenerId);
 | |
|     mManager = nullptr;
 | |
|     if (listener) {
 | |
|       listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   virtual void CancelOnInitiatingThread() override {
 | |
|     NS_ASSERT_OWNINGTHREAD(Action);
 | |
|     Action::CancelOnInitiatingThread();
 | |
|     CancelAllStreamCopying();
 | |
|   }
 | |
| 
 | |
|   virtual bool MatchesCacheId(CacheId aCacheId) const override {
 | |
|     NS_ASSERT_OWNINGTHREAD(Action);
 | |
|     return aCacheId == mCacheId;
 | |
|   }
 | |
| 
 | |
|   struct Entry {
 | |
|     CacheRequest mRequest;
 | |
|     nsCOMPtr<nsIInputStream> mRequestStream;
 | |
|     nsID mRequestBodyId;
 | |
|     nsCOMPtr<nsISupports> mRequestCopyContext;
 | |
| 
 | |
|     CacheResponse mResponse;
 | |
|     nsCOMPtr<nsIInputStream> mResponseStream;
 | |
|     nsID mResponseBodyId;
 | |
|     nsCOMPtr<nsISupports> mResponseCopyContext;
 | |
|   };
 | |
| 
 | |
|   enum StreamId { RequestStream, ResponseStream };
 | |
| 
 | |
|   nsresult StartStreamCopy(const CacheDirectoryMetadata& aDirectoryMetadata,
 | |
|                            Entry& aEntry, StreamId aStreamId,
 | |
|                            uint32_t* aCopyCountOut) {
 | |
|     MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aCopyCountOut);
 | |
| 
 | |
|     if (IsCanceled()) {
 | |
|       return NS_ERROR_ABORT;
 | |
|     }
 | |
| 
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aStreamId == RequestStream ||
 | |
|                           aStreamId == ResponseStream);
 | |
| 
 | |
|     const auto& source = aStreamId == RequestStream ? aEntry.mRequestStream
 | |
|                                                     : aEntry.mResponseStream;
 | |
| 
 | |
|     if (!source) {
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     QM_TRY_INSPECT((const auto& [bodyId, copyContext]),
 | |
|                    BodyStartWriteStream(aDirectoryMetadata, *mDBDir, *source,
 | |
|                                         this, AsyncCopyCompleteFunc));
 | |
| 
 | |
|     if (aStreamId == RequestStream) {
 | |
|       aEntry.mRequestBodyId = bodyId;
 | |
|     } else {
 | |
|       aEntry.mResponseBodyId = bodyId;
 | |
|     }
 | |
| 
 | |
|     mBodyIdWrittenList.AppendElement(bodyId);
 | |
| 
 | |
|     if (copyContext) {
 | |
|       MutexAutoLock lock(mMutex);
 | |
|       mCopyContextList.AppendElement(copyContext);
 | |
|     }
 | |
| 
 | |
|     *aCopyCountOut += 1;
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   void CancelAllStreamCopying() {
 | |
|     // May occur on either owning thread or target thread
 | |
|     MutexAutoLock lock(mMutex);
 | |
|     for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) {
 | |
|       MOZ_DIAGNOSTIC_ASSERT(mCopyContextList[i]);
 | |
|       BodyCancelWrite(*mCopyContextList[i]);
 | |
|     }
 | |
|     mCopyContextList.Clear();
 | |
|   }
 | |
| 
 | |
|   static void AsyncCopyCompleteFunc(void* aClosure, nsresult aRv) {
 | |
|     // May be on any thread, including STS event target.
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aClosure);
 | |
|     // Weak ref as we are guaranteed to the action is alive until
 | |
|     // CompleteOnInitiatingThread is called.
 | |
|     CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure);
 | |
|     action->CallOnAsyncCopyCompleteOnTargetThread(aRv);
 | |
|   }
 | |
| 
 | |
|   void CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv) {
 | |
|     // May be on any thread, including STS event target.  Non-owning runnable
 | |
|     // here since we are guaranteed the Action will survive until
 | |
|     // CompleteOnInitiatingThread is called.
 | |
|     nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod<nsresult>(
 | |
|         "dom::cache::Manager::CachePutAllAction::OnAsyncCopyComplete", this,
 | |
|         &CachePutAllAction::OnAsyncCopyComplete, aRv);
 | |
|     MOZ_ALWAYS_SUCCEEDS(
 | |
|         mTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL));
 | |
|   }
 | |
| 
 | |
|   void DoResolve(nsresult aRv) {
 | |
|     MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
| 
 | |
|     // DoResolve() must not be called until all async copying has completed.
 | |
| #ifdef DEBUG
 | |
|     {
 | |
|       MutexAutoLock lock(mMutex);
 | |
|       MOZ_ASSERT(mCopyContextList.IsEmpty());
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     // Clean up any files we might have written before hitting the error.
 | |
|     if (NS_FAILED(aRv)) {
 | |
|       BodyDeleteFiles(*mDirectoryMetadata, *mDBDir, mBodyIdWrittenList);
 | |
|       if (mUpdatedPaddingSize > 0) {
 | |
|         DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
 | |
|                                           mUpdatedPaddingSize);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Must be released on the target thread where it was opened.
 | |
|     mConn = nullptr;
 | |
| 
 | |
|     // Drop our ref to the target thread as we are done with this thread.
 | |
|     // Also makes our thread assertions catch any incorrect method calls
 | |
|     // after resolve.
 | |
|     mTarget = nullptr;
 | |
| 
 | |
|     // Make sure to de-ref the resolver per the Action API contract.
 | |
|     SafeRefPtr<Action::Resolver> resolver = std::move(mResolver);
 | |
|     resolver->Resolve(aRv);
 | |
|   }
 | |
| 
 | |
|   // initiating thread only
 | |
|   SafeRefPtr<Manager> mManager;
 | |
|   const ListenerId mListenerId;
 | |
| 
 | |
|   // Set on initiating thread, read on target thread.  State machine guarantees
 | |
|   // these are not modified while being read by the target thread.
 | |
|   const CacheId mCacheId;
 | |
|   nsTArray<Entry> mList;
 | |
|   uint32_t mExpectedAsyncCopyCompletions;
 | |
| 
 | |
|   // target thread only
 | |
|   SafeRefPtr<Resolver> mResolver;
 | |
|   nsCOMPtr<nsIFile> mDBDir;
 | |
|   nsCOMPtr<mozIStorageConnection> mConn;
 | |
|   nsCOMPtr<nsISerialEventTarget> mTarget;
 | |
|   nsresult mAsyncResult;
 | |
|   nsTArray<nsID> mBodyIdWrittenList;
 | |
| 
 | |
|   // Written to on target thread, accessed on initiating thread after target
 | |
|   // thread activity is guaranteed complete
 | |
|   nsTArray<nsID> mDeletedBodyIdList;
 | |
| 
 | |
|   // accessed from any thread while mMutex locked
 | |
|   Mutex mMutex MOZ_UNANNOTATED;
 | |
|   nsTArray<nsCOMPtr<nsISupports>> mCopyContextList;
 | |
| 
 | |
|   Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
 | |
|   // Track how much pad amount has been added for new entries so that it can be
 | |
|   // removed if an error occurs.
 | |
|   int64_t mUpdatedPaddingSize;
 | |
|   // Track any pad amount associated with overwritten entries.
 | |
|   int64_t mDeletedPaddingSize;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::CacheDeleteAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   CacheDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                     CacheId aCacheId, const CacheDeleteArgs& aArgs)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mCacheId(aCacheId),
 | |
|         mArgs(aArgs),
 | |
|         mSuccess(false) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     mDirectoryMetadata.emplace(aDirectoryMetadata);
 | |
| 
 | |
|     mozStorageTransaction trans(aConn, false,
 | |
|                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(trans.Start()));
 | |
| 
 | |
|     QM_TRY_UNWRAP(
 | |
|         auto maybeDeletionInfo,
 | |
|         db::CacheDelete(*aConn, mCacheId, mArgs.request(), mArgs.params()));
 | |
| 
 | |
|     mSuccess = maybeDeletionInfo.isSome();
 | |
|     if (mSuccess) {
 | |
|       mDeletionInfo = std::move(maybeDeletionInfo.ref());
 | |
|     }
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
 | |
|                aDBDir, aConn, /* aIncreaceSize */ 0,
 | |
|                mDeletionInfo.mDeletedPaddingSize,
 | |
|                [&trans]() mutable { return trans.Commit(); })),
 | |
|            QM_PROPAGATE, [this](const nsresult) { mSuccess = false; });
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     // If the transaction fails, we shouldn't delete the body files and decrease
 | |
|     // their padding size.
 | |
|     if (aRv.Failed()) {
 | |
|       mDeletionInfo.mDeletedBodyIdList.Clear();
 | |
|       mDeletionInfo.mDeletedPaddingSize = 0;
 | |
|     }
 | |
| 
 | |
|     mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
 | |
| 
 | |
|     if (mDeletionInfo.mDeletedPaddingSize > 0) {
 | |
|       DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
 | |
|                                         mDeletionInfo.mDeletedPaddingSize);
 | |
|     }
 | |
| 
 | |
|     aListener->OnOpComplete(std::move(aRv), CacheDeleteResult(mSuccess));
 | |
|   }
 | |
| 
 | |
|   virtual bool MatchesCacheId(CacheId aCacheId) const override {
 | |
|     return aCacheId == mCacheId;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const CacheId mCacheId;
 | |
|   const CacheDeleteArgs mArgs;
 | |
|   bool mSuccess;
 | |
|   DeletionInfo mDeletionInfo;
 | |
|   Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::CacheKeysAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   CacheKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                   CacheId aCacheId, const CacheKeysArgs& aArgs,
 | |
|                   SafeRefPtr<StreamList> aStreamList)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mCacheId(aCacheId),
 | |
|         mArgs(aArgs),
 | |
|         mStreamList(std::move(aStreamList)) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
 | |
| 
 | |
|     QM_TRY_UNWRAP(
 | |
|         mSavedRequests,
 | |
|         db::CacheKeys(*aConn, mCacheId, mArgs.maybeRequest(), mArgs.params()));
 | |
| 
 | |
|     for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) {
 | |
|       if (!mSavedRequests[i].mHasBodyId ||
 | |
|           IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
 | |
|         mSavedRequests[i].mHasBodyId = false;
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsIInputStream> stream;
 | |
|       if (mArgs.openMode() == OpenMode::Eager) {
 | |
|         QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
 | |
|                                        mSavedRequests[i].mBodyId));
 | |
|       }
 | |
| 
 | |
|       mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream));
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     mStreamList->Activate(mCacheId);
 | |
|     aListener->OnOpComplete(std::move(aRv), CacheKeysResult(), mSavedRequests,
 | |
|                             *mStreamList);
 | |
|     mStreamList = nullptr;
 | |
|   }
 | |
| 
 | |
|   virtual bool MatchesCacheId(CacheId aCacheId) const override {
 | |
|     return aCacheId == mCacheId;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const CacheId mCacheId;
 | |
|   const CacheKeysArgs mArgs;
 | |
|   SafeRefPtr<StreamList> mStreamList;
 | |
|   nsTArray<SavedRequest> mSavedRequests;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::StorageMatchAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   StorageMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                      Namespace aNamespace, const StorageMatchArgs& aArgs,
 | |
|                      SafeRefPtr<StreamList> aStreamList)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mNamespace(aNamespace),
 | |
|         mArgs(aArgs),
 | |
|         mStreamList(std::move(aStreamList)),
 | |
|         mFoundResponse(false) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
 | |
| 
 | |
|     auto maybeResponse =
 | |
|         db::StorageMatch(*aConn, mNamespace, mArgs.request(), mArgs.params());
 | |
|     if (NS_WARN_IF(maybeResponse.isErr())) {
 | |
|       return maybeResponse.unwrapErr();
 | |
|     }
 | |
| 
 | |
|     mFoundResponse = maybeResponse.inspect().isSome();
 | |
|     if (mFoundResponse) {
 | |
|       mSavedResponse = maybeResponse.unwrap().ref();
 | |
|     }
 | |
| 
 | |
|     if (!mFoundResponse || !mSavedResponse.mHasBodyId ||
 | |
|         IsHeadRequest(mArgs.request(), mArgs.params())) {
 | |
|       mSavedResponse.mHasBodyId = false;
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIInputStream> stream;
 | |
|     if (mArgs.openMode() == OpenMode::Eager) {
 | |
|       QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
 | |
|                                      mSavedResponse.mBodyId));
 | |
|     }
 | |
| 
 | |
|     mStreamList->Add(mSavedResponse.mBodyId, std::move(stream));
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     if (!mFoundResponse) {
 | |
|       aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()));
 | |
|     } else {
 | |
|       mStreamList->Activate(mSavedResponse.mCacheId);
 | |
|       aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()),
 | |
|                               mSavedResponse, *mStreamList);
 | |
|     }
 | |
|     mStreamList = nullptr;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const Namespace mNamespace;
 | |
|   const StorageMatchArgs mArgs;
 | |
|   SafeRefPtr<StreamList> mStreamList;
 | |
|   bool mFoundResponse;
 | |
|   SavedResponse mSavedResponse;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::StorageHasAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   StorageHasAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                    Namespace aNamespace, const StorageHasArgs& aArgs)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mNamespace(aNamespace),
 | |
|         mArgs(aArgs),
 | |
|         mCacheFound(false) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     QM_TRY_INSPECT(const auto& maybeCacheId,
 | |
|                    db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
 | |
| 
 | |
|     mCacheFound = maybeCacheId.isSome();
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     aListener->OnOpComplete(std::move(aRv), StorageHasResult(mCacheFound));
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const Namespace mNamespace;
 | |
|   const StorageHasArgs mArgs;
 | |
|   bool mCacheFound;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::StorageOpenAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   StorageOpenAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                     Namespace aNamespace, const StorageOpenArgs& aArgs)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mNamespace(aNamespace),
 | |
|         mArgs(aArgs),
 | |
|         mCacheId(INVALID_CACHE_ID) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     // Cache does not exist, create it instead
 | |
|     mozStorageTransaction trans(aConn, false,
 | |
|                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(trans.Start()));
 | |
| 
 | |
|     // Look for existing cache
 | |
|     QM_TRY_INSPECT(const auto& maybeCacheId,
 | |
|                    db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
 | |
| 
 | |
|     if (maybeCacheId.isSome()) {
 | |
|       mCacheId = maybeCacheId.ref();
 | |
|       MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     QM_TRY_UNWRAP(mCacheId, db::CreateCacheId(*aConn));
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(
 | |
|         db::StoragePutCache(*aConn, mNamespace, mArgs.key(), mCacheId)));
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(trans.Commit()));
 | |
| 
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aRv.Failed() || mCacheId != INVALID_CACHE_ID);
 | |
|     aListener->OnOpComplete(std::move(aRv),
 | |
|                             StorageOpenResult(nullptr, nullptr, mNamespace),
 | |
|                             mCacheId);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const Namespace mNamespace;
 | |
|   const StorageOpenArgs mArgs;
 | |
|   CacheId mCacheId;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::StorageDeleteAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   StorageDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                       Namespace aNamespace, const StorageDeleteArgs& aArgs)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mNamespace(aNamespace),
 | |
|         mArgs(aArgs),
 | |
|         mCacheDeleted(false),
 | |
|         mCacheId(INVALID_CACHE_ID) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     mozStorageTransaction trans(aConn, false,
 | |
|                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(trans.Start()));
 | |
| 
 | |
|     QM_TRY_INSPECT(const auto& maybeCacheId,
 | |
|                    db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
 | |
| 
 | |
|     if (maybeCacheId.isNothing()) {
 | |
|       mCacheDeleted = false;
 | |
|       return NS_OK;
 | |
|     }
 | |
|     mCacheId = maybeCacheId.ref();
 | |
| 
 | |
|     // Don't delete the removing padding size here, we'll delete it on
 | |
|     // DeleteOrphanedCacheAction.
 | |
|     QM_TRY(
 | |
|         MOZ_TO_RESULT(db::StorageForgetCache(*aConn, mNamespace, mArgs.key())));
 | |
| 
 | |
|     QM_TRY(MOZ_TO_RESULT(trans.Commit()));
 | |
| 
 | |
|     mCacheDeleted = true;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     if (mCacheDeleted) {
 | |
|       // If content is referencing this cache, mark it orphaned to be
 | |
|       // deleted later.
 | |
|       if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) {
 | |
|         // no outstanding references, delete immediately
 | |
|         const auto pinnedContext =
 | |
|             SafeRefPtr{mManager->mContext, AcquireStrongRefFromRawPtr{}};
 | |
| 
 | |
|         if (pinnedContext->IsCanceled()) {
 | |
|           pinnedContext->NoteOrphanedData();
 | |
|         } else {
 | |
|           pinnedContext->CancelForCacheId(mCacheId);
 | |
|           pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
 | |
|               mManager.clonePtr(), mCacheId));
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     aListener->OnOpComplete(std::move(aRv), StorageDeleteResult(mCacheDeleted));
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const Namespace mNamespace;
 | |
|   const StorageDeleteArgs mArgs;
 | |
|   bool mCacheDeleted;
 | |
|   CacheId mCacheId;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::StorageKeysAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   StorageKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                     Namespace aNamespace)
 | |
|       : BaseAction(std::move(aManager), aListenerId), mNamespace(aNamespace) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     QM_TRY_UNWRAP(mKeys, db::StorageGetKeys(*aConn, mNamespace));
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     if (aRv.Failed()) {
 | |
|       mKeys.Clear();
 | |
|     }
 | |
|     aListener->OnOpComplete(std::move(aRv), StorageKeysResult(mKeys));
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   const Namespace mNamespace;
 | |
|   nsTArray<nsString> mKeys;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| class Manager::OpenStreamAction final : public Manager::BaseAction {
 | |
|  public:
 | |
|   OpenStreamAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
 | |
|                    InputStreamResolver&& aResolver, const nsID& aBodyId)
 | |
|       : BaseAction(std::move(aManager), aListenerId),
 | |
|         mResolver(std::move(aResolver)),
 | |
|         mBodyId(aBodyId) {}
 | |
| 
 | |
|   virtual nsresult RunSyncWithDBOnTarget(
 | |
|       const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
 | |
|       mozIStorageConnection* aConn) override {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aDBDir);
 | |
| 
 | |
|     QM_TRY_UNWRAP(mBodyStream, BodyOpen(aDirectoryMetadata, *aDBDir, mBodyId));
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
 | |
|     if (aRv.Failed()) {
 | |
|       // Ignore the reason for fail and just pass a null input stream to let it
 | |
|       // fail.
 | |
|       aRv.SuppressException();
 | |
|       mResolver(nullptr);
 | |
|     } else {
 | |
|       mResolver(std::move(mBodyStream));
 | |
|     }
 | |
| 
 | |
|     mResolver = nullptr;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   InputStreamResolver mResolver;
 | |
|   const nsID mBodyId;
 | |
|   nsCOMPtr<nsIInputStream> mBodyStream;
 | |
| };
 | |
| 
 | |
| // ----------------------------------------------------------------------------
 | |
| 
 | |
| // static
 | |
| Manager::ListenerId Manager::sNextListenerId = 0;
 | |
| 
 | |
| void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
 | |
|                                      const CacheOpResult& aResult) {
 | |
|   OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID, Nothing());
 | |
| }
 | |
| 
 | |
| void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
 | |
|                                      const CacheOpResult& aResult,
 | |
|                                      CacheId aOpenedCacheId) {
 | |
|   OnOpComplete(std::move(aRv), aResult, aOpenedCacheId, Nothing());
 | |
| }
 | |
| 
 | |
| void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
 | |
|                                      const CacheOpResult& aResult,
 | |
|                                      const SavedResponse& aSavedResponse,
 | |
|                                      StreamList& aStreamList) {
 | |
|   AutoTArray<SavedResponse, 1> responseList;
 | |
|   responseList.AppendElement(aSavedResponse);
 | |
|   OnOpComplete(
 | |
|       std::move(aRv), aResult, INVALID_CACHE_ID,
 | |
|       Some(StreamInfo{responseList, nsTArray<SavedRequest>(), aStreamList}));
 | |
| }
 | |
| 
 | |
| void Manager::Listener::OnOpComplete(
 | |
|     ErrorResult&& aRv, const CacheOpResult& aResult,
 | |
|     const nsTArray<SavedResponse>& aSavedResponseList,
 | |
|     StreamList& aStreamList) {
 | |
|   OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
 | |
|                Some(StreamInfo{aSavedResponseList, nsTArray<SavedRequest>(),
 | |
|                                aStreamList}));
 | |
| }
 | |
| 
 | |
| void Manager::Listener::OnOpComplete(
 | |
|     ErrorResult&& aRv, const CacheOpResult& aResult,
 | |
|     const nsTArray<SavedRequest>& aSavedRequestList, StreamList& aStreamList) {
 | |
|   OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
 | |
|                Some(StreamInfo{nsTArray<SavedResponse>(), aSavedRequestList,
 | |
|                                aStreamList}));
 | |
| }
 | |
| 
 | |
| // static
 | |
| Result<SafeRefPtr<Manager>, nsresult> Manager::AcquireCreateIfNonExistent(
 | |
|     const SafeRefPtr<ManagerId>& aManagerId) {
 | |
|   mozilla::ipc::AssertIsOnBackgroundThread();
 | |
|   return Factory::AcquireCreateIfNonExistent(aManagerId);
 | |
| }
 | |
| 
 | |
| // static
 | |
| void Manager::InitiateShutdown() {
 | |
|   mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|   Factory::AbortAll();
 | |
| 
 | |
|   Factory::ShutdownAll();
 | |
| }
 | |
| 
 | |
| // static
 | |
| bool Manager::IsShutdownAllComplete() {
 | |
|   mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|   return Factory::IsShutdownAllComplete();
 | |
| }
 | |
| 
 | |
| #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
| void Manager::RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId) {
 | |
|   Factory::RecordMayNotDeleteCSCP(aCacheStreamControlParentId);
 | |
| }
 | |
| 
 | |
| void Manager::RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId) {
 | |
|   Factory::RecordHaveDeletedCSCP(aCacheStreamControlParentId);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| // static
 | |
| nsCString Manager::GetShutdownStatus() {
 | |
|   mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|   return Factory::GetShutdownStatus();
 | |
| }
 | |
| 
 | |
| // static
 | |
| void Manager::Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
 | |
|   mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|   Factory::Abort(aDirectoryLockIds);
 | |
| }
 | |
| 
 | |
| // static
 | |
| void Manager::AbortAll() {
 | |
|   mozilla::ipc::AssertIsOnBackgroundThread();
 | |
| 
 | |
|   Factory::AbortAll();
 | |
| }
 | |
| 
 | |
| void Manager::RemoveListener(Listener* aListener) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   // There may not be a listener here in the case where an actor is killed
 | |
|   // before it can perform any actual async requests on Manager.
 | |
|   mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
 | |
|   MOZ_ASSERT(
 | |
|       !mListeners.Contains(aListener, ListenerEntryListenerComparator()));
 | |
|   MaybeAllowContextToClose();
 | |
| }
 | |
| 
 | |
| void Manager::RemoveContext(Context& aContext) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mContext);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mContext == &aContext);
 | |
| 
 | |
|   // Whether the Context destruction was triggered from the Manager going
 | |
|   // idle or the underlying storage being invalidated, we should know we
 | |
|   // are closing before the Context is destroyed.
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
 | |
| 
 | |
|   // Before forgetting the Context, check to see if we have any outstanding
 | |
|   // cache or body objects waiting for deletion.  If so, note that we've
 | |
|   // orphaned data so it will be cleaned up on the next open.
 | |
|   if (std::any_of(
 | |
|           mCacheIdRefs.cbegin(), mCacheIdRefs.cend(),
 | |
|           [](const auto& cacheIdRef) { return cacheIdRef.mOrphaned; }) ||
 | |
|       std::any_of(mBodyIdRefs.cbegin(), mBodyIdRefs.cend(),
 | |
|                   [](const auto& bodyIdRef) { return bodyIdRef.mOrphaned; })) {
 | |
|     aContext.NoteOrphanedData();
 | |
|   }
 | |
| 
 | |
|   mContext = nullptr;
 | |
| 
 | |
|   // Once the context is gone, we can immediately remove ourself from the
 | |
|   // Factory list.  We don't need to block shutdown by staying in the list
 | |
|   // any more.
 | |
|   Factory::Remove(*this);
 | |
| }
 | |
| 
 | |
| void Manager::NoteClosing() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   // This can be called more than once legitimately through different paths.
 | |
|   mState = Closing;
 | |
| }
 | |
| 
 | |
| Manager::State Manager::GetState() const {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   return mState;
 | |
| }
 | |
| 
 | |
| void Manager::AddRefCacheId(CacheId aCacheId) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   const auto end = mCacheIdRefs.end();
 | |
|   const auto foundIt =
 | |
|       std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
 | |
|   if (foundIt != end) {
 | |
|     foundIt->mCount += 1;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mCacheIdRefs.AppendElement(CacheIdRefCounter{aCacheId, 1, false});
 | |
| }
 | |
| 
 | |
| void Manager::ReleaseCacheId(CacheId aCacheId) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   const auto end = mCacheIdRefs.end();
 | |
|   const auto foundIt =
 | |
|       std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
 | |
|   if (foundIt != end) {
 | |
| #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     const uint32_t oldRef = foundIt->mCount;
 | |
| #endif
 | |
|     foundIt->mCount -= 1;
 | |
|     MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
 | |
|     if (foundIt->mCount == 0) {
 | |
|       const bool orphaned = foundIt->mOrphaned;
 | |
|       mCacheIdRefs.RemoveElementAt(foundIt);
 | |
|       const auto pinnedContext =
 | |
|           SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|       // If the context is already gone, then orphan flag should have been
 | |
|       // set in RemoveContext().
 | |
|       if (orphaned && pinnedContext) {
 | |
|         if (pinnedContext->IsCanceled()) {
 | |
|           pinnedContext->NoteOrphanedData();
 | |
|         } else {
 | |
|           pinnedContext->CancelForCacheId(aCacheId);
 | |
|           pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
 | |
|               SafeRefPtrFromThis(), aCacheId));
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     MaybeAllowContextToClose();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT_UNREACHABLE("Attempt to release CacheId that is not referenced!");
 | |
| }
 | |
| 
 | |
| void Manager::AddRefBodyId(const nsID& aBodyId) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   const auto end = mBodyIdRefs.end();
 | |
|   const auto foundIt =
 | |
|       std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
 | |
|   if (foundIt != end) {
 | |
|     foundIt->mCount += 1;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mBodyIdRefs.AppendElement(BodyIdRefCounter{aBodyId, 1, false});
 | |
| }
 | |
| 
 | |
| void Manager::ReleaseBodyId(const nsID& aBodyId) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   const auto end = mBodyIdRefs.end();
 | |
|   const auto foundIt =
 | |
|       std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
 | |
|   if (foundIt != end) {
 | |
| #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 | |
|     const uint32_t oldRef = foundIt->mCount;
 | |
| #endif
 | |
|     foundIt->mCount -= 1;
 | |
|     MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
 | |
|     if (foundIt->mCount < 1) {
 | |
|       const bool orphaned = foundIt->mOrphaned;
 | |
|       mBodyIdRefs.RemoveElementAt(foundIt);
 | |
|       const auto pinnedContext =
 | |
|           SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|       // If the context is already gone, then orphan flag should have been
 | |
|       // set in RemoveContext().
 | |
|       if (orphaned && pinnedContext) {
 | |
|         if (pinnedContext->IsCanceled()) {
 | |
|           pinnedContext->NoteOrphanedData();
 | |
|         } else {
 | |
|           pinnedContext->Dispatch(
 | |
|               MakeSafeRefPtr<DeleteOrphanedBodyAction>(aBodyId));
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     MaybeAllowContextToClose();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT_UNREACHABLE("Attempt to release BodyId that is not referenced!");
 | |
| }
 | |
| 
 | |
| const ManagerId& Manager::GetManagerId() const { return *mManagerId; }
 | |
| 
 | |
| void Manager::AddStreamList(StreamList& aStreamList) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   mStreamLists.AppendElement(WrapNotNullUnchecked(&aStreamList));
 | |
| }
 | |
| 
 | |
| void Manager::RemoveStreamList(StreamList& aStreamList) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   mStreamLists.RemoveElement(&aStreamList);
 | |
| }
 | |
| 
 | |
| void Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId,
 | |
|                              const CacheOpArgs& aOpArgs) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aListener);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs);
 | |
| 
 | |
|   if (NS_WARN_IF(mState == Closing)) {
 | |
|     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
 | |
| 
 | |
|   auto action = [this, aListener, aCacheId, &aOpArgs,
 | |
|                  &pinnedContext]() -> SafeRefPtr<Action> {
 | |
|     const ListenerId listenerId = SaveListener(aListener);
 | |
| 
 | |
|     if (CacheOpArgs::TCacheDeleteArgs == aOpArgs.type()) {
 | |
|       return MakeSafeRefPtr<CacheDeleteAction>(SafeRefPtrFromThis(), listenerId,
 | |
|                                                aCacheId,
 | |
|                                                aOpArgs.get_CacheDeleteArgs());
 | |
|     }
 | |
| 
 | |
|     auto streamList = MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
 | |
|                                                  pinnedContext.clonePtr());
 | |
| 
 | |
|     switch (aOpArgs.type()) {
 | |
|       case CacheOpArgs::TCacheMatchArgs:
 | |
|         return MakeSafeRefPtr<CacheMatchAction>(
 | |
|             SafeRefPtrFromThis(), listenerId, aCacheId,
 | |
|             aOpArgs.get_CacheMatchArgs(), std::move(streamList));
 | |
|       case CacheOpArgs::TCacheMatchAllArgs:
 | |
|         return MakeSafeRefPtr<CacheMatchAllAction>(
 | |
|             SafeRefPtrFromThis(), listenerId, aCacheId,
 | |
|             aOpArgs.get_CacheMatchAllArgs(), std::move(streamList));
 | |
|       case CacheOpArgs::TCacheKeysArgs:
 | |
|         return MakeSafeRefPtr<CacheKeysAction>(
 | |
|             SafeRefPtrFromThis(), listenerId, aCacheId,
 | |
|             aOpArgs.get_CacheKeysArgs(), std::move(streamList));
 | |
|       default:
 | |
|         MOZ_CRASH("Unknown Cache operation!");
 | |
|     }
 | |
|   }();
 | |
| 
 | |
|   pinnedContext->Dispatch(std::move(action));
 | |
| }
 | |
| 
 | |
| void Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace,
 | |
|                                const CacheOpArgs& aOpArgs) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aListener);
 | |
| 
 | |
|   if (NS_WARN_IF(mState == Closing)) {
 | |
|     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
 | |
| 
 | |
|   auto action = [this, aListener, aNamespace, &aOpArgs,
 | |
|                  &pinnedContext]() -> SafeRefPtr<Action> {
 | |
|     const ListenerId listenerId = SaveListener(aListener);
 | |
| 
 | |
|     switch (aOpArgs.type()) {
 | |
|       case CacheOpArgs::TStorageMatchArgs:
 | |
|         return MakeSafeRefPtr<StorageMatchAction>(
 | |
|             SafeRefPtrFromThis(), listenerId, aNamespace,
 | |
|             aOpArgs.get_StorageMatchArgs(),
 | |
|             MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
 | |
|                                        pinnedContext.clonePtr()));
 | |
|       case CacheOpArgs::TStorageHasArgs:
 | |
|         return MakeSafeRefPtr<StorageHasAction>(SafeRefPtrFromThis(),
 | |
|                                                 listenerId, aNamespace,
 | |
|                                                 aOpArgs.get_StorageHasArgs());
 | |
|       case CacheOpArgs::TStorageOpenArgs:
 | |
|         return MakeSafeRefPtr<StorageOpenAction>(SafeRefPtrFromThis(),
 | |
|                                                  listenerId, aNamespace,
 | |
|                                                  aOpArgs.get_StorageOpenArgs());
 | |
|       case CacheOpArgs::TStorageDeleteArgs:
 | |
|         return MakeSafeRefPtr<StorageDeleteAction>(
 | |
|             SafeRefPtrFromThis(), listenerId, aNamespace,
 | |
|             aOpArgs.get_StorageDeleteArgs());
 | |
|       case CacheOpArgs::TStorageKeysArgs:
 | |
|         return MakeSafeRefPtr<StorageKeysAction>(SafeRefPtrFromThis(),
 | |
|                                                  listenerId, aNamespace);
 | |
|       default:
 | |
|         MOZ_CRASH("Unknown CacheStorage operation!");
 | |
|     }
 | |
|   }();
 | |
| 
 | |
|   pinnedContext->Dispatch(std::move(action));
 | |
| }
 | |
| 
 | |
| void Manager::ExecuteOpenStream(Listener* aListener,
 | |
|                                 InputStreamResolver&& aResolver,
 | |
|                                 const nsID& aBodyId) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aListener);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aResolver);
 | |
| 
 | |
|   if (NS_WARN_IF(mState == Closing)) {
 | |
|     aResolver(nullptr);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
 | |
| 
 | |
|   // We save the listener simply to track the existence of the caller here.
 | |
|   // Our returned value will really be passed to the resolver when the
 | |
|   // operation completes.  In the future we should remove the Listener
 | |
|   // mechanism in favor of std::function or MozPromise.
 | |
|   ListenerId listenerId = SaveListener(aListener);
 | |
| 
 | |
|   pinnedContext->Dispatch(MakeSafeRefPtr<OpenStreamAction>(
 | |
|       SafeRefPtrFromThis(), listenerId, std::move(aResolver), aBodyId));
 | |
| }
 | |
| 
 | |
| void Manager::ExecutePutAll(
 | |
|     Listener* aListener, CacheId aCacheId,
 | |
|     const nsTArray<CacheRequestResponse>& aPutList,
 | |
|     const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
 | |
|     const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aListener);
 | |
| 
 | |
|   if (NS_WARN_IF(mState == Closing)) {
 | |
|     aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
 | |
| 
 | |
|   ListenerId listenerId = SaveListener(aListener);
 | |
|   pinnedContext->Dispatch(MakeSafeRefPtr<CachePutAllAction>(
 | |
|       SafeRefPtrFromThis(), listenerId, aCacheId, aPutList, aRequestStreamList,
 | |
|       aResponseStreamList));
 | |
| }
 | |
| 
 | |
| Manager::Manager(SafeRefPtr<ManagerId> aManagerId, nsIThread* aIOThread,
 | |
|                  const ConstructorGuard&)
 | |
|     : mManagerId(std::move(aManagerId)),
 | |
|       mIOThread(aIOThread),
 | |
|       mContext(nullptr),
 | |
|       mShuttingDown(false),
 | |
|       mState(Open) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mManagerId);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mIOThread);
 | |
| }
 | |
| 
 | |
| Manager::~Manager() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mContext);
 | |
| 
 | |
|   nsCOMPtr<nsIThread> ioThread;
 | |
|   mIOThread.swap(ioThread);
 | |
| 
 | |
|   // Don't spin the event loop in the destructor waiting for the thread to
 | |
|   // shutdown.  Defer this to the main thread, instead.
 | |
|   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(
 | |
|       "nsIThread::AsyncShutdown", ioThread, &nsIThread::AsyncShutdown)));
 | |
| }
 | |
| 
 | |
| void Manager::Init(Maybe<Manager&> aOldManager) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   // Create the context immediately.  Since there can at most be one Context
 | |
|   // per Manager now, this lets us cleanly call Factory::Remove() once the
 | |
|   // Context goes away.
 | |
|   SafeRefPtr<Context> ref = Context::Create(
 | |
|       SafeRefPtrFromThis(), mIOThread->SerialEventTarget(),
 | |
|       MakeSafeRefPtr<SetupAction>(),
 | |
|       aOldManager ? SomeRef(*aOldManager->mContext) : Nothing());
 | |
|   mContext = ref.unsafeGetRawPtr();
 | |
| }
 | |
| 
 | |
| void Manager::Shutdown() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   // Ignore duplicate attempts to shutdown.  This can occur when we start
 | |
|   // a browser initiated shutdown and then run ~Manager() which also
 | |
|   // calls Shutdown().
 | |
|   if (mShuttingDown) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mShuttingDown = true;
 | |
| 
 | |
|   // Note that we are closing to prevent any new requests from coming in and
 | |
|   // creating a new Context.  We must ensure all Contexts and IO operations are
 | |
|   // complete before shutdown proceeds.
 | |
|   NoteClosing();
 | |
| 
 | |
|   // If there is a context, then cancel and only note that we are done after
 | |
|   // its cleaned up.
 | |
|   if (mContext) {
 | |
|     const auto pinnedContext =
 | |
|         SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|     pinnedContext->CancelAll();
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| Maybe<DirectoryLock&> Manager::MaybeDirectoryLockRef() const {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mContext);
 | |
| 
 | |
|   return mContext->MaybeDirectoryLockRef();
 | |
| }
 | |
| 
 | |
| void Manager::Abort() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mContext);
 | |
| 
 | |
|   // Note that we are closing to prevent any new requests from coming in and
 | |
|   // creating a new Context.  We must ensure all Contexts and IO operations are
 | |
|   // complete before origin clear proceeds.
 | |
|   NoteClosing();
 | |
| 
 | |
|   // Cancel and only note that we are done after the context is cleaned up.
 | |
|   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|   pinnedContext->CancelAll();
 | |
| }
 | |
| 
 | |
| Manager::ListenerId Manager::SaveListener(Listener* aListener) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   // Once a Listener is added, we keep a reference to it until its
 | |
|   // removed.  Since the same Listener might make multiple requests,
 | |
|   // ensure we only have a single reference in our list.
 | |
|   ListenerList::index_type index =
 | |
|       mListeners.IndexOf(aListener, 0, ListenerEntryListenerComparator());
 | |
|   if (index != ListenerList::NoIndex) {
 | |
|     return mListeners[index].mId;
 | |
|   }
 | |
| 
 | |
|   ListenerId id = sNextListenerId;
 | |
|   sNextListenerId += 1;
 | |
| 
 | |
|   mListeners.AppendElement(ListenerEntry(id, aListener));
 | |
|   return id;
 | |
| }
 | |
| 
 | |
| Manager::Listener* Manager::GetListener(ListenerId aListenerId) const {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
|   ListenerList::index_type index =
 | |
|       mListeners.IndexOf(aListenerId, 0, ListenerEntryIdComparator());
 | |
|   if (index != ListenerList::NoIndex) {
 | |
|     return mListeners[index].mListener;
 | |
|   }
 | |
| 
 | |
|   // This can legitimately happen if the actor is deleted while a request is
 | |
|   // in process.  For example, the child process OOMs.
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| bool Manager::SetCacheIdOrphanedIfRefed(CacheId aCacheId) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   const auto end = mCacheIdRefs.end();
 | |
|   const auto foundIt =
 | |
|       std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
 | |
|   if (foundIt != end) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
 | |
|     foundIt->mOrphaned = true;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // TODO: provide way to set body non-orphaned if its added back to a cache (bug
 | |
| // 1110479)
 | |
| 
 | |
| bool Manager::SetBodyIdOrphanedIfRefed(const nsID& aBodyId) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   const auto end = mBodyIdRefs.end();
 | |
|   const auto foundIt =
 | |
|       std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
 | |
|   if (foundIt != end) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
 | |
|     foundIt->mOrphaned = true;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void Manager::NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   // XXX TransformIfIntoNewArray might be generalized to allow specifying the
 | |
|   // type of nsTArray to create, so that it can create an AutoTArray as well; an
 | |
|   // TransformIf (without AbortOnErr) might be added, which could be used here.
 | |
|   DeleteOrphanedBodyAction::DeletedBodyIdList deleteNowList;
 | |
|   deleteNowList.SetCapacity(aDeletedBodyIdList.Length());
 | |
| 
 | |
|   std::copy_if(aDeletedBodyIdList.cbegin(), aDeletedBodyIdList.cend(),
 | |
|                MakeBackInserter(deleteNowList),
 | |
|                [this](const auto& deletedBodyId) {
 | |
|                  return !SetBodyIdOrphanedIfRefed(deletedBodyId);
 | |
|                });
 | |
| 
 | |
|   // TODO: note that we need to check these bodies for staleness on startup (bug
 | |
|   // 1110446)
 | |
|   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|   if (!deleteNowList.IsEmpty() && pinnedContext &&
 | |
|       !pinnedContext->IsCanceled()) {
 | |
|     pinnedContext->Dispatch(
 | |
|         MakeSafeRefPtr<DeleteOrphanedBodyAction>(std::move(deleteNowList)));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Manager::MaybeAllowContextToClose() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Manager);
 | |
| 
 | |
|   // If we have an active context, but we have no more users of the Manager,
 | |
|   // then let it shut itself down.  We must wait for all possible users of
 | |
|   // Cache state information to complete before doing this.  Once we allow
 | |
|   // the Context to close we may not reliably get notified of storage
 | |
|   // invalidation.
 | |
|   const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
 | |
|   if (pinnedContext && mListeners.IsEmpty() && mCacheIdRefs.IsEmpty() &&
 | |
|       mBodyIdRefs.IsEmpty()) {
 | |
|     // Mark this Manager as invalid so that it won't get used again.  We don't
 | |
|     // want to start any new operations once we allow the Context to close since
 | |
|     // it may race with the underlying storage getting invalidated.
 | |
|     NoteClosing();
 | |
| 
 | |
|     pinnedContext->AllowToClose();
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom::cache
 | 
