mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 13:21:05 +00:00
9db2246cff
Differential Revision: https://phabricator.services.mozilla.com/D139690
2129 lines
72 KiB
C++
2129 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
|