Source code

Revision control

Other Tools

/* -*- 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 QuotaInfo& aQuotaInfo, 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(aQuotaInfo)) {
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, &aQuotaInfo, &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(
aQuotaInfo, *aDBDir, deletionInfo.mDeletedBodyIdList)));
if (deletionInfo.mDeletedPaddingSize > 0) {
DecreaseUsageForQuotaInfo(aQuotaInfo,
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(aQuotaInfo, *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 QuotaInfo& aQuotaInfo,
Data*) override {
MOZ_DIAGNOSTIC_ASSERT(aResolver);
MOZ_DIAGNOSTIC_ASSERT(aQuotaInfo.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(*aQuotaInfo.mDir, u"cache"_ns), QM_VOID,
resolve);
QM_TRY(
MOZ_TO_RESULT(BodyDeleteFiles(aQuotaInfo, *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;
}
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(" )");
}
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;
};
// 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 QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
mozIStorageConnection* aConn) override {
mQuotaInfo.emplace(aQuotaInfo);
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) {
DecreaseUsageForQuotaInfo(mQuotaInfo.ref(),
mDeletionInfo.mDeletedPaddingSize);
}
// ensure we release the manager on the initiating thread
mManager = nullptr;
}
private:
SafeRefPtr<Manager> mManager;
const CacheId mCacheId;
DeletionInfo mDeletionInfo;
Maybe<QuotaInfo> mQuotaInfo;
};
// ----------------------------------------------------------------------------
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 QuotaInfo& aQuotaInfo, 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(aQuotaInfo, *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 QuotaInfo& aQuotaInfo, 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(aQuotaInfo, *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 QuotaInfo& aQuotaInfo, 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;
mQuotaInfo.emplace(aQuotaInfo);
// 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, &aQuotaInfo]() -> nsresult {
QM_TRY(CollectEachInRange(
mList, [this, &aQuotaInfo](auto& entry) -> nsresult {
QM_TRY(
MOZ_TO_RESULT(StartStreamCopy(aQuotaInfo, entry, RequestStream,
&mExpectedAsyncCopyCompletions)));
QM_TRY(
MOZ_TO_RESULT(StartStreamCopy(aQuotaInfo, 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(
mQuotaInfo.ref(), *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) {
DecreaseUsageForQuotaInfo(mQuotaInfo.ref(), 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 QuotaInfo& aQuotaInfo, 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(aQuotaInfo, *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(mQuotaInfo.ref(), *mDBDir, mBodyIdWrittenList);
if (mUpdatedPaddingSize > 0) {
DecreaseUsageForQuotaInfo(mQuotaInfo.ref(), 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;
nsTArray<nsCOMPtr<nsISupports>> mCopyContextList;
Maybe<QuotaInfo> mQuotaInfo;
// 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 QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
mozIStorageConnection* aConn) override {
mQuotaInfo.emplace(aQuotaInfo);
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) {
DecreaseUsageForQuotaInfo(mQuotaInfo.ref(),
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<QuotaInfo> mQuotaInfo;
};
// ----------------------------------------------------------------------------
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 QuotaInfo& aQuotaInfo, 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(aQuotaInfo, *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 QuotaInfo& aQuotaInfo, 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(aQuotaInfo, *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 QuotaInfo& aQuotaInfo, 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 QuotaInfo& aQuotaInfo, 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 QuotaInfo& aQuotaInfo, 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 QuotaInfo& aQuotaInfo, 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 QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
mozIStorageConnection* aConn) override {
MOZ_DIAGNOSTIC_ASSERT(aDBDir);
QM_TRY_UNWRAP(mBodyStream, BodyOpen(aQuotaInfo, *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::ShutdownAll();
}
// static
bool Manager::IsShutdownAllComplete() {
mozilla::ipc::AssertIsOnBackgroundThread();
return Factory::IsShutdownAllComplete();
}
// 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);
}