Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / cache / Manager.cpp
blob68a19e5862346d3001b8ad180ffab8060e5c0165
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/cache/Manager.h"
9 #include "mozilla/AppShutdown.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/AutoRestore.h"
12 #include "mozilla/Mutex.h"
13 #include "mozilla/StaticMutex.h"
14 #include "mozilla/StaticPtr.h"
15 #include "mozilla/Unused.h"
16 #include "mozilla/dom/cache/Context.h"
17 #include "mozilla/dom/cache/DBAction.h"
18 #include "mozilla/dom/cache/DBSchema.h"
19 #include "mozilla/dom/cache/FileUtils.h"
20 #include "mozilla/dom/cache/ManagerId.h"
21 #include "mozilla/dom/cache/CacheTypes.h"
22 #include "mozilla/dom/cache/SavedTypes.h"
23 #include "mozilla/dom/cache/StreamList.h"
24 #include "mozilla/dom/cache/Types.h"
25 #include "mozilla/dom/quota/Client.h"
26 #include "mozilla/dom/quota/ClientImpl.h"
27 #include "mozilla/dom/quota/StringifyUtils.h"
28 #include "mozilla/dom/quota/QuotaManager.h"
29 #include "mozilla/ipc/BackgroundParent.h"
30 #include "mozStorageHelper.h"
31 #include "nsIInputStream.h"
32 #include "nsID.h"
33 #include "nsIFile.h"
34 #include "nsIThread.h"
35 #include "nsIUUIDGenerator.h"
36 #include "nsThreadUtils.h"
37 #include "nsTObserverArray.h"
38 #include "QuotaClientImpl.h"
39 #include "Types.h"
41 namespace mozilla::dom::cache {
43 using mozilla::dom::quota::CloneFileAndAppend;
44 using mozilla::dom::quota::DirectoryLock;
46 namespace {
48 /**
49 * Note: The aCommitHook argument will be invoked while a lock is held. Callers
50 * should be careful not to pass a hook that might lock on something else and
51 * trigger a deadlock.
53 template <typename Callable>
54 nsresult MaybeUpdatePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn,
55 const int64_t aIncreaseSize,
56 const int64_t aDecreaseSize,
57 Callable aCommitHook) {
58 MOZ_ASSERT(!NS_IsMainThread());
59 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
60 MOZ_DIAGNOSTIC_ASSERT(aConn);
61 MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
62 MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
64 RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
65 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
67 QM_TRY(MOZ_TO_RESULT(cacheQuotaClient->MaybeUpdatePaddingFileInternal(
68 *aBaseDir, *aConn, aIncreaseSize, aDecreaseSize, aCommitHook)));
70 return NS_OK;
73 Maybe<CipherKey> GetOrCreateCipherKey(NotNull<Context*> aContext,
74 const nsID& aBodyId, bool aCreate) {
75 const auto& maybeMetadata = aContext->MaybeCacheDirectoryMetadataRef();
76 MOZ_DIAGNOSTIC_ASSERT(maybeMetadata);
78 auto privateOrigin = maybeMetadata->mIsPrivate;
79 if (!privateOrigin) {
80 return Nothing{};
83 nsCString bodyIdStr{aBodyId.ToString().get()};
85 auto& cipherKeyManager = aContext->MutableCipherKeyManagerRef();
87 return aCreate ? Some(cipherKeyManager.Ensure(bodyIdStr))
88 : cipherKeyManager.Get(bodyIdStr);
91 // An Action that is executed when a Context is first created. It ensures that
92 // the directory and database are setup properly. This lets other actions
93 // not worry about these details.
94 class SetupAction final : public SyncDBAction {
95 public:
96 SetupAction() : SyncDBAction(DBAction::Create) {}
98 virtual nsresult RunSyncWithDBOnTarget(
99 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
100 mozIStorageConnection* aConn) override {
101 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
103 QM_TRY(MOZ_TO_RESULT(BodyCreateDir(*aDBDir)));
105 // executes in its own transaction
106 QM_TRY(MOZ_TO_RESULT(db::CreateOrMigrateSchema(*aDBDir, *aConn)));
108 // If the Context marker file exists, then the last session was
109 // not cleanly shutdown. In these cases sqlite will ensure that
110 // the database is valid, but we might still orphan data. Both
111 // Cache objects and body files can be referenced by DOM objects
112 // after they are "removed" from their parent. So we need to
113 // look and see if any of these late access objects have been
114 // orphaned.
116 // Note, this must be done after any schema version updates to
117 // ensure our DBSchema methods work correctly.
118 if (MarkerFileExists(aDirectoryMetadata)) {
119 NS_WARNING("Cache not shutdown cleanly! Cleaning up stale data...");
120 mozStorageTransaction trans(aConn, false,
121 mozIStorageConnection::TRANSACTION_IMMEDIATE);
123 QM_TRY(MOZ_TO_RESULT(trans.Start()));
125 // Clean up orphaned Cache objects
126 QM_TRY_INSPECT(const auto& orphanedCacheIdList,
127 db::FindOrphanedCacheIds(*aConn));
129 QM_TRY_INSPECT(
130 const CheckedInt64& overallDeletedPaddingSize,
131 Reduce(
132 orphanedCacheIdList, CheckedInt64(0),
133 [aConn, &aDirectoryMetadata, &aDBDir](
134 CheckedInt64 oldValue, const Maybe<const CacheId&>& element)
135 -> Result<CheckedInt64, nsresult> {
136 QM_TRY_INSPECT(const auto& deletionInfo,
137 db::DeleteCacheId(*aConn, *element));
139 QM_TRY(MOZ_TO_RESULT(
140 BodyDeleteFiles(aDirectoryMetadata, *aDBDir,
141 deletionInfo.mDeletedBodyIdList)));
143 if (deletionInfo.mDeletedPaddingSize > 0) {
144 DecreaseUsageForDirectoryMetadata(
145 aDirectoryMetadata, deletionInfo.mDeletedPaddingSize);
148 return oldValue + deletionInfo.mDeletedPaddingSize;
149 }));
151 // Clean up orphaned body objects
152 QM_TRY_INSPECT(const auto& knownBodyIdList, db::GetKnownBodyIds(*aConn));
154 QM_TRY(MOZ_TO_RESULT(BodyDeleteOrphanedFiles(aDirectoryMetadata, *aDBDir,
155 knownBodyIdList)));
157 // Commit() explicitly here, because we want to ensure the padding file
158 // has the correct content.
159 // We'll restore padding file below, so just warn here if failure happens.
161 // XXX Before, if MaybeUpdatePaddingFile failed but we didn't enter the if
162 // body below, we would have propagated the MaybeUpdatePaddingFile
163 // failure, but if we entered it and RestorePaddingFile succeeded, we
164 // would have returned NS_OK. Now, we will never propagate a
165 // MaybeUpdatePaddingFile failure.
166 QM_WARNONLY_TRY(QM_TO_RESULT(
167 MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0,
168 overallDeletedPaddingSize.value(),
169 [&trans]() { return trans.Commit(); })));
172 if (DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::TMP_FILE) ||
173 !DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::FILE)) {
174 QM_TRY(MOZ_TO_RESULT(RestorePaddingFile(aDBDir, aConn)));
177 return NS_OK;
181 // ----------------------------------------------------------------------------
183 // Action that is executed when we determine that content has stopped using
184 // a body file that has been orphaned.
185 class DeleteOrphanedBodyAction final : public Action {
186 public:
187 using DeletedBodyIdList = AutoTArray<nsID, 64>;
189 explicit DeleteOrphanedBodyAction(DeletedBodyIdList&& aDeletedBodyIdList)
190 : mDeletedBodyIdList(std::move(aDeletedBodyIdList)) {}
192 explicit DeleteOrphanedBodyAction(const nsID& aBodyId)
193 : mDeletedBodyIdList{aBodyId} {}
195 void RunOnTarget(SafeRefPtr<Resolver> aResolver,
196 const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
197 Data*,
198 const Maybe<CipherKey>& /*aMaybeCipherKey*/) override {
199 MOZ_DIAGNOSTIC_ASSERT(aResolver);
200 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata);
201 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir);
203 // Note that since DeleteOrphanedBodyAction isn't used while the context is
204 // being initialized, we don't need to check for cancellation here.
206 const auto resolve = [&aResolver](const nsresult rv) {
207 aResolver->Resolve(rv);
210 QM_TRY_INSPECT(const auto& dbDir,
211 CloneFileAndAppend(*aDirectoryMetadata->mDir, u"cache"_ns),
212 QM_VOID, resolve);
214 QM_TRY(MOZ_TO_RESULT(BodyDeleteFiles(*aDirectoryMetadata, *dbDir,
215 mDeletedBodyIdList)),
216 QM_VOID, resolve);
218 aResolver->Resolve(NS_OK);
221 private:
222 DeletedBodyIdList mDeletedBodyIdList;
225 bool IsHeadRequest(const CacheRequest& aRequest,
226 const CacheQueryParams& aParams) {
227 return !aParams.ignoreMethod() &&
228 aRequest.method().LowerCaseEqualsLiteral("head");
231 bool IsHeadRequest(const Maybe<CacheRequest>& aRequest,
232 const CacheQueryParams& aParams) {
233 if (aRequest.isSome()) {
234 return !aParams.ignoreMethod() &&
235 aRequest.ref().method().LowerCaseEqualsLiteral("head");
237 return false;
240 auto MatchByCacheId(CacheId aCacheId) {
241 return [aCacheId](const auto& entry) { return entry.mCacheId == aCacheId; };
244 auto MatchByBodyId(const nsID& aBodyId) {
245 return [&aBodyId](const auto& entry) { return entry.mBodyId == aBodyId; };
248 } // namespace
250 // ----------------------------------------------------------------------------
252 // Singleton class to track Manager instances and ensure there is only
253 // one for each unique ManagerId.
254 class Manager::Factory {
255 public:
256 friend class StaticAutoPtr<Manager::Factory>;
258 static Result<SafeRefPtr<Manager>, nsresult> AcquireCreateIfNonExistent(
259 const SafeRefPtr<ManagerId>& aManagerId) {
260 mozilla::ipc::AssertIsOnBackgroundThread();
262 // If we get here during/after quota manager shutdown, we bail out.
263 MOZ_ASSERT(AppShutdown::GetCurrentShutdownPhase() <
264 ShutdownPhase::AppShutdownQM);
265 if (AppShutdown::GetCurrentShutdownPhase() >=
266 ShutdownPhase::AppShutdownQM) {
267 NS_WARNING(
268 "Attempt to AcquireCreateIfNonExistent a Manager during QM "
269 "shutdown.");
270 return Err(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
273 // Ensure there is a factory instance. This forces the Acquire() call
274 // below to use the same factory.
275 QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()));
277 SafeRefPtr<Manager> ref = Acquire(*aManagerId);
278 if (!ref) {
279 // TODO: replace this with a thread pool (bug 1119864)
280 // XXX Can't use QM_TRY_INSPECT because that causes a clang-plugin
281 // error of the NoNewThreadsChecker.
282 nsCOMPtr<nsIThread> ioThread;
283 QM_TRY(MOZ_TO_RESULT(
284 NS_NewNamedThread("DOMCacheThread", getter_AddRefs(ioThread))));
286 ref = MakeSafeRefPtr<Manager>(aManagerId.clonePtr(), ioThread,
287 ConstructorGuard{});
289 // There may be an old manager for this origin in the process of
290 // cleaning up. We need to tell the new manager about this so
291 // that it won't actually start until the old manager is done.
292 const SafeRefPtr<Manager> oldManager = Acquire(*aManagerId, Closing);
293 ref->Init(oldManager.maybeDeref());
295 MOZ_ASSERT(!sFactory->mManagerList.Contains(ref));
296 sFactory->mManagerList.AppendElement(
297 WrapNotNullUnchecked(ref.unsafeGetRawPtr()));
300 return ref;
303 static void Remove(Manager& aManager) {
304 mozilla::ipc::AssertIsOnBackgroundThread();
305 MOZ_DIAGNOSTIC_ASSERT(sFactory);
307 MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(&aManager));
309 // This might both happen in late shutdown such that this event
310 // is executed even after the QuotaManager singleton passed away
311 // or if the QuotaManager has not yet been created.
312 quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
313 quota::Client::DOMCACHE, "Manager removed"_ns);
315 // clean up the factory singleton if there are no more managers
316 MaybeDestroyInstance();
319 static void Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
320 mozilla::ipc::AssertIsOnBackgroundThread();
322 AbortMatching([&aDirectoryLockIds](const auto& manager) {
323 // Check if the Manager holds an acquired DirectoryLock. Origin clearing
324 // can't be blocked by this Manager if there is no acquired DirectoryLock.
325 // If there is an acquired DirectoryLock, check if the table contains the
326 // lock for the Manager.
327 return Client::IsLockForObjectAcquiredAndContainedInLockTable(
328 manager, aDirectoryLockIds);
332 static void AbortAll() {
333 mozilla::ipc::AssertIsOnBackgroundThread();
335 AbortMatching([](const auto&) { return true; });
338 static void ShutdownAll() {
339 mozilla::ipc::AssertIsOnBackgroundThread();
341 if (!sFactory) {
342 return;
345 MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
348 // Note that we are synchronously calling shutdown code here. If any
349 // of the shutdown code synchronously decides to delete the Factory
350 // we need to delay that delete until the end of this method.
351 AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
352 sFactory->mInSyncAbortOrShutdown = true;
354 for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
355 auto pinnedManager =
356 SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
357 pinnedManager->Shutdown();
361 MaybeDestroyInstance();
364 static bool IsShutdownAllComplete() {
365 mozilla::ipc::AssertIsOnBackgroundThread();
366 return !sFactory;
369 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
370 static void RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId) {
371 if (sFactory) {
372 sFactory->mPotentiallyUnreleasedCSCP.AppendElement(
373 aCacheStreamControlParentId);
377 static void RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId) {
378 if (sFactory) {
379 sFactory->mPotentiallyUnreleasedCSCP.RemoveElement(
380 aCacheStreamControlParentId);
383 #endif
384 static nsCString GetShutdownStatus() {
385 mozilla::ipc::AssertIsOnBackgroundThread();
387 nsCString data;
389 if (sFactory && !sFactory->mManagerList.IsEmpty()) {
390 data.Append(
391 "ManagerList: "_ns +
392 IntToCString(static_cast<uint64_t>(sFactory->mManagerList.Length())) +
393 kStringifyStartSet);
395 for (const auto& manager : sFactory->mManagerList.NonObservingRange()) {
396 manager->Stringify(data);
399 data.Append(kStringifyEndSet);
400 if (sFactory->mPotentiallyUnreleasedCSCP.Length() > 0) {
401 data.Append(
402 "There have been CSCP instances whose"
403 "Send__delete__ might not have freed them.");
407 return data;
410 private:
411 Factory() : mInSyncAbortOrShutdown(false) {
412 MOZ_COUNT_CTOR(cache::Manager::Factory);
415 ~Factory() {
416 MOZ_COUNT_DTOR(cache::Manager::Factory);
417 MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
418 MOZ_DIAGNOSTIC_ASSERT(!mInSyncAbortOrShutdown);
421 static nsresult MaybeCreateInstance() {
422 mozilla::ipc::AssertIsOnBackgroundThread();
424 if (!sFactory) {
425 // We cannot use ClearOnShutdown() here because we're not on the main
426 // thread. Instead, we delete sFactory in Factory::Remove() after the
427 // last manager is removed. ShutdownObserver ensures this happens
428 // before shutdown.
429 sFactory = new Factory();
432 // Never return sFactory to code outside Factory. We need to delete it
433 // out from under ourselves just before we return from Remove(). This
434 // would be (even more) dangerous if other code had a pointer to the
435 // factory itself.
437 return NS_OK;
440 static void MaybeDestroyInstance() {
441 mozilla::ipc::AssertIsOnBackgroundThread();
442 MOZ_DIAGNOSTIC_ASSERT(sFactory);
444 // If the factory is is still in use then we cannot delete yet. This
445 // could be due to managers still existing or because we are in the
446 // middle of aborting or shutting down. We need to be careful not to delete
447 // ourself synchronously during shutdown.
448 if (!sFactory->mManagerList.IsEmpty() || sFactory->mInSyncAbortOrShutdown) {
449 return;
452 sFactory = nullptr;
455 static SafeRefPtr<Manager> Acquire(const ManagerId& aManagerId,
456 State aState = Open) {
457 mozilla::ipc::AssertIsOnBackgroundThread();
459 QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()), nullptr);
461 // Iterate in reverse to find the most recent, matching Manager. This
462 // is important when looking for a Closing Manager. If a new Manager
463 // chains to an old Manager we want it to be the most recent one.
464 const auto range = Reversed(sFactory->mManagerList.NonObservingRange());
465 const auto foundIt = std::find_if(
466 range.begin(), range.end(), [aState, &aManagerId](const auto& manager) {
467 return aState == manager->GetState() &&
468 *manager->mManagerId == aManagerId;
470 return foundIt != range.end()
471 ? SafeRefPtr{foundIt->get(), AcquireStrongRefFromRawPtr{}}
472 : nullptr;
475 template <typename Condition>
476 static void AbortMatching(const Condition& aCondition) {
477 mozilla::ipc::AssertIsOnBackgroundThread();
479 if (!sFactory) {
480 return;
483 MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
486 // Note that we are synchronously calling abort code here. If any
487 // of the shutdown code synchronously decides to delete the Factory
488 // we need to delay that delete until the end of this method.
489 AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
490 sFactory->mInSyncAbortOrShutdown = true;
492 for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
493 if (aCondition(*manager)) {
494 auto pinnedManager =
495 SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
496 pinnedManager->Abort();
501 MaybeDestroyInstance();
504 // Singleton created on demand and deleted when last Manager is cleared
505 // in Remove().
506 // PBackground thread only.
507 static StaticAutoPtr<Factory> sFactory;
509 // Weak references as we don't want to keep Manager objects alive forever.
510 // When a Manager is destroyed it calls Factory::Remove() to clear itself.
511 // PBackground thread only.
512 nsTObserverArray<NotNull<Manager*>> mManagerList;
514 // This flag is set when we are looping through the list and calling Abort()
515 // or Shutdown() on each Manager. We need to be careful not to synchronously
516 // trigger the deletion of the factory while still executing this loop.
517 bool mInSyncAbortOrShutdown;
519 nsTArray<int32_t> mPotentiallyUnreleasedCSCP;
522 // static
523 StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
525 // ----------------------------------------------------------------------------
527 // Abstract class to help implement the various Actions. The vast majority
528 // of Actions are synchronous and need to report back to a Listener on the
529 // Manager.
530 class Manager::BaseAction : public SyncDBAction {
531 protected:
532 BaseAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId)
533 : SyncDBAction(DBAction::Existing),
534 mManager(std::move(aManager)),
535 mListenerId(aListenerId) {}
537 virtual void Complete(Listener* aListener, ErrorResult&& aRv) = 0;
539 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
540 NS_ASSERT_OWNINGTHREAD(Manager::BaseAction);
541 Listener* listener = mManager->GetListener(mListenerId);
542 if (listener) {
543 Complete(listener, ErrorResult(aRv));
546 // ensure we release the manager on the initiating thread
547 mManager = nullptr;
550 SafeRefPtr<Manager> mManager;
551 const ListenerId mListenerId;
554 // ----------------------------------------------------------------------------
556 // Action that is executed when we determine that content has stopped using
557 // a Cache object that has been orphaned.
558 class Manager::DeleteOrphanedCacheAction final : public SyncDBAction {
559 public:
560 DeleteOrphanedCacheAction(SafeRefPtr<Manager> aManager, CacheId aCacheId)
561 : SyncDBAction(DBAction::Existing),
562 mManager(std::move(aManager)),
563 mCacheId(aCacheId) {}
565 virtual nsresult RunSyncWithDBOnTarget(
566 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
567 mozIStorageConnection* aConn) override {
568 mDirectoryMetadata.emplace(aDirectoryMetadata);
570 mozStorageTransaction trans(aConn, false,
571 mozIStorageConnection::TRANSACTION_IMMEDIATE);
573 QM_TRY(MOZ_TO_RESULT(trans.Start()));
575 QM_TRY_UNWRAP(mDeletionInfo, db::DeleteCacheId(*aConn, mCacheId));
577 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
578 aDBDir, aConn, /* aIncreaceSize */ 0, mDeletionInfo.mDeletedPaddingSize,
579 [&trans]() mutable { return trans.Commit(); })));
581 return NS_OK;
584 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
585 // If the transaction fails, we shouldn't delete the body files and decrease
586 // their padding size.
587 if (NS_FAILED(aRv)) {
588 mDeletionInfo.mDeletedBodyIdList.Clear();
589 mDeletionInfo.mDeletedPaddingSize = 0;
592 mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
594 if (mDeletionInfo.mDeletedPaddingSize > 0) {
595 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
596 mDeletionInfo.mDeletedPaddingSize);
599 // ensure we release the manager on the initiating thread
600 mManager = nullptr;
603 private:
604 SafeRefPtr<Manager> mManager;
605 const CacheId mCacheId;
606 DeletionInfo mDeletionInfo;
607 Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
610 // ----------------------------------------------------------------------------
612 class Manager::CacheMatchAction final : public Manager::BaseAction {
613 public:
614 CacheMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
615 CacheId aCacheId, const CacheMatchArgs& aArgs,
616 SafeRefPtr<StreamList> aStreamList)
617 : BaseAction(std::move(aManager), aListenerId),
618 mCacheId(aCacheId),
619 mArgs(aArgs),
620 mStreamList(std::move(aStreamList)),
621 mFoundResponse(false) {}
623 virtual nsresult RunSyncWithDBOnTarget(
624 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
625 mozIStorageConnection* aConn) override {
626 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
628 QM_TRY_INSPECT(
629 const auto& maybeResponse,
630 db::CacheMatch(*aConn, mCacheId, mArgs.request(), mArgs.params()));
632 mFoundResponse = maybeResponse.isSome();
633 if (mFoundResponse) {
634 mResponse = std::move(maybeResponse.ref());
637 if (!mFoundResponse || !mResponse.mHasBodyId ||
638 IsHeadRequest(mArgs.request(), mArgs.params())) {
639 mResponse.mHasBodyId = false;
640 return NS_OK;
643 const auto& bodyId = mResponse.mBodyId;
645 nsCOMPtr<nsIInputStream> stream;
646 if (mArgs.openMode() == OpenMode::Eager) {
647 QM_TRY_UNWRAP(
648 stream,
649 BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
650 GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId,
651 /* aCreate */ false)));
654 // If we entered shutdown on the main thread while we were doing IO,
655 // bail out now.
656 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) {
657 if (stream) {
658 stream->Close();
660 return NS_ERROR_ABORT;
663 mStreamList->Add(mResponse.mBodyId, std::move(stream));
665 return NS_OK;
668 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
669 if (!mFoundResponse) {
670 aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()));
671 } else {
672 mStreamList->Activate(mCacheId);
673 aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()),
674 mResponse, *mStreamList);
676 mStreamList = nullptr;
679 virtual bool MatchesCacheId(CacheId aCacheId) const override {
680 return aCacheId == mCacheId;
683 private:
684 const CacheId mCacheId;
685 const CacheMatchArgs mArgs;
686 SafeRefPtr<StreamList> mStreamList;
687 bool mFoundResponse;
688 SavedResponse mResponse;
691 // ----------------------------------------------------------------------------
693 class Manager::CacheMatchAllAction final : public Manager::BaseAction {
694 public:
695 CacheMatchAllAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
696 CacheId aCacheId, const CacheMatchAllArgs& aArgs,
697 SafeRefPtr<StreamList> aStreamList)
698 : BaseAction(std::move(aManager), aListenerId),
699 mCacheId(aCacheId),
700 mArgs(aArgs),
701 mStreamList(std::move(aStreamList)) {}
703 virtual nsresult RunSyncWithDBOnTarget(
704 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
705 mozIStorageConnection* aConn) override {
706 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
708 QM_TRY_UNWRAP(mSavedResponses,
709 db::CacheMatchAll(*aConn, mCacheId, mArgs.maybeRequest(),
710 mArgs.params()));
712 for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) {
713 if (!mSavedResponses[i].mHasBodyId ||
714 IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
715 mSavedResponses[i].mHasBodyId = false;
716 continue;
719 const auto& bodyId = mSavedResponses[i].mBodyId;
721 nsCOMPtr<nsIInputStream> stream;
722 if (mArgs.openMode() == OpenMode::Eager) {
723 QM_TRY_UNWRAP(stream,
724 BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
725 GetOrCreateCipherKey(
726 WrapNotNull(mManager->mContext), bodyId,
727 /* aCreate */ false)));
730 // If we entered shutdown on the main thread while we were doing IO,
731 // bail out now.
732 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) {
733 if (stream) {
734 stream->Close();
736 return NS_ERROR_ABORT;
739 mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream));
742 return NS_OK;
745 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
746 mStreamList->Activate(mCacheId);
747 aListener->OnOpComplete(std::move(aRv), CacheMatchAllResult(),
748 mSavedResponses, *mStreamList);
749 mStreamList = nullptr;
752 virtual bool MatchesCacheId(CacheId aCacheId) const override {
753 return aCacheId == mCacheId;
756 private:
757 const CacheId mCacheId;
758 const CacheMatchAllArgs mArgs;
759 SafeRefPtr<StreamList> mStreamList;
760 nsTArray<SavedResponse> mSavedResponses;
763 // ----------------------------------------------------------------------------
765 // This is the most complex Action. It puts a request/response pair into the
766 // Cache. It does not complete until all of the body data has been saved to
767 // disk. This means its an asynchronous Action.
768 class Manager::CachePutAllAction final : public DBAction {
769 public:
770 CachePutAllAction(
771 SafeRefPtr<Manager> aManager, ListenerId aListenerId, CacheId aCacheId,
772 const nsTArray<CacheRequestResponse>& aPutList,
773 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
774 const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
775 : DBAction(DBAction::Existing),
776 mManager(std::move(aManager)),
777 mListenerId(aListenerId),
778 mCacheId(aCacheId),
779 mList(aPutList.Length()),
780 mExpectedAsyncCopyCompletions(1),
781 mAsyncResult(NS_OK),
782 mMutex("cache::Manager::CachePutAllAction"),
783 mUpdatedPaddingSize(0),
784 mDeletedPaddingSize(0) {
785 MOZ_DIAGNOSTIC_ASSERT(!aPutList.IsEmpty());
786 MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aRequestStreamList.Length());
787 MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aResponseStreamList.Length());
789 for (uint32_t i = 0; i < aPutList.Length(); ++i) {
790 Entry* entry = mList.AppendElement();
791 entry->mRequest = aPutList[i].request();
792 entry->mRequestStream = aRequestStreamList[i];
793 entry->mResponse = aPutList[i].response();
794 entry->mResponseStream = aResponseStreamList[i];
798 private:
799 ~CachePutAllAction() = default;
801 virtual void RunWithDBOnTarget(
802 SafeRefPtr<Resolver> aResolver,
803 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
804 mozIStorageConnection* aConn) override {
805 MOZ_DIAGNOSTIC_ASSERT(aResolver);
806 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
807 MOZ_DIAGNOSTIC_ASSERT(aConn);
808 MOZ_DIAGNOSTIC_ASSERT(!mResolver);
809 MOZ_DIAGNOSTIC_ASSERT(!mDBDir);
810 MOZ_DIAGNOSTIC_ASSERT(!mConn);
812 MOZ_DIAGNOSTIC_ASSERT(!mTarget);
813 mTarget = GetCurrentSerialEventTarget();
814 MOZ_DIAGNOSTIC_ASSERT(mTarget);
816 // We should be pre-initialized to expect one async completion. This is
817 // the "manual" completion we call at the end of this method in all
818 // cases.
819 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions == 1);
821 mResolver = std::move(aResolver);
822 mDBDir = aDBDir;
823 mConn = aConn;
824 mDirectoryMetadata.emplace(aDirectoryMetadata);
826 // File bodies are streamed to disk via asynchronous copying. Start
827 // this copying now. Each copy will eventually result in a call
828 // to OnAsyncCopyComplete().
829 const nsresult rv = [this, &aDirectoryMetadata]() -> nsresult {
830 QM_TRY(CollectEachInRange(
831 mList, [this, &aDirectoryMetadata](auto& entry) -> nsresult {
832 QM_TRY(MOZ_TO_RESULT(
833 StartStreamCopy(aDirectoryMetadata, entry, RequestStream,
834 &mExpectedAsyncCopyCompletions)));
836 QM_TRY(MOZ_TO_RESULT(
837 StartStreamCopy(aDirectoryMetadata, entry, ResponseStream,
838 &mExpectedAsyncCopyCompletions)));
840 return NS_OK;
841 }));
843 return NS_OK;
844 }();
846 // Always call OnAsyncCopyComplete() manually here. This covers the
847 // case where there is no async copying and also reports any startup
848 // errors correctly. If we hit an error, then OnAsyncCopyComplete()
849 // will cancel any async copying.
850 OnAsyncCopyComplete(rv);
853 // Called once for each asynchronous file copy whether it succeeds or
854 // fails. If a file copy is canceled, it still calls this method with
855 // an error code.
856 void OnAsyncCopyComplete(nsresult aRv) {
857 MOZ_ASSERT(mTarget->IsOnCurrentThread());
858 MOZ_DIAGNOSTIC_ASSERT(mConn);
859 MOZ_DIAGNOSTIC_ASSERT(mResolver);
860 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions > 0);
862 // Explicitly check for cancellation here to catch a race condition.
863 // Consider:
865 // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
866 // copy context yet.
867 // 2) CancelAllStreamCopying() occurs on PBackground thread
868 // 3) Copy context from (1) is saved on IO thread.
870 // Checking for cancellation here catches this condition when we
871 // first call OnAsyncCopyComplete() manually from RunWithDBOnTarget().
873 // This explicit cancellation check also handles the case where we
874 // are canceled just after all stream copying completes. We should
875 // abort the synchronous DB operations in this case if we have not
876 // started them yet.
877 if (NS_SUCCEEDED(aRv) && IsCanceled()) {
878 aRv = NS_ERROR_ABORT;
881 // If any of the async copies fail, we need to still wait for them all to
882 // complete. Cancel any other streams still working and remember the
883 // error. All canceled streams will call OnAsyncCopyComplete().
884 if (NS_FAILED(aRv) && NS_SUCCEEDED(mAsyncResult)) {
885 CancelAllStreamCopying();
886 mAsyncResult = aRv;
889 // Check to see if async copying is still on-going. If so, then simply
890 // return for now. We must wait for a later OnAsyncCopyComplete() call.
891 mExpectedAsyncCopyCompletions -= 1;
892 if (mExpectedAsyncCopyCompletions > 0) {
893 return;
896 // We have finished with all async copying. Indicate this by clearing all
897 // our copy contexts.
899 MutexAutoLock lock(mMutex);
900 mCopyContextList.Clear();
903 // An error occurred while async copying. Terminate the Action.
904 // DoResolve() will clean up any files we may have written.
905 if (NS_FAILED(mAsyncResult)) {
906 DoResolve(mAsyncResult);
907 return;
910 mozStorageTransaction trans(mConn, false,
911 mozIStorageConnection::TRANSACTION_IMMEDIATE);
913 QM_TRY(MOZ_TO_RESULT(trans.Start()), QM_VOID);
915 const nsresult rv = [this, &trans]() -> nsresult {
916 QM_TRY(CollectEachInRange(mList, [this](Entry& e) -> nsresult {
917 if (e.mRequestStream) {
918 QM_TRY_UNWRAP(int64_t bodyDiskSize,
919 BodyFinalizeWrite(*mDBDir, e.mRequestBodyId));
920 e.mRequest.bodyDiskSize() = bodyDiskSize;
921 } else {
922 e.mRequest.bodyDiskSize() = 0;
924 if (e.mResponseStream) {
925 // Gerenate padding size for opaque response if needed.
926 if (e.mResponse.type() == ResponseType::Opaque) {
927 // It'll generate padding if we've not set it yet.
928 QM_TRY(MOZ_TO_RESULT(BodyMaybeUpdatePaddingSize(
929 *mDirectoryMetadata, *mDBDir, e.mResponseBodyId,
930 e.mResponse.paddingInfo(), &e.mResponse.paddingSize())));
932 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - e.mResponse.paddingSize() >=
933 mUpdatedPaddingSize);
934 mUpdatedPaddingSize += e.mResponse.paddingSize();
937 QM_TRY_UNWRAP(int64_t bodyDiskSize,
938 BodyFinalizeWrite(*mDBDir, e.mResponseBodyId));
939 e.mResponse.bodyDiskSize() = bodyDiskSize;
940 } else {
941 e.mResponse.bodyDiskSize() = 0;
944 QM_TRY_UNWRAP(
945 auto deletionInfo,
946 db::CachePut(*mConn, mCacheId, e.mRequest,
947 e.mRequestStream ? &e.mRequestBodyId : nullptr,
948 e.mResponse,
949 e.mResponseStream ? &e.mResponseBodyId : nullptr));
951 const int64_t deletedPaddingSize = deletionInfo.mDeletedPaddingSize;
952 mDeletedBodyIdList = std::move(deletionInfo.mDeletedBodyIdList);
954 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - mDeletedPaddingSize >=
955 deletedPaddingSize);
956 mDeletedPaddingSize += deletedPaddingSize;
958 return NS_OK;
959 }));
961 // Update padding file when it's necessary
962 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
963 mDBDir, mConn, mUpdatedPaddingSize, mDeletedPaddingSize,
964 [&trans]() mutable { return trans.Commit(); })));
966 return NS_OK;
967 }();
969 DoResolve(rv);
972 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
973 NS_ASSERT_OWNINGTHREAD(Action);
975 for (uint32_t i = 0; i < mList.Length(); ++i) {
976 mList[i].mRequestStream = nullptr;
977 mList[i].mResponseStream = nullptr;
980 // If the transaction fails, we shouldn't delete the body files and decrease
981 // their padding size.
982 if (NS_FAILED(aRv)) {
983 mDeletedBodyIdList.Clear();
984 mDeletedPaddingSize = 0;
987 mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
989 if (mDeletedPaddingSize > 0) {
990 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
991 mDeletedPaddingSize);
994 Listener* listener = mManager->GetListener(mListenerId);
995 mManager = nullptr;
996 if (listener) {
997 listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult());
1001 virtual void CancelOnInitiatingThread() override {
1002 NS_ASSERT_OWNINGTHREAD(Action);
1003 Action::CancelOnInitiatingThread();
1004 CancelAllStreamCopying();
1007 virtual bool MatchesCacheId(CacheId aCacheId) const override {
1008 NS_ASSERT_OWNINGTHREAD(Action);
1009 return aCacheId == mCacheId;
1012 struct Entry {
1013 CacheRequest mRequest;
1014 nsCOMPtr<nsIInputStream> mRequestStream;
1015 nsID mRequestBodyId{};
1016 nsCOMPtr<nsISupports> mRequestCopyContext;
1018 CacheResponse mResponse;
1019 nsCOMPtr<nsIInputStream> mResponseStream;
1020 nsID mResponseBodyId{};
1021 nsCOMPtr<nsISupports> mResponseCopyContext;
1024 enum StreamId { RequestStream, ResponseStream };
1026 nsresult StartStreamCopy(const CacheDirectoryMetadata& aDirectoryMetadata,
1027 Entry& aEntry, StreamId aStreamId,
1028 uint32_t* aCopyCountOut) {
1029 MOZ_ASSERT(mTarget->IsOnCurrentThread());
1030 MOZ_DIAGNOSTIC_ASSERT(aCopyCountOut);
1032 if (IsCanceled()) {
1033 return NS_ERROR_ABORT;
1036 MOZ_DIAGNOSTIC_ASSERT(aStreamId == RequestStream ||
1037 aStreamId == ResponseStream);
1039 const auto& source = aStreamId == RequestStream ? aEntry.mRequestStream
1040 : aEntry.mResponseStream;
1042 if (!source) {
1043 return NS_OK;
1045 QM_TRY_INSPECT(const auto& idGen,
1046 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIUUIDGenerator>,
1047 MOZ_SELECT_OVERLOAD(do_GetService),
1048 "@mozilla.org/uuid-generator;1"));
1050 nsID bodyId{};
1051 QM_TRY(MOZ_TO_RESULT(idGen->GenerateUUIDInPlace(&bodyId)));
1053 Maybe<CipherKey> maybeKey =
1054 GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId,
1055 /* aCreate */ true);
1057 QM_TRY_INSPECT(
1058 const auto& copyContext,
1059 BodyStartWriteStream(aDirectoryMetadata, *mDBDir, bodyId, maybeKey,
1060 *source, this, AsyncCopyCompleteFunc));
1062 if (aStreamId == RequestStream) {
1063 aEntry.mRequestBodyId = bodyId;
1064 } else {
1065 aEntry.mResponseBodyId = bodyId;
1068 mBodyIdWrittenList.AppendElement(bodyId);
1070 if (copyContext) {
1071 MutexAutoLock lock(mMutex);
1072 mCopyContextList.AppendElement(copyContext);
1075 *aCopyCountOut += 1;
1077 return NS_OK;
1080 void CancelAllStreamCopying() {
1081 // May occur on either owning thread or target thread
1082 MutexAutoLock lock(mMutex);
1083 for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) {
1084 MOZ_DIAGNOSTIC_ASSERT(mCopyContextList[i]);
1085 BodyCancelWrite(*mCopyContextList[i]);
1087 mCopyContextList.Clear();
1090 static void AsyncCopyCompleteFunc(void* aClosure, nsresult aRv) {
1091 // May be on any thread, including STS event target.
1092 MOZ_DIAGNOSTIC_ASSERT(aClosure);
1093 // Weak ref as we are guaranteed to the action is alive until
1094 // CompleteOnInitiatingThread is called.
1095 CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure);
1096 action->CallOnAsyncCopyCompleteOnTargetThread(aRv);
1099 void CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv) {
1100 // May be on any thread, including STS event target. Non-owning runnable
1101 // here since we are guaranteed the Action will survive until
1102 // CompleteOnInitiatingThread is called.
1103 nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod<nsresult>(
1104 "dom::cache::Manager::CachePutAllAction::OnAsyncCopyComplete", this,
1105 &CachePutAllAction::OnAsyncCopyComplete, aRv);
1106 MOZ_ALWAYS_SUCCEEDS(
1107 mTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL));
1110 void DoResolve(nsresult aRv) {
1111 MOZ_ASSERT(mTarget->IsOnCurrentThread());
1113 // DoResolve() must not be called until all async copying has completed.
1114 #ifdef DEBUG
1116 MutexAutoLock lock(mMutex);
1117 MOZ_ASSERT(mCopyContextList.IsEmpty());
1119 #endif
1121 // Clean up any files we might have written before hitting the error.
1122 if (NS_FAILED(aRv)) {
1123 BodyDeleteFiles(*mDirectoryMetadata, *mDBDir, mBodyIdWrittenList);
1124 if (mUpdatedPaddingSize > 0) {
1125 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
1126 mUpdatedPaddingSize);
1130 // Must be released on the target thread where it was opened.
1131 mConn = nullptr;
1133 // Drop our ref to the target thread as we are done with this thread.
1134 // Also makes our thread assertions catch any incorrect method calls
1135 // after resolve.
1136 mTarget = nullptr;
1138 // Make sure to de-ref the resolver per the Action API contract.
1139 SafeRefPtr<Action::Resolver> resolver = std::move(mResolver);
1140 resolver->Resolve(aRv);
1143 // initiating thread only
1144 SafeRefPtr<Manager> mManager;
1145 const ListenerId mListenerId;
1147 // Set on initiating thread, read on target thread. State machine guarantees
1148 // these are not modified while being read by the target thread.
1149 const CacheId mCacheId;
1150 nsTArray<Entry> mList;
1151 uint32_t mExpectedAsyncCopyCompletions;
1153 // target thread only
1154 SafeRefPtr<Resolver> mResolver;
1155 nsCOMPtr<nsIFile> mDBDir;
1156 nsCOMPtr<mozIStorageConnection> mConn;
1157 nsCOMPtr<nsISerialEventTarget> mTarget;
1158 nsresult mAsyncResult;
1159 nsTArray<nsID> mBodyIdWrittenList;
1161 // Written to on target thread, accessed on initiating thread after target
1162 // thread activity is guaranteed complete
1163 nsTArray<nsID> mDeletedBodyIdList;
1165 // accessed from any thread while mMutex locked
1166 Mutex mMutex MOZ_UNANNOTATED;
1167 nsTArray<nsCOMPtr<nsISupports>> mCopyContextList;
1169 Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
1170 // Track how much pad amount has been added for new entries so that it can be
1171 // removed if an error occurs.
1172 int64_t mUpdatedPaddingSize;
1173 // Track any pad amount associated with overwritten entries.
1174 int64_t mDeletedPaddingSize;
1177 // ----------------------------------------------------------------------------
1179 class Manager::CacheDeleteAction final : public Manager::BaseAction {
1180 public:
1181 CacheDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1182 CacheId aCacheId, const CacheDeleteArgs& aArgs)
1183 : BaseAction(std::move(aManager), aListenerId),
1184 mCacheId(aCacheId),
1185 mArgs(aArgs),
1186 mSuccess(false) {}
1188 virtual nsresult RunSyncWithDBOnTarget(
1189 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1190 mozIStorageConnection* aConn) override {
1191 mDirectoryMetadata.emplace(aDirectoryMetadata);
1193 mozStorageTransaction trans(aConn, false,
1194 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1196 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1198 QM_TRY_UNWRAP(
1199 auto maybeDeletionInfo,
1200 db::CacheDelete(*aConn, mCacheId, mArgs.request(), mArgs.params()));
1202 mSuccess = maybeDeletionInfo.isSome();
1203 if (mSuccess) {
1204 mDeletionInfo = std::move(maybeDeletionInfo.ref());
1207 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
1208 aDBDir, aConn, /* aIncreaceSize */ 0,
1209 mDeletionInfo.mDeletedPaddingSize,
1210 [&trans]() mutable { return trans.Commit(); })),
1211 QM_PROPAGATE, [this](const nsresult) { mSuccess = false; });
1213 return NS_OK;
1216 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1217 // If the transaction fails, we shouldn't delete the body files and decrease
1218 // their padding size.
1219 if (aRv.Failed()) {
1220 mDeletionInfo.mDeletedBodyIdList.Clear();
1221 mDeletionInfo.mDeletedPaddingSize = 0;
1224 mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
1226 if (mDeletionInfo.mDeletedPaddingSize > 0) {
1227 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
1228 mDeletionInfo.mDeletedPaddingSize);
1231 aListener->OnOpComplete(std::move(aRv), CacheDeleteResult(mSuccess));
1234 virtual bool MatchesCacheId(CacheId aCacheId) const override {
1235 return aCacheId == mCacheId;
1238 private:
1239 const CacheId mCacheId;
1240 const CacheDeleteArgs mArgs;
1241 bool mSuccess;
1242 DeletionInfo mDeletionInfo;
1243 Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
1246 // ----------------------------------------------------------------------------
1248 class Manager::CacheKeysAction final : public Manager::BaseAction {
1249 public:
1250 CacheKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1251 CacheId aCacheId, const CacheKeysArgs& aArgs,
1252 SafeRefPtr<StreamList> aStreamList)
1253 : BaseAction(std::move(aManager), aListenerId),
1254 mCacheId(aCacheId),
1255 mArgs(aArgs),
1256 mStreamList(std::move(aStreamList)) {}
1258 virtual nsresult RunSyncWithDBOnTarget(
1259 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1260 mozIStorageConnection* aConn) override {
1261 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1263 QM_TRY_UNWRAP(
1264 mSavedRequests,
1265 db::CacheKeys(*aConn, mCacheId, mArgs.maybeRequest(), mArgs.params()));
1267 for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) {
1268 if (!mSavedRequests[i].mHasBodyId ||
1269 IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
1270 mSavedRequests[i].mHasBodyId = false;
1271 continue;
1274 const auto& bodyId = mSavedRequests[i].mBodyId;
1276 nsCOMPtr<nsIInputStream> stream;
1277 if (mArgs.openMode() == OpenMode::Eager) {
1278 QM_TRY_UNWRAP(stream,
1279 BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
1280 GetOrCreateCipherKey(
1281 WrapNotNull(mManager->mContext), bodyId,
1282 /* aCreate */ false)));
1285 // If we entered shutdown on the main thread while we were doing IO,
1286 // bail out now.
1287 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) {
1288 if (stream) {
1289 stream->Close();
1291 return NS_ERROR_ABORT;
1294 mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream));
1297 return NS_OK;
1300 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1301 mStreamList->Activate(mCacheId);
1302 aListener->OnOpComplete(std::move(aRv), CacheKeysResult(), mSavedRequests,
1303 *mStreamList);
1304 mStreamList = nullptr;
1307 virtual bool MatchesCacheId(CacheId aCacheId) const override {
1308 return aCacheId == mCacheId;
1311 private:
1312 const CacheId mCacheId;
1313 const CacheKeysArgs mArgs;
1314 SafeRefPtr<StreamList> mStreamList;
1315 nsTArray<SavedRequest> mSavedRequests;
1318 // ----------------------------------------------------------------------------
1320 class Manager::StorageMatchAction final : public Manager::BaseAction {
1321 public:
1322 StorageMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1323 Namespace aNamespace, const StorageMatchArgs& aArgs,
1324 SafeRefPtr<StreamList> aStreamList)
1325 : BaseAction(std::move(aManager), aListenerId),
1326 mNamespace(aNamespace),
1327 mArgs(aArgs),
1328 mStreamList(std::move(aStreamList)),
1329 mFoundResponse(false) {}
1331 virtual nsresult RunSyncWithDBOnTarget(
1332 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1333 mozIStorageConnection* aConn) override {
1334 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1336 auto maybeResponse =
1337 db::StorageMatch(*aConn, mNamespace, mArgs.request(), mArgs.params());
1338 if (NS_WARN_IF(maybeResponse.isErr())) {
1339 return maybeResponse.unwrapErr();
1342 mFoundResponse = maybeResponse.inspect().isSome();
1343 if (mFoundResponse) {
1344 mSavedResponse = maybeResponse.unwrap().ref();
1347 if (!mFoundResponse || !mSavedResponse.mHasBodyId ||
1348 IsHeadRequest(mArgs.request(), mArgs.params())) {
1349 mSavedResponse.mHasBodyId = false;
1350 return NS_OK;
1353 const auto& bodyId = mSavedResponse.mBodyId;
1355 nsCOMPtr<nsIInputStream> stream;
1356 if (mArgs.openMode() == OpenMode::Eager) {
1357 QM_TRY_UNWRAP(
1358 stream,
1359 BodyOpen(aDirectoryMetadata, *aDBDir, bodyId,
1360 GetOrCreateCipherKey(WrapNotNull(mManager->mContext), bodyId,
1361 /* aCreate */ false)));
1364 // If we entered shutdown on the main thread while we were doing IO,
1365 // bail out now.
1366 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM)) {
1367 if (stream) {
1368 stream->Close();
1370 return NS_ERROR_ABORT;
1373 mStreamList->Add(mSavedResponse.mBodyId, std::move(stream));
1375 return NS_OK;
1378 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1379 if (!mFoundResponse) {
1380 aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()));
1381 } else {
1382 mStreamList->Activate(mSavedResponse.mCacheId);
1383 aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()),
1384 mSavedResponse, *mStreamList);
1386 mStreamList = nullptr;
1389 private:
1390 const Namespace mNamespace;
1391 const StorageMatchArgs mArgs;
1392 SafeRefPtr<StreamList> mStreamList;
1393 bool mFoundResponse;
1394 SavedResponse mSavedResponse;
1397 // ----------------------------------------------------------------------------
1399 class Manager::StorageHasAction final : public Manager::BaseAction {
1400 public:
1401 StorageHasAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1402 Namespace aNamespace, const StorageHasArgs& aArgs)
1403 : BaseAction(std::move(aManager), aListenerId),
1404 mNamespace(aNamespace),
1405 mArgs(aArgs),
1406 mCacheFound(false) {}
1408 virtual nsresult RunSyncWithDBOnTarget(
1409 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1410 mozIStorageConnection* aConn) override {
1411 QM_TRY_INSPECT(const auto& maybeCacheId,
1412 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1414 mCacheFound = maybeCacheId.isSome();
1416 return NS_OK;
1419 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1420 aListener->OnOpComplete(std::move(aRv), StorageHasResult(mCacheFound));
1423 private:
1424 const Namespace mNamespace;
1425 const StorageHasArgs mArgs;
1426 bool mCacheFound;
1429 // ----------------------------------------------------------------------------
1431 class Manager::StorageOpenAction final : public Manager::BaseAction {
1432 public:
1433 StorageOpenAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1434 Namespace aNamespace, const StorageOpenArgs& aArgs)
1435 : BaseAction(std::move(aManager), aListenerId),
1436 mNamespace(aNamespace),
1437 mArgs(aArgs),
1438 mCacheId(INVALID_CACHE_ID) {}
1440 virtual nsresult RunSyncWithDBOnTarget(
1441 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1442 mozIStorageConnection* aConn) override {
1443 // Cache does not exist, create it instead
1444 mozStorageTransaction trans(aConn, false,
1445 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1447 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1449 // Look for existing cache
1450 QM_TRY_INSPECT(const auto& maybeCacheId,
1451 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1453 if (maybeCacheId.isSome()) {
1454 mCacheId = maybeCacheId.ref();
1455 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
1456 return NS_OK;
1459 QM_TRY_UNWRAP(mCacheId, db::CreateCacheId(*aConn));
1461 QM_TRY(MOZ_TO_RESULT(
1462 db::StoragePutCache(*aConn, mNamespace, mArgs.key(), mCacheId)));
1464 QM_TRY(MOZ_TO_RESULT(trans.Commit()));
1466 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
1467 return NS_OK;
1470 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1471 MOZ_DIAGNOSTIC_ASSERT(aRv.Failed() || mCacheId != INVALID_CACHE_ID);
1472 aListener->OnOpComplete(
1473 std::move(aRv), StorageOpenResult((PCacheParent*)nullptr, mNamespace),
1474 mCacheId);
1477 private:
1478 const Namespace mNamespace;
1479 const StorageOpenArgs mArgs;
1480 CacheId mCacheId;
1483 // ----------------------------------------------------------------------------
1485 class Manager::StorageDeleteAction final : public Manager::BaseAction {
1486 public:
1487 StorageDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1488 Namespace aNamespace, const StorageDeleteArgs& aArgs)
1489 : BaseAction(std::move(aManager), aListenerId),
1490 mNamespace(aNamespace),
1491 mArgs(aArgs),
1492 mCacheDeleted(false),
1493 mCacheId(INVALID_CACHE_ID) {}
1495 virtual nsresult RunSyncWithDBOnTarget(
1496 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1497 mozIStorageConnection* aConn) override {
1498 mozStorageTransaction trans(aConn, false,
1499 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1501 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1503 QM_TRY_INSPECT(const auto& maybeCacheId,
1504 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1506 if (maybeCacheId.isNothing()) {
1507 mCacheDeleted = false;
1508 return NS_OK;
1510 mCacheId = maybeCacheId.ref();
1512 // Don't delete the removing padding size here, we'll delete it on
1513 // DeleteOrphanedCacheAction.
1514 QM_TRY(
1515 MOZ_TO_RESULT(db::StorageForgetCache(*aConn, mNamespace, mArgs.key())));
1517 QM_TRY(MOZ_TO_RESULT(trans.Commit()));
1519 mCacheDeleted = true;
1520 return NS_OK;
1523 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1524 if (mCacheDeleted) {
1525 // If content is referencing this cache, mark it orphaned to be
1526 // deleted later.
1527 if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) {
1528 // no outstanding references, delete immediately
1529 const auto pinnedContext =
1530 SafeRefPtr{mManager->mContext, AcquireStrongRefFromRawPtr{}};
1532 if (pinnedContext->IsCanceled()) {
1533 pinnedContext->NoteOrphanedData();
1534 } else {
1535 pinnedContext->CancelForCacheId(mCacheId);
1536 pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
1537 mManager.clonePtr(), mCacheId));
1542 aListener->OnOpComplete(std::move(aRv), StorageDeleteResult(mCacheDeleted));
1545 private:
1546 const Namespace mNamespace;
1547 const StorageDeleteArgs mArgs;
1548 bool mCacheDeleted;
1549 CacheId mCacheId;
1552 // ----------------------------------------------------------------------------
1554 class Manager::StorageKeysAction final : public Manager::BaseAction {
1555 public:
1556 StorageKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1557 Namespace aNamespace)
1558 : BaseAction(std::move(aManager), aListenerId), mNamespace(aNamespace) {}
1560 virtual nsresult RunSyncWithDBOnTarget(
1561 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1562 mozIStorageConnection* aConn) override {
1563 QM_TRY_UNWRAP(mKeys, db::StorageGetKeys(*aConn, mNamespace));
1565 return NS_OK;
1568 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1569 if (aRv.Failed()) {
1570 mKeys.Clear();
1572 aListener->OnOpComplete(std::move(aRv), StorageKeysResult(mKeys));
1575 private:
1576 const Namespace mNamespace;
1577 nsTArray<nsString> mKeys;
1580 // ----------------------------------------------------------------------------
1582 class Manager::OpenStreamAction final : public Manager::BaseAction {
1583 public:
1584 OpenStreamAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1585 InputStreamResolver&& aResolver, const nsID& aBodyId)
1586 : BaseAction(std::move(aManager), aListenerId),
1587 mResolver(std::move(aResolver)),
1588 mBodyId(aBodyId) {}
1590 virtual nsresult RunSyncWithDBOnTarget(
1591 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1592 mozIStorageConnection* aConn) override {
1593 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1595 QM_TRY_UNWRAP(
1596 mBodyStream,
1597 BodyOpen(aDirectoryMetadata, *aDBDir, mBodyId,
1598 GetOrCreateCipherKey(WrapNotNull(mManager->mContext), mBodyId,
1599 /* aCreate */ false)));
1601 return NS_OK;
1604 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1605 if (aRv.Failed()) {
1606 // Ignore the reason for fail and just pass a null input stream to let it
1607 // fail.
1608 aRv.SuppressException();
1609 mResolver(nullptr);
1610 } else {
1611 mResolver(std::move(mBodyStream));
1614 mResolver = nullptr;
1617 private:
1618 InputStreamResolver mResolver;
1619 const nsID mBodyId;
1620 nsCOMPtr<nsIInputStream> mBodyStream;
1623 // ----------------------------------------------------------------------------
1625 // static
1626 Manager::ListenerId Manager::sNextListenerId = 0;
1628 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1629 const CacheOpResult& aResult) {
1630 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID, Nothing());
1633 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1634 const CacheOpResult& aResult,
1635 CacheId aOpenedCacheId) {
1636 OnOpComplete(std::move(aRv), aResult, aOpenedCacheId, Nothing());
1639 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1640 const CacheOpResult& aResult,
1641 const SavedResponse& aSavedResponse,
1642 StreamList& aStreamList) {
1643 AutoTArray<SavedResponse, 1> responseList;
1644 responseList.AppendElement(aSavedResponse);
1645 OnOpComplete(
1646 std::move(aRv), aResult, INVALID_CACHE_ID,
1647 Some(StreamInfo{responseList, nsTArray<SavedRequest>(), aStreamList}));
1650 void Manager::Listener::OnOpComplete(
1651 ErrorResult&& aRv, const CacheOpResult& aResult,
1652 const nsTArray<SavedResponse>& aSavedResponseList,
1653 StreamList& aStreamList) {
1654 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
1655 Some(StreamInfo{aSavedResponseList, nsTArray<SavedRequest>(),
1656 aStreamList}));
1659 void Manager::Listener::OnOpComplete(
1660 ErrorResult&& aRv, const CacheOpResult& aResult,
1661 const nsTArray<SavedRequest>& aSavedRequestList, StreamList& aStreamList) {
1662 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
1663 Some(StreamInfo{nsTArray<SavedResponse>(), aSavedRequestList,
1664 aStreamList}));
1667 // static
1668 Result<SafeRefPtr<Manager>, nsresult> Manager::AcquireCreateIfNonExistent(
1669 const SafeRefPtr<ManagerId>& aManagerId) {
1670 mozilla::ipc::AssertIsOnBackgroundThread();
1671 return Factory::AcquireCreateIfNonExistent(aManagerId);
1674 // static
1675 void Manager::InitiateShutdown() {
1676 mozilla::ipc::AssertIsOnBackgroundThread();
1678 Factory::AbortAll();
1680 Factory::ShutdownAll();
1683 // static
1684 bool Manager::IsShutdownAllComplete() {
1685 mozilla::ipc::AssertIsOnBackgroundThread();
1687 return Factory::IsShutdownAllComplete();
1690 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1691 void Manager::RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId) {
1692 Factory::RecordMayNotDeleteCSCP(aCacheStreamControlParentId);
1695 void Manager::RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId) {
1696 Factory::RecordHaveDeletedCSCP(aCacheStreamControlParentId);
1698 #endif
1700 // static
1701 nsCString Manager::GetShutdownStatus() {
1702 mozilla::ipc::AssertIsOnBackgroundThread();
1704 return Factory::GetShutdownStatus();
1707 // static
1708 void Manager::Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
1709 mozilla::ipc::AssertIsOnBackgroundThread();
1711 Factory::Abort(aDirectoryLockIds);
1714 // static
1715 void Manager::AbortAll() {
1716 mozilla::ipc::AssertIsOnBackgroundThread();
1718 Factory::AbortAll();
1721 void Manager::RemoveListener(Listener* aListener) {
1722 NS_ASSERT_OWNINGTHREAD(Manager);
1723 // There may not be a listener here in the case where an actor is killed
1724 // before it can perform any actual async requests on Manager.
1725 mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
1726 MOZ_ASSERT(
1727 !mListeners.Contains(aListener, ListenerEntryListenerComparator()));
1728 MaybeAllowContextToClose();
1731 void Manager::RemoveContext(Context& aContext) {
1732 NS_ASSERT_OWNINGTHREAD(Manager);
1733 MOZ_DIAGNOSTIC_ASSERT(mContext);
1734 MOZ_DIAGNOSTIC_ASSERT(mContext == &aContext);
1736 // Whether the Context destruction was triggered from the Manager going
1737 // idle or the underlying storage being invalidated, we should know we
1738 // are closing before the Context is destroyed.
1739 MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
1741 // Before forgetting the Context, check to see if we have any outstanding
1742 // cache or body objects waiting for deletion. If so, note that we've
1743 // orphaned data so it will be cleaned up on the next open.
1744 if (std::any_of(
1745 mCacheIdRefs.cbegin(), mCacheIdRefs.cend(),
1746 [](const auto& cacheIdRef) { return cacheIdRef.mOrphaned; }) ||
1747 std::any_of(mBodyIdRefs.cbegin(), mBodyIdRefs.cend(),
1748 [](const auto& bodyIdRef) { return bodyIdRef.mOrphaned; })) {
1749 aContext.NoteOrphanedData();
1752 mContext = nullptr;
1754 // Once the context is gone, we can immediately remove ourself from the
1755 // Factory list. We don't need to block shutdown by staying in the list
1756 // any more.
1757 Factory::Remove(*this);
1760 void Manager::NoteClosing() {
1761 NS_ASSERT_OWNINGTHREAD(Manager);
1762 // This can be called more than once legitimately through different paths.
1763 mState = Closing;
1766 Manager::State Manager::GetState() const {
1767 NS_ASSERT_OWNINGTHREAD(Manager);
1768 return mState;
1771 void Manager::AddRefCacheId(CacheId aCacheId) {
1772 NS_ASSERT_OWNINGTHREAD(Manager);
1774 const auto end = mCacheIdRefs.end();
1775 const auto foundIt =
1776 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
1777 if (foundIt != end) {
1778 foundIt->mCount += 1;
1779 return;
1782 mCacheIdRefs.AppendElement(CacheIdRefCounter{aCacheId, 1, false});
1785 void Manager::ReleaseCacheId(CacheId aCacheId) {
1786 NS_ASSERT_OWNINGTHREAD(Manager);
1788 const auto end = mCacheIdRefs.end();
1789 const auto foundIt =
1790 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
1791 if (foundIt != end) {
1792 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1793 const uint32_t oldRef = foundIt->mCount;
1794 #endif
1795 foundIt->mCount -= 1;
1796 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
1797 if (foundIt->mCount == 0) {
1798 const bool orphaned = foundIt->mOrphaned;
1799 mCacheIdRefs.RemoveElementAt(foundIt);
1800 const auto pinnedContext =
1801 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1802 // If the context is already gone, then orphan flag should have been
1803 // set in RemoveContext().
1804 if (orphaned && pinnedContext) {
1805 if (pinnedContext->IsCanceled()) {
1806 pinnedContext->NoteOrphanedData();
1807 } else {
1808 pinnedContext->CancelForCacheId(aCacheId);
1809 pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
1810 SafeRefPtrFromThis(), aCacheId));
1814 MaybeAllowContextToClose();
1815 return;
1818 MOZ_ASSERT_UNREACHABLE("Attempt to release CacheId that is not referenced!");
1821 void Manager::AddRefBodyId(const nsID& aBodyId) {
1822 NS_ASSERT_OWNINGTHREAD(Manager);
1824 const auto end = mBodyIdRefs.end();
1825 const auto foundIt =
1826 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
1827 if (foundIt != end) {
1828 foundIt->mCount += 1;
1829 return;
1832 mBodyIdRefs.AppendElement(BodyIdRefCounter{aBodyId, 1, false});
1835 void Manager::ReleaseBodyId(const nsID& aBodyId) {
1836 NS_ASSERT_OWNINGTHREAD(Manager);
1838 const auto end = mBodyIdRefs.end();
1839 const auto foundIt =
1840 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
1841 if (foundIt != end) {
1842 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1843 const uint32_t oldRef = foundIt->mCount;
1844 #endif
1845 foundIt->mCount -= 1;
1846 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
1847 if (foundIt->mCount < 1) {
1848 const bool orphaned = foundIt->mOrphaned;
1849 mBodyIdRefs.RemoveElementAt(foundIt);
1850 const auto pinnedContext =
1851 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1852 // If the context is already gone, then orphan flag should have been
1853 // set in RemoveContext().
1854 if (orphaned && pinnedContext) {
1855 if (pinnedContext->IsCanceled()) {
1856 pinnedContext->NoteOrphanedData();
1857 } else {
1858 pinnedContext->Dispatch(
1859 MakeSafeRefPtr<DeleteOrphanedBodyAction>(aBodyId));
1863 MaybeAllowContextToClose();
1864 return;
1867 MOZ_ASSERT_UNREACHABLE("Attempt to release BodyId that is not referenced!");
1870 const ManagerId& Manager::GetManagerId() const { return *mManagerId; }
1872 void Manager::AddStreamList(StreamList& aStreamList) {
1873 NS_ASSERT_OWNINGTHREAD(Manager);
1874 mStreamLists.AppendElement(WrapNotNullUnchecked(&aStreamList));
1877 void Manager::RemoveStreamList(StreamList& aStreamList) {
1878 NS_ASSERT_OWNINGTHREAD(Manager);
1879 mStreamLists.RemoveElement(&aStreamList);
1882 void Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId,
1883 const CacheOpArgs& aOpArgs) {
1884 NS_ASSERT_OWNINGTHREAD(Manager);
1885 MOZ_DIAGNOSTIC_ASSERT(aListener);
1886 MOZ_DIAGNOSTIC_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs);
1888 if (NS_WARN_IF(mState == Closing)) {
1889 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
1890 return;
1893 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1894 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1896 auto action = [this, aListener, aCacheId, &aOpArgs,
1897 &pinnedContext]() -> SafeRefPtr<Action> {
1898 const ListenerId listenerId = SaveListener(aListener);
1900 if (CacheOpArgs::TCacheDeleteArgs == aOpArgs.type()) {
1901 return MakeSafeRefPtr<CacheDeleteAction>(SafeRefPtrFromThis(), listenerId,
1902 aCacheId,
1903 aOpArgs.get_CacheDeleteArgs());
1906 auto streamList = MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
1907 pinnedContext.clonePtr());
1909 switch (aOpArgs.type()) {
1910 case CacheOpArgs::TCacheMatchArgs:
1911 return MakeSafeRefPtr<CacheMatchAction>(
1912 SafeRefPtrFromThis(), listenerId, aCacheId,
1913 aOpArgs.get_CacheMatchArgs(), std::move(streamList));
1914 case CacheOpArgs::TCacheMatchAllArgs:
1915 return MakeSafeRefPtr<CacheMatchAllAction>(
1916 SafeRefPtrFromThis(), listenerId, aCacheId,
1917 aOpArgs.get_CacheMatchAllArgs(), std::move(streamList));
1918 case CacheOpArgs::TCacheKeysArgs:
1919 return MakeSafeRefPtr<CacheKeysAction>(
1920 SafeRefPtrFromThis(), listenerId, aCacheId,
1921 aOpArgs.get_CacheKeysArgs(), std::move(streamList));
1922 default:
1923 MOZ_CRASH("Unknown Cache operation!");
1925 }();
1927 pinnedContext->Dispatch(std::move(action));
1930 void Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace,
1931 const CacheOpArgs& aOpArgs) {
1932 NS_ASSERT_OWNINGTHREAD(Manager);
1933 MOZ_DIAGNOSTIC_ASSERT(aListener);
1935 if (NS_WARN_IF(mState == Closing)) {
1936 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
1937 return;
1940 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1941 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1943 auto action = [this, aListener, aNamespace, &aOpArgs,
1944 &pinnedContext]() -> SafeRefPtr<Action> {
1945 const ListenerId listenerId = SaveListener(aListener);
1947 switch (aOpArgs.type()) {
1948 case CacheOpArgs::TStorageMatchArgs:
1949 return MakeSafeRefPtr<StorageMatchAction>(
1950 SafeRefPtrFromThis(), listenerId, aNamespace,
1951 aOpArgs.get_StorageMatchArgs(),
1952 MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
1953 pinnedContext.clonePtr()));
1954 case CacheOpArgs::TStorageHasArgs:
1955 return MakeSafeRefPtr<StorageHasAction>(SafeRefPtrFromThis(),
1956 listenerId, aNamespace,
1957 aOpArgs.get_StorageHasArgs());
1958 case CacheOpArgs::TStorageOpenArgs:
1959 return MakeSafeRefPtr<StorageOpenAction>(SafeRefPtrFromThis(),
1960 listenerId, aNamespace,
1961 aOpArgs.get_StorageOpenArgs());
1962 case CacheOpArgs::TStorageDeleteArgs:
1963 return MakeSafeRefPtr<StorageDeleteAction>(
1964 SafeRefPtrFromThis(), listenerId, aNamespace,
1965 aOpArgs.get_StorageDeleteArgs());
1966 case CacheOpArgs::TStorageKeysArgs:
1967 return MakeSafeRefPtr<StorageKeysAction>(SafeRefPtrFromThis(),
1968 listenerId, aNamespace);
1969 default:
1970 MOZ_CRASH("Unknown CacheStorage operation!");
1972 }();
1974 pinnedContext->Dispatch(std::move(action));
1977 void Manager::ExecuteOpenStream(Listener* aListener,
1978 InputStreamResolver&& aResolver,
1979 const nsID& aBodyId) {
1980 NS_ASSERT_OWNINGTHREAD(Manager);
1981 MOZ_DIAGNOSTIC_ASSERT(aListener);
1982 MOZ_DIAGNOSTIC_ASSERT(aResolver);
1984 if (NS_WARN_IF(mState == Closing)) {
1985 aResolver(nullptr);
1986 return;
1989 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1990 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1992 // We save the listener simply to track the existence of the caller here.
1993 // Our returned value will really be passed to the resolver when the
1994 // operation completes. In the future we should remove the Listener
1995 // mechanism in favor of std::function or MozPromise.
1996 ListenerId listenerId = SaveListener(aListener);
1998 pinnedContext->Dispatch(MakeSafeRefPtr<OpenStreamAction>(
1999 SafeRefPtrFromThis(), listenerId, std::move(aResolver), aBodyId));
2002 void Manager::ExecutePutAll(
2003 Listener* aListener, CacheId aCacheId,
2004 const nsTArray<CacheRequestResponse>& aPutList,
2005 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
2006 const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList) {
2007 NS_ASSERT_OWNINGTHREAD(Manager);
2008 MOZ_DIAGNOSTIC_ASSERT(aListener);
2010 if (NS_WARN_IF(mState == Closing)) {
2011 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult());
2012 return;
2015 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2016 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
2018 ListenerId listenerId = SaveListener(aListener);
2019 pinnedContext->Dispatch(MakeSafeRefPtr<CachePutAllAction>(
2020 SafeRefPtrFromThis(), listenerId, aCacheId, aPutList, aRequestStreamList,
2021 aResponseStreamList));
2024 Manager::Manager(SafeRefPtr<ManagerId> aManagerId, nsIThread* aIOThread,
2025 const ConstructorGuard&)
2026 : mManagerId(std::move(aManagerId)),
2027 mIOThread(aIOThread),
2028 mContext(nullptr),
2029 mShuttingDown(false),
2030 mState(Open) {
2031 MOZ_DIAGNOSTIC_ASSERT(mManagerId);
2032 MOZ_DIAGNOSTIC_ASSERT(mIOThread);
2035 Manager::~Manager() {
2036 NS_ASSERT_OWNINGTHREAD(Manager);
2037 MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
2038 MOZ_DIAGNOSTIC_ASSERT(!mContext);
2040 nsCOMPtr<nsIThread> ioThread;
2041 mIOThread.swap(ioThread);
2043 // Don't spin the event loop in the destructor waiting for the thread to
2044 // shutdown. Defer this to the main thread, instead.
2045 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(
2046 "nsIThread::AsyncShutdown", ioThread, &nsIThread::AsyncShutdown)));
2049 void Manager::Init(Maybe<Manager&> aOldManager) {
2050 NS_ASSERT_OWNINGTHREAD(Manager);
2052 // Create the context immediately. Since there can at most be one Context
2053 // per Manager now, this lets us cleanly call Factory::Remove() once the
2054 // Context goes away.
2055 SafeRefPtr<Context> ref = Context::Create(
2056 SafeRefPtrFromThis(), mIOThread, MakeSafeRefPtr<SetupAction>(),
2057 aOldManager ? SomeRef(*aOldManager->mContext) : Nothing());
2058 mContext = ref.unsafeGetRawPtr();
2061 void Manager::Shutdown() {
2062 NS_ASSERT_OWNINGTHREAD(Manager);
2064 // Ignore duplicate attempts to shutdown. This can occur when we start
2065 // a browser initiated shutdown and then run ~Manager() which also
2066 // calls Shutdown().
2067 if (mShuttingDown) {
2068 return;
2071 mShuttingDown = true;
2073 // Note that we are closing to prevent any new requests from coming in and
2074 // creating a new Context. We must ensure all Contexts and IO operations are
2075 // complete before shutdown proceeds.
2076 NoteClosing();
2078 // If there is a context, then cancel and only note that we are done after
2079 // its cleaned up.
2080 if (mContext) {
2081 const auto pinnedContext =
2082 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2083 pinnedContext->CancelAll();
2084 return;
2088 Maybe<DirectoryLock&> Manager::MaybeDirectoryLockRef() const {
2089 NS_ASSERT_OWNINGTHREAD(Manager);
2090 MOZ_DIAGNOSTIC_ASSERT(mContext);
2092 return mContext->MaybeDirectoryLockRef();
2095 void Manager::Abort() {
2096 NS_ASSERT_OWNINGTHREAD(Manager);
2097 MOZ_DIAGNOSTIC_ASSERT(mContext);
2099 // Note that we are closing to prevent any new requests from coming in and
2100 // creating a new Context. We must ensure all Contexts and IO operations are
2101 // complete before origin clear proceeds.
2102 NoteClosing();
2104 // Cancel and only note that we are done after the context is cleaned up.
2105 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2106 pinnedContext->CancelAll();
2109 Manager::ListenerId Manager::SaveListener(Listener* aListener) {
2110 NS_ASSERT_OWNINGTHREAD(Manager);
2112 // Once a Listener is added, we keep a reference to it until its
2113 // removed. Since the same Listener might make multiple requests,
2114 // ensure we only have a single reference in our list.
2115 ListenerList::index_type index =
2116 mListeners.IndexOf(aListener, 0, ListenerEntryListenerComparator());
2117 if (index != ListenerList::NoIndex) {
2118 return mListeners[index].mId;
2121 ListenerId id = sNextListenerId;
2122 sNextListenerId += 1;
2124 mListeners.AppendElement(ListenerEntry(id, aListener));
2125 return id;
2128 Manager::Listener* Manager::GetListener(ListenerId aListenerId) const {
2129 NS_ASSERT_OWNINGTHREAD(Manager);
2130 ListenerList::index_type index =
2131 mListeners.IndexOf(aListenerId, 0, ListenerEntryIdComparator());
2132 if (index != ListenerList::NoIndex) {
2133 return mListeners[index].mListener;
2136 // This can legitimately happen if the actor is deleted while a request is
2137 // in process. For example, the child process OOMs.
2138 return nullptr;
2141 bool Manager::SetCacheIdOrphanedIfRefed(CacheId aCacheId) {
2142 NS_ASSERT_OWNINGTHREAD(Manager);
2144 const auto end = mCacheIdRefs.end();
2145 const auto foundIt =
2146 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
2147 if (foundIt != end) {
2148 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
2149 MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
2150 foundIt->mOrphaned = true;
2151 return true;
2154 return false;
2157 // TODO: provide way to set body non-orphaned if its added back to a cache (bug
2158 // 1110479)
2160 bool Manager::SetBodyIdOrphanedIfRefed(const nsID& aBodyId) {
2161 NS_ASSERT_OWNINGTHREAD(Manager);
2163 const auto end = mBodyIdRefs.end();
2164 const auto foundIt =
2165 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
2166 if (foundIt != end) {
2167 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
2168 MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
2169 foundIt->mOrphaned = true;
2170 return true;
2173 return false;
2176 void Manager::NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList) {
2177 NS_ASSERT_OWNINGTHREAD(Manager);
2179 // XXX TransformIfIntoNewArray might be generalized to allow specifying the
2180 // type of nsTArray to create, so that it can create an AutoTArray as well; an
2181 // TransformIf (without AbortOnErr) might be added, which could be used here.
2182 DeleteOrphanedBodyAction::DeletedBodyIdList deleteNowList;
2183 deleteNowList.SetCapacity(aDeletedBodyIdList.Length());
2185 std::copy_if(aDeletedBodyIdList.cbegin(), aDeletedBodyIdList.cend(),
2186 MakeBackInserter(deleteNowList),
2187 [this](const auto& deletedBodyId) {
2188 return !SetBodyIdOrphanedIfRefed(deletedBodyId);
2191 // TODO: note that we need to check these bodies for staleness on startup (bug
2192 // 1110446)
2193 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2194 if (!deleteNowList.IsEmpty() && pinnedContext &&
2195 !pinnedContext->IsCanceled()) {
2196 pinnedContext->Dispatch(
2197 MakeSafeRefPtr<DeleteOrphanedBodyAction>(std::move(deleteNowList)));
2201 void Manager::MaybeAllowContextToClose() {
2202 NS_ASSERT_OWNINGTHREAD(Manager);
2204 // If we have an active context, but we have no more users of the Manager,
2205 // then let it shut itself down. We must wait for all possible users of
2206 // Cache state information to complete before doing this. Once we allow
2207 // the Context to close we may not reliably get notified of storage
2208 // invalidation.
2209 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2210 if (pinnedContext && mListeners.IsEmpty() && mCacheIdRefs.IsEmpty() &&
2211 mBodyIdRefs.IsEmpty()) {
2212 // Mark this Manager as invalid so that it won't get used again. We don't
2213 // want to start any new operations once we allow the Context to close since
2214 // it may race with the underlying storage getting invalidated.
2215 NoteClosing();
2217 pinnedContext->AllowToClose();
2221 void Manager::DoStringify(nsACString& aData) {
2222 aData.Append("Manager "_ns + kStringifyStartInstance +
2224 "Origin:"_ns +
2225 quota::AnonymizedOriginString(GetManagerId().QuotaOrigin()) +
2226 kStringifyDelimiter +
2228 "State:"_ns + IntToCString(mState) + kStringifyDelimiter);
2230 aData.AppendLiteral("Context:");
2231 if (mContext) {
2232 mContext->Stringify(aData);
2233 } else {
2234 aData.AppendLiteral("0");
2237 aData.Append(kStringifyEndInstance);
2240 } // namespace mozilla::dom::cache