Bug 1733673 [wpt PR 31066] - Annotate CSS Transforms WPT reftests as fuzzy where...
[gecko.git] / dom / cache / Manager.cpp
blobfec4e990bb1740b93d5902630c76c3fd350d825a
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/AutoRestore.h"
11 #include "mozilla/Mutex.h"
12 #include "mozilla/StaticMutex.h"
13 #include "mozilla/StaticPtr.h"
14 #include "mozilla/Unused.h"
15 #include "mozilla/dom/cache/Context.h"
16 #include "mozilla/dom/cache/DBAction.h"
17 #include "mozilla/dom/cache/DBSchema.h"
18 #include "mozilla/dom/cache/FileUtils.h"
19 #include "mozilla/dom/cache/ManagerId.h"
20 #include "mozilla/dom/cache/CacheTypes.h"
21 #include "mozilla/dom/cache/SavedTypes.h"
22 #include "mozilla/dom/cache/StreamList.h"
23 #include "mozilla/dom/cache/Types.h"
24 #include "mozilla/dom/quota/Client.h"
25 #include "mozilla/dom/quota/ClientImpl.h"
26 #include "mozilla/dom/quota/QuotaManager.h"
27 #include "mozilla/ipc/BackgroundParent.h"
28 #include "mozStorageHelper.h"
29 #include "nsIInputStream.h"
30 #include "nsID.h"
31 #include "nsIFile.h"
32 #include "nsIThread.h"
33 #include "nsThreadUtils.h"
34 #include "nsTObserverArray.h"
35 #include "QuotaClientImpl.h"
37 namespace mozilla::dom::cache {
39 using mozilla::dom::quota::Client;
40 using mozilla::dom::quota::CloneFileAndAppend;
41 using mozilla::dom::quota::DirectoryLock;
43 namespace {
45 /**
46 * Note: The aCommitHook argument will be invoked while a lock is held. Callers
47 * should be careful not to pass a hook that might lock on something else and
48 * trigger a deadlock.
50 template <typename Callable>
51 nsresult MaybeUpdatePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn,
52 const int64_t aIncreaseSize,
53 const int64_t aDecreaseSize,
54 Callable aCommitHook) {
55 MOZ_ASSERT(!NS_IsMainThread());
56 MOZ_DIAGNOSTIC_ASSERT(aBaseDir);
57 MOZ_DIAGNOSTIC_ASSERT(aConn);
58 MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0);
59 MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0);
61 RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
62 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
64 QM_TRY(MOZ_TO_RESULT(cacheQuotaClient->MaybeUpdatePaddingFileInternal(
65 *aBaseDir, *aConn, aIncreaseSize, aDecreaseSize, aCommitHook)));
67 return NS_OK;
70 // An Action that is executed when a Context is first created. It ensures that
71 // the directory and database are setup properly. This lets other actions
72 // not worry about these details.
73 class SetupAction final : public SyncDBAction {
74 public:
75 SetupAction() : SyncDBAction(DBAction::Create) {}
77 virtual nsresult RunSyncWithDBOnTarget(
78 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
79 mozIStorageConnection* aConn) override {
80 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
82 QM_TRY(MOZ_TO_RESULT(BodyCreateDir(*aDBDir)));
84 // executes in its own transaction
85 QM_TRY(MOZ_TO_RESULT(db::CreateOrMigrateSchema(*aConn)));
87 // If the Context marker file exists, then the last session was
88 // not cleanly shutdown. In these cases sqlite will ensure that
89 // the database is valid, but we might still orphan data. Both
90 // Cache objects and body files can be referenced by DOM objects
91 // after they are "removed" from their parent. So we need to
92 // look and see if any of these late access objects have been
93 // orphaned.
95 // Note, this must be done after any schema version updates to
96 // ensure our DBSchema methods work correctly.
97 if (MarkerFileExists(aQuotaInfo)) {
98 NS_WARNING("Cache not shutdown cleanly! Cleaning up stale data...");
99 mozStorageTransaction trans(aConn, false,
100 mozIStorageConnection::TRANSACTION_IMMEDIATE);
102 QM_TRY(MOZ_TO_RESULT(trans.Start()));
104 // Clean up orphaned Cache objects
105 QM_TRY_INSPECT(const auto& orphanedCacheIdList,
106 db::FindOrphanedCacheIds(*aConn));
108 QM_TRY_INSPECT(
109 const CheckedInt64& overallDeletedPaddingSize,
110 Reduce(
111 orphanedCacheIdList, CheckedInt64(0),
112 [aConn, &aQuotaInfo, &aDBDir](
113 CheckedInt64 oldValue, const Maybe<const CacheId&>& element)
114 -> Result<CheckedInt64, nsresult> {
115 QM_TRY_INSPECT(const auto& deletionInfo,
116 db::DeleteCacheId(*aConn, *element));
118 QM_TRY(MOZ_TO_RESULT(BodyDeleteFiles(
119 aQuotaInfo, *aDBDir, deletionInfo.mDeletedBodyIdList)));
121 if (deletionInfo.mDeletedPaddingSize > 0) {
122 DecreaseUsageForQuotaInfo(aQuotaInfo,
123 deletionInfo.mDeletedPaddingSize);
126 return oldValue + deletionInfo.mDeletedPaddingSize;
127 }));
129 // Clean up orphaned body objects
130 QM_TRY_INSPECT(const auto& knownBodyIdList, db::GetKnownBodyIds(*aConn));
132 QM_TRY(MOZ_TO_RESULT(
133 BodyDeleteOrphanedFiles(aQuotaInfo, *aDBDir, knownBodyIdList)));
135 // Commit() explicitly here, because we want to ensure the padding file
136 // has the correct content.
137 // We'll restore padding file below, so just warn here if failure happens.
139 // XXX Before, if MaybeUpdatePaddingFile failed but we didn't enter the if
140 // body below, we would have propagated the MaybeUpdatePaddingFile
141 // failure, but if we entered it and RestorePaddingFile succeeded, we
142 // would have returned NS_OK. Now, we will never propagate a
143 // MaybeUpdatePaddingFile failure.
144 QM_WARNONLY_TRY(QM_TO_RESULT(
145 MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0,
146 overallDeletedPaddingSize.value(),
147 [&trans]() { return trans.Commit(); })));
150 if (DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::TMP_FILE) ||
151 !DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::FILE)) {
152 QM_TRY(MOZ_TO_RESULT(RestorePaddingFile(aDBDir, aConn)));
155 return NS_OK;
159 // ----------------------------------------------------------------------------
161 // Action that is executed when we determine that content has stopped using
162 // a body file that has been orphaned.
163 class DeleteOrphanedBodyAction final : public Action {
164 public:
165 using DeletedBodyIdList = AutoTArray<nsID, 64>;
167 explicit DeleteOrphanedBodyAction(DeletedBodyIdList&& aDeletedBodyIdList)
168 : mDeletedBodyIdList(std::move(aDeletedBodyIdList)) {}
170 explicit DeleteOrphanedBodyAction(const nsID& aBodyId)
171 : mDeletedBodyIdList{aBodyId} {}
173 void RunOnTarget(SafeRefPtr<Resolver> aResolver, const QuotaInfo& aQuotaInfo,
174 Data*) override {
175 MOZ_DIAGNOSTIC_ASSERT(aResolver);
176 MOZ_DIAGNOSTIC_ASSERT(aQuotaInfo.mDir);
178 // Note that since DeleteOrphanedBodyAction isn't used while the context is
179 // being initialized, we don't need to check for cancellation here.
181 const auto resolve = [&aResolver](const nsresult rv) {
182 aResolver->Resolve(rv);
185 QM_TRY_INSPECT(const auto& dbDir,
186 CloneFileAndAppend(*aQuotaInfo.mDir, u"cache"_ns), QM_VOID,
187 resolve);
189 QM_TRY(
190 MOZ_TO_RESULT(BodyDeleteFiles(aQuotaInfo, *dbDir, mDeletedBodyIdList)),
191 QM_VOID, resolve);
193 aResolver->Resolve(NS_OK);
196 private:
197 DeletedBodyIdList mDeletedBodyIdList;
200 bool IsHeadRequest(const CacheRequest& aRequest,
201 const CacheQueryParams& aParams) {
202 return !aParams.ignoreMethod() &&
203 aRequest.method().LowerCaseEqualsLiteral("head");
206 bool IsHeadRequest(const Maybe<CacheRequest>& aRequest,
207 const CacheQueryParams& aParams) {
208 if (aRequest.isSome()) {
209 return !aParams.ignoreMethod() &&
210 aRequest.ref().method().LowerCaseEqualsLiteral("head");
212 return false;
215 auto MatchByCacheId(CacheId aCacheId) {
216 return [aCacheId](const auto& entry) { return entry.mCacheId == aCacheId; };
219 auto MatchByBodyId(const nsID& aBodyId) {
220 return [&aBodyId](const auto& entry) { return entry.mBodyId == aBodyId; };
223 } // namespace
225 // ----------------------------------------------------------------------------
227 // Singleton class to track Manager instances and ensure there is only
228 // one for each unique ManagerId.
229 class Manager::Factory {
230 public:
231 friend class StaticAutoPtr<Manager::Factory>;
233 static Result<SafeRefPtr<Manager>, nsresult> AcquireCreateIfNonExistent(
234 const SafeRefPtr<ManagerId>& aManagerId) {
235 mozilla::ipc::AssertIsOnBackgroundThread();
237 // If we get here during/after quota manager shutdown, we bail out.
238 MOZ_ASSERT(AppShutdown::GetCurrentShutdownPhase() <
239 ShutdownPhase::AppShutdownQM);
240 if (AppShutdown::GetCurrentShutdownPhase() >=
241 ShutdownPhase::AppShutdownQM) {
242 NS_WARNING(
243 "Attempt to AcquireCreateIfNonExistent a Manager during QM "
244 "shutdown.");
245 return Err(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
248 // Ensure there is a factory instance. This forces the Acquire() call
249 // below to use the same factory.
250 QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()));
252 SafeRefPtr<Manager> ref = Acquire(*aManagerId);
253 if (!ref) {
254 // TODO: replace this with a thread pool (bug 1119864)
255 // XXX Can't use QM_TRY_INSPECT because that causes a clang-plugin
256 // error of the NoNewThreadsChecker.
257 nsCOMPtr<nsIThread> ioThread;
258 QM_TRY(MOZ_TO_RESULT(
259 NS_NewNamedThread("DOMCacheThread", getter_AddRefs(ioThread))));
261 ref = MakeSafeRefPtr<Manager>(aManagerId.clonePtr(), ioThread,
262 ConstructorGuard{});
264 // There may be an old manager for this origin in the process of
265 // cleaning up. We need to tell the new manager about this so
266 // that it won't actually start until the old manager is done.
267 const SafeRefPtr<Manager> oldManager = Acquire(*aManagerId, Closing);
268 ref->Init(oldManager.maybeDeref());
270 MOZ_ASSERT(!sFactory->mManagerList.Contains(ref));
271 sFactory->mManagerList.AppendElement(
272 WrapNotNullUnchecked(ref.unsafeGetRawPtr()));
275 return ref;
278 static void Remove(Manager& aManager) {
279 mozilla::ipc::AssertIsOnBackgroundThread();
280 MOZ_DIAGNOSTIC_ASSERT(sFactory);
282 MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(&aManager));
284 // This might both happen in late shutdown such that this event
285 // is executed even after the QuotaManager singleton passed away
286 // or if the QuotaManager has not yet been created.
287 quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
288 quota::Client::DOMCACHE, "Manager removed"_ns);
290 // clean up the factory singleton if there are no more managers
291 MaybeDestroyInstance();
294 static void Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
295 mozilla::ipc::AssertIsOnBackgroundThread();
297 AbortMatching([&aDirectoryLockIds](const auto& manager) {
298 // Check if the Manager holds an acquired DirectoryLock. Origin clearing
299 // can't be blocked by this Manager if there is no acquired DirectoryLock.
300 // If there is an acquired DirectoryLock, check if the table contains the
301 // lock for the Manager.
302 return Client::IsLockForObjectAcquiredAndContainedInLockTable(
303 manager, aDirectoryLockIds);
307 static void AbortAll() {
308 mozilla::ipc::AssertIsOnBackgroundThread();
310 AbortMatching([](const auto&) { return true; });
313 static void ShutdownAll() {
314 mozilla::ipc::AssertIsOnBackgroundThread();
316 if (!sFactory) {
317 return;
320 MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
323 // Note that we are synchronously calling shutdown code here. If any
324 // of the shutdown code synchronously decides to delete the Factory
325 // we need to delay that delete until the end of this method.
326 AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
327 sFactory->mInSyncAbortOrShutdown = true;
329 for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
330 auto pinnedManager =
331 SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
332 pinnedManager->Shutdown();
336 MaybeDestroyInstance();
339 static bool IsShutdownAllComplete() {
340 mozilla::ipc::AssertIsOnBackgroundThread();
341 return !sFactory;
344 static nsCString GetShutdownStatus() {
345 mozilla::ipc::AssertIsOnBackgroundThread();
347 nsCString data;
349 if (sFactory && !sFactory->mManagerList.IsEmpty()) {
350 data.Append(
351 "Managers: "_ns +
352 IntToCString(static_cast<uint64_t>(sFactory->mManagerList.Length())) +
353 " ("_ns);
355 for (const auto& manager : sFactory->mManagerList.NonObservingRange()) {
356 data.Append(quota::AnonymizedOriginString(
357 manager->GetManagerId().QuotaOrigin()));
359 data.AppendLiteral(": ");
361 data.Append(manager->GetState() == State::Open ? "Open"_ns
362 : "Closing"_ns);
364 data.AppendLiteral(", ");
367 data.AppendLiteral(" )");
370 return data;
373 private:
374 Factory() : mInSyncAbortOrShutdown(false) {
375 MOZ_COUNT_CTOR(cache::Manager::Factory);
378 ~Factory() {
379 MOZ_COUNT_DTOR(cache::Manager::Factory);
380 MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
381 MOZ_DIAGNOSTIC_ASSERT(!mInSyncAbortOrShutdown);
384 static nsresult MaybeCreateInstance() {
385 mozilla::ipc::AssertIsOnBackgroundThread();
387 if (!sFactory) {
388 // We cannot use ClearOnShutdown() here because we're not on the main
389 // thread. Instead, we delete sFactory in Factory::Remove() after the
390 // last manager is removed. ShutdownObserver ensures this happens
391 // before shutdown.
392 sFactory = new Factory();
395 // Never return sFactory to code outside Factory. We need to delete it
396 // out from under ourselves just before we return from Remove(). This
397 // would be (even more) dangerous if other code had a pointer to the
398 // factory itself.
400 return NS_OK;
403 static void MaybeDestroyInstance() {
404 mozilla::ipc::AssertIsOnBackgroundThread();
405 MOZ_DIAGNOSTIC_ASSERT(sFactory);
407 // If the factory is is still in use then we cannot delete yet. This
408 // could be due to managers still existing or because we are in the
409 // middle of aborting or shutting down. We need to be careful not to delete
410 // ourself synchronously during shutdown.
411 if (!sFactory->mManagerList.IsEmpty() || sFactory->mInSyncAbortOrShutdown) {
412 return;
415 sFactory = nullptr;
418 static SafeRefPtr<Manager> Acquire(const ManagerId& aManagerId,
419 State aState = Open) {
420 mozilla::ipc::AssertIsOnBackgroundThread();
422 QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()), nullptr);
424 // Iterate in reverse to find the most recent, matching Manager. This
425 // is important when looking for a Closing Manager. If a new Manager
426 // chains to an old Manager we want it to be the most recent one.
427 const auto range = Reversed(sFactory->mManagerList.NonObservingRange());
428 const auto foundIt = std::find_if(
429 range.begin(), range.end(), [aState, &aManagerId](const auto& manager) {
430 return aState == manager->GetState() &&
431 *manager->mManagerId == aManagerId;
433 return foundIt != range.end()
434 ? SafeRefPtr{foundIt->get(), AcquireStrongRefFromRawPtr{}}
435 : nullptr;
438 template <typename Condition>
439 static void AbortMatching(const Condition& aCondition) {
440 mozilla::ipc::AssertIsOnBackgroundThread();
442 if (!sFactory) {
443 return;
446 MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
449 // Note that we are synchronously calling abort code here. If any
450 // of the shutdown code synchronously decides to delete the Factory
451 // we need to delay that delete until the end of this method.
452 AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
453 sFactory->mInSyncAbortOrShutdown = true;
455 for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
456 if (aCondition(*manager)) {
457 auto pinnedManager =
458 SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
459 pinnedManager->Abort();
464 MaybeDestroyInstance();
467 // Singleton created on demand and deleted when last Manager is cleared
468 // in Remove().
469 // PBackground thread only.
470 static StaticAutoPtr<Factory> sFactory;
472 // Weak references as we don't want to keep Manager objects alive forever.
473 // When a Manager is destroyed it calls Factory::Remove() to clear itself.
474 // PBackground thread only.
475 nsTObserverArray<NotNull<Manager*>> mManagerList;
477 // This flag is set when we are looping through the list and calling Abort()
478 // or Shutdown() on each Manager. We need to be careful not to synchronously
479 // trigger the deletion of the factory while still executing this loop.
480 bool mInSyncAbortOrShutdown;
483 // static
484 StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
486 // ----------------------------------------------------------------------------
488 // Abstract class to help implement the various Actions. The vast majority
489 // of Actions are synchronous and need to report back to a Listener on the
490 // Manager.
491 class Manager::BaseAction : public SyncDBAction {
492 protected:
493 BaseAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId)
494 : SyncDBAction(DBAction::Existing),
495 mManager(std::move(aManager)),
496 mListenerId(aListenerId) {}
498 virtual void Complete(Listener* aListener, ErrorResult&& aRv) = 0;
500 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
501 NS_ASSERT_OWNINGTHREAD(Manager::BaseAction);
502 Listener* listener = mManager->GetListener(mListenerId);
503 if (listener) {
504 Complete(listener, ErrorResult(aRv));
507 // ensure we release the manager on the initiating thread
508 mManager = nullptr;
511 SafeRefPtr<Manager> mManager;
512 const ListenerId mListenerId;
515 // ----------------------------------------------------------------------------
517 // Action that is executed when we determine that content has stopped using
518 // a Cache object that has been orphaned.
519 class Manager::DeleteOrphanedCacheAction final : public SyncDBAction {
520 public:
521 DeleteOrphanedCacheAction(SafeRefPtr<Manager> aManager, CacheId aCacheId)
522 : SyncDBAction(DBAction::Existing),
523 mManager(std::move(aManager)),
524 mCacheId(aCacheId) {}
526 virtual nsresult RunSyncWithDBOnTarget(
527 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
528 mozIStorageConnection* aConn) override {
529 mQuotaInfo.emplace(aQuotaInfo);
531 mozStorageTransaction trans(aConn, false,
532 mozIStorageConnection::TRANSACTION_IMMEDIATE);
534 QM_TRY(MOZ_TO_RESULT(trans.Start()));
536 QM_TRY_UNWRAP(mDeletionInfo, db::DeleteCacheId(*aConn, mCacheId));
538 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
539 aDBDir, aConn, /* aIncreaceSize */ 0, mDeletionInfo.mDeletedPaddingSize,
540 [&trans]() mutable { return trans.Commit(); })));
542 return NS_OK;
545 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
546 // If the transaction fails, we shouldn't delete the body files and decrease
547 // their padding size.
548 if (NS_FAILED(aRv)) {
549 mDeletionInfo.mDeletedBodyIdList.Clear();
550 mDeletionInfo.mDeletedPaddingSize = 0;
553 mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
555 if (mDeletionInfo.mDeletedPaddingSize > 0) {
556 DecreaseUsageForQuotaInfo(mQuotaInfo.ref(),
557 mDeletionInfo.mDeletedPaddingSize);
560 // ensure we release the manager on the initiating thread
561 mManager = nullptr;
564 private:
565 SafeRefPtr<Manager> mManager;
566 const CacheId mCacheId;
567 DeletionInfo mDeletionInfo;
568 Maybe<QuotaInfo> mQuotaInfo;
571 // ----------------------------------------------------------------------------
573 class Manager::CacheMatchAction final : public Manager::BaseAction {
574 public:
575 CacheMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
576 CacheId aCacheId, const CacheMatchArgs& aArgs,
577 SafeRefPtr<StreamList> aStreamList)
578 : BaseAction(std::move(aManager), aListenerId),
579 mCacheId(aCacheId),
580 mArgs(aArgs),
581 mStreamList(std::move(aStreamList)),
582 mFoundResponse(false) {}
584 virtual nsresult RunSyncWithDBOnTarget(
585 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
586 mozIStorageConnection* aConn) override {
587 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
589 QM_TRY_INSPECT(
590 const auto& maybeResponse,
591 db::CacheMatch(*aConn, mCacheId, mArgs.request(), mArgs.params()));
593 mFoundResponse = maybeResponse.isSome();
594 if (mFoundResponse) {
595 mResponse = std::move(maybeResponse.ref());
598 if (!mFoundResponse || !mResponse.mHasBodyId ||
599 IsHeadRequest(mArgs.request(), mArgs.params())) {
600 mResponse.mHasBodyId = false;
601 return NS_OK;
604 nsCOMPtr<nsIInputStream> stream;
605 if (mArgs.openMode() == OpenMode::Eager) {
606 QM_TRY_UNWRAP(stream, BodyOpen(aQuotaInfo, *aDBDir, mResponse.mBodyId));
609 mStreamList->Add(mResponse.mBodyId, std::move(stream));
611 return NS_OK;
614 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
615 if (!mFoundResponse) {
616 aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()));
617 } else {
618 mStreamList->Activate(mCacheId);
619 aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()),
620 mResponse, *mStreamList);
622 mStreamList = nullptr;
625 virtual bool MatchesCacheId(CacheId aCacheId) const override {
626 return aCacheId == mCacheId;
629 private:
630 const CacheId mCacheId;
631 const CacheMatchArgs mArgs;
632 SafeRefPtr<StreamList> mStreamList;
633 bool mFoundResponse;
634 SavedResponse mResponse;
637 // ----------------------------------------------------------------------------
639 class Manager::CacheMatchAllAction final : public Manager::BaseAction {
640 public:
641 CacheMatchAllAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
642 CacheId aCacheId, const CacheMatchAllArgs& aArgs,
643 SafeRefPtr<StreamList> aStreamList)
644 : BaseAction(std::move(aManager), aListenerId),
645 mCacheId(aCacheId),
646 mArgs(aArgs),
647 mStreamList(std::move(aStreamList)) {}
649 virtual nsresult RunSyncWithDBOnTarget(
650 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
651 mozIStorageConnection* aConn) override {
652 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
654 QM_TRY_UNWRAP(mSavedResponses,
655 db::CacheMatchAll(*aConn, mCacheId, mArgs.maybeRequest(),
656 mArgs.params()));
658 for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) {
659 if (!mSavedResponses[i].mHasBodyId ||
660 IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
661 mSavedResponses[i].mHasBodyId = false;
662 continue;
665 nsCOMPtr<nsIInputStream> stream;
666 if (mArgs.openMode() == OpenMode::Eager) {
667 QM_TRY_UNWRAP(
668 stream, BodyOpen(aQuotaInfo, *aDBDir, mSavedResponses[i].mBodyId));
671 mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream));
674 return NS_OK;
677 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
678 mStreamList->Activate(mCacheId);
679 aListener->OnOpComplete(std::move(aRv), CacheMatchAllResult(),
680 mSavedResponses, *mStreamList);
681 mStreamList = nullptr;
684 virtual bool MatchesCacheId(CacheId aCacheId) const override {
685 return aCacheId == mCacheId;
688 private:
689 const CacheId mCacheId;
690 const CacheMatchAllArgs mArgs;
691 SafeRefPtr<StreamList> mStreamList;
692 nsTArray<SavedResponse> mSavedResponses;
695 // ----------------------------------------------------------------------------
697 // This is the most complex Action. It puts a request/response pair into the
698 // Cache. It does not complete until all of the body data has been saved to
699 // disk. This means its an asynchronous Action.
700 class Manager::CachePutAllAction final : public DBAction {
701 public:
702 CachePutAllAction(
703 SafeRefPtr<Manager> aManager, ListenerId aListenerId, CacheId aCacheId,
704 const nsTArray<CacheRequestResponse>& aPutList,
705 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
706 const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
707 : DBAction(DBAction::Existing),
708 mManager(std::move(aManager)),
709 mListenerId(aListenerId),
710 mCacheId(aCacheId),
711 mList(aPutList.Length()),
712 mExpectedAsyncCopyCompletions(1),
713 mAsyncResult(NS_OK),
714 mMutex("cache::Manager::CachePutAllAction"),
715 mUpdatedPaddingSize(0),
716 mDeletedPaddingSize(0) {
717 MOZ_DIAGNOSTIC_ASSERT(!aPutList.IsEmpty());
718 MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aRequestStreamList.Length());
719 MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aResponseStreamList.Length());
721 for (uint32_t i = 0; i < aPutList.Length(); ++i) {
722 Entry* entry = mList.AppendElement();
723 entry->mRequest = aPutList[i].request();
724 entry->mRequestStream = aRequestStreamList[i];
725 entry->mResponse = aPutList[i].response();
726 entry->mResponseStream = aResponseStreamList[i];
730 private:
731 ~CachePutAllAction() = default;
733 virtual void RunWithDBOnTarget(SafeRefPtr<Resolver> aResolver,
734 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
735 mozIStorageConnection* aConn) override {
736 MOZ_DIAGNOSTIC_ASSERT(aResolver);
737 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
738 MOZ_DIAGNOSTIC_ASSERT(aConn);
739 MOZ_DIAGNOSTIC_ASSERT(!mResolver);
740 MOZ_DIAGNOSTIC_ASSERT(!mDBDir);
741 MOZ_DIAGNOSTIC_ASSERT(!mConn);
743 MOZ_DIAGNOSTIC_ASSERT(!mTarget);
744 mTarget = GetCurrentSerialEventTarget();
745 MOZ_DIAGNOSTIC_ASSERT(mTarget);
747 // We should be pre-initialized to expect one async completion. This is
748 // the "manual" completion we call at the end of this method in all
749 // cases.
750 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions == 1);
752 mResolver = std::move(aResolver);
753 mDBDir = aDBDir;
754 mConn = aConn;
755 mQuotaInfo.emplace(aQuotaInfo);
757 // File bodies are streamed to disk via asynchronous copying. Start
758 // this copying now. Each copy will eventually result in a call
759 // to OnAsyncCopyComplete().
760 const nsresult rv = [this, &aQuotaInfo]() -> nsresult {
761 QM_TRY(CollectEachInRange(
762 mList, [this, &aQuotaInfo](auto& entry) -> nsresult {
763 QM_TRY(
764 MOZ_TO_RESULT(StartStreamCopy(aQuotaInfo, entry, RequestStream,
765 &mExpectedAsyncCopyCompletions)));
767 QM_TRY(
768 MOZ_TO_RESULT(StartStreamCopy(aQuotaInfo, entry, ResponseStream,
769 &mExpectedAsyncCopyCompletions)));
771 return NS_OK;
772 }));
774 return NS_OK;
775 }();
777 // Always call OnAsyncCopyComplete() manually here. This covers the
778 // case where there is no async copying and also reports any startup
779 // errors correctly. If we hit an error, then OnAsyncCopyComplete()
780 // will cancel any async copying.
781 OnAsyncCopyComplete(rv);
784 // Called once for each asynchronous file copy whether it succeeds or
785 // fails. If a file copy is canceled, it still calls this method with
786 // an error code.
787 void OnAsyncCopyComplete(nsresult aRv) {
788 MOZ_ASSERT(mTarget->IsOnCurrentThread());
789 MOZ_DIAGNOSTIC_ASSERT(mConn);
790 MOZ_DIAGNOSTIC_ASSERT(mResolver);
791 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions > 0);
793 // Explicitly check for cancellation here to catch a race condition.
794 // Consider:
796 // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
797 // copy context yet.
798 // 2) CancelAllStreamCopying() occurs on PBackground thread
799 // 3) Copy context from (1) is saved on IO thread.
801 // Checking for cancellation here catches this condition when we
802 // first call OnAsyncCopyComplete() manually from RunWithDBOnTarget().
804 // This explicit cancellation check also handles the case where we
805 // are canceled just after all stream copying completes. We should
806 // abort the synchronous DB operations in this case if we have not
807 // started them yet.
808 if (NS_SUCCEEDED(aRv) && IsCanceled()) {
809 aRv = NS_ERROR_ABORT;
812 // If any of the async copies fail, we need to still wait for them all to
813 // complete. Cancel any other streams still working and remember the
814 // error. All canceled streams will call OnAsyncCopyComplete().
815 if (NS_FAILED(aRv) && NS_SUCCEEDED(mAsyncResult)) {
816 CancelAllStreamCopying();
817 mAsyncResult = aRv;
820 // Check to see if async copying is still on-going. If so, then simply
821 // return for now. We must wait for a later OnAsyncCopyComplete() call.
822 mExpectedAsyncCopyCompletions -= 1;
823 if (mExpectedAsyncCopyCompletions > 0) {
824 return;
827 // We have finished with all async copying. Indicate this by clearing all
828 // our copy contexts.
830 MutexAutoLock lock(mMutex);
831 mCopyContextList.Clear();
834 // An error occurred while async copying. Terminate the Action.
835 // DoResolve() will clean up any files we may have written.
836 if (NS_FAILED(mAsyncResult)) {
837 DoResolve(mAsyncResult);
838 return;
841 mozStorageTransaction trans(mConn, false,
842 mozIStorageConnection::TRANSACTION_IMMEDIATE);
844 QM_TRY(MOZ_TO_RESULT(trans.Start()), QM_VOID);
846 const nsresult rv = [this, &trans]() -> nsresult {
847 QM_TRY(CollectEachInRange(mList, [this](Entry& e) -> nsresult {
848 if (e.mRequestStream) {
849 QM_TRY(MOZ_TO_RESULT(BodyFinalizeWrite(*mDBDir, e.mRequestBodyId)));
851 if (e.mResponseStream) {
852 // Gerenate padding size for opaque response if needed.
853 if (e.mResponse.type() == ResponseType::Opaque) {
854 // It'll generate padding if we've not set it yet.
855 QM_TRY(MOZ_TO_RESULT(BodyMaybeUpdatePaddingSize(
856 mQuotaInfo.ref(), *mDBDir, e.mResponseBodyId,
857 e.mResponse.paddingInfo(), &e.mResponse.paddingSize())));
859 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - e.mResponse.paddingSize() >=
860 mUpdatedPaddingSize);
861 mUpdatedPaddingSize += e.mResponse.paddingSize();
864 QM_TRY(MOZ_TO_RESULT(BodyFinalizeWrite(*mDBDir, e.mResponseBodyId)));
867 QM_TRY_UNWRAP(
868 auto deletionInfo,
869 db::CachePut(*mConn, mCacheId, e.mRequest,
870 e.mRequestStream ? &e.mRequestBodyId : nullptr,
871 e.mResponse,
872 e.mResponseStream ? &e.mResponseBodyId : nullptr));
874 const int64_t deletedPaddingSize = deletionInfo.mDeletedPaddingSize;
875 mDeletedBodyIdList = std::move(deletionInfo.mDeletedBodyIdList);
877 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - mDeletedPaddingSize >=
878 deletedPaddingSize);
879 mDeletedPaddingSize += deletedPaddingSize;
881 return NS_OK;
882 }));
884 // Update padding file when it's necessary
885 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
886 mDBDir, mConn, mUpdatedPaddingSize, mDeletedPaddingSize,
887 [&trans]() mutable { return trans.Commit(); })));
889 return NS_OK;
890 }();
892 DoResolve(rv);
895 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
896 NS_ASSERT_OWNINGTHREAD(Action);
898 for (uint32_t i = 0; i < mList.Length(); ++i) {
899 mList[i].mRequestStream = nullptr;
900 mList[i].mResponseStream = nullptr;
903 // If the transaction fails, we shouldn't delete the body files and decrease
904 // their padding size.
905 if (NS_FAILED(aRv)) {
906 mDeletedBodyIdList.Clear();
907 mDeletedPaddingSize = 0;
910 mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
912 if (mDeletedPaddingSize > 0) {
913 DecreaseUsageForQuotaInfo(mQuotaInfo.ref(), mDeletedPaddingSize);
916 Listener* listener = mManager->GetListener(mListenerId);
917 mManager = nullptr;
918 if (listener) {
919 listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult());
923 virtual void CancelOnInitiatingThread() override {
924 NS_ASSERT_OWNINGTHREAD(Action);
925 Action::CancelOnInitiatingThread();
926 CancelAllStreamCopying();
929 virtual bool MatchesCacheId(CacheId aCacheId) const override {
930 NS_ASSERT_OWNINGTHREAD(Action);
931 return aCacheId == mCacheId;
934 struct Entry {
935 CacheRequest mRequest;
936 nsCOMPtr<nsIInputStream> mRequestStream;
937 nsID mRequestBodyId;
938 nsCOMPtr<nsISupports> mRequestCopyContext;
940 CacheResponse mResponse;
941 nsCOMPtr<nsIInputStream> mResponseStream;
942 nsID mResponseBodyId;
943 nsCOMPtr<nsISupports> mResponseCopyContext;
946 enum StreamId { RequestStream, ResponseStream };
948 nsresult StartStreamCopy(const QuotaInfo& aQuotaInfo, Entry& aEntry,
949 StreamId aStreamId, uint32_t* aCopyCountOut) {
950 MOZ_ASSERT(mTarget->IsOnCurrentThread());
951 MOZ_DIAGNOSTIC_ASSERT(aCopyCountOut);
953 if (IsCanceled()) {
954 return NS_ERROR_ABORT;
957 MOZ_DIAGNOSTIC_ASSERT(aStreamId == RequestStream ||
958 aStreamId == ResponseStream);
960 const auto& source = aStreamId == RequestStream ? aEntry.mRequestStream
961 : aEntry.mResponseStream;
963 if (!source) {
964 return NS_OK;
967 QM_TRY_INSPECT((const auto& [bodyId, copyContext]),
968 BodyStartWriteStream(aQuotaInfo, *mDBDir, *source, this,
969 AsyncCopyCompleteFunc));
971 if (aStreamId == RequestStream) {
972 aEntry.mRequestBodyId = bodyId;
973 } else {
974 aEntry.mResponseBodyId = bodyId;
977 mBodyIdWrittenList.AppendElement(bodyId);
979 if (copyContext) {
980 MutexAutoLock lock(mMutex);
981 mCopyContextList.AppendElement(copyContext);
984 *aCopyCountOut += 1;
986 return NS_OK;
989 void CancelAllStreamCopying() {
990 // May occur on either owning thread or target thread
991 MutexAutoLock lock(mMutex);
992 for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) {
993 MOZ_DIAGNOSTIC_ASSERT(mCopyContextList[i]);
994 BodyCancelWrite(*mCopyContextList[i]);
996 mCopyContextList.Clear();
999 static void AsyncCopyCompleteFunc(void* aClosure, nsresult aRv) {
1000 // May be on any thread, including STS event target.
1001 MOZ_DIAGNOSTIC_ASSERT(aClosure);
1002 // Weak ref as we are guaranteed to the action is alive until
1003 // CompleteOnInitiatingThread is called.
1004 CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure);
1005 action->CallOnAsyncCopyCompleteOnTargetThread(aRv);
1008 void CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv) {
1009 // May be on any thread, including STS event target. Non-owning runnable
1010 // here since we are guaranteed the Action will survive until
1011 // CompleteOnInitiatingThread is called.
1012 nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod<nsresult>(
1013 "dom::cache::Manager::CachePutAllAction::OnAsyncCopyComplete", this,
1014 &CachePutAllAction::OnAsyncCopyComplete, aRv);
1015 MOZ_ALWAYS_SUCCEEDS(
1016 mTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL));
1019 void DoResolve(nsresult aRv) {
1020 MOZ_ASSERT(mTarget->IsOnCurrentThread());
1022 // DoResolve() must not be called until all async copying has completed.
1023 #ifdef DEBUG
1025 MutexAutoLock lock(mMutex);
1026 MOZ_ASSERT(mCopyContextList.IsEmpty());
1028 #endif
1030 // Clean up any files we might have written before hitting the error.
1031 if (NS_FAILED(aRv)) {
1032 BodyDeleteFiles(mQuotaInfo.ref(), *mDBDir, mBodyIdWrittenList);
1033 if (mUpdatedPaddingSize > 0) {
1034 DecreaseUsageForQuotaInfo(mQuotaInfo.ref(), mUpdatedPaddingSize);
1038 // Must be released on the target thread where it was opened.
1039 mConn = nullptr;
1041 // Drop our ref to the target thread as we are done with this thread.
1042 // Also makes our thread assertions catch any incorrect method calls
1043 // after resolve.
1044 mTarget = nullptr;
1046 // Make sure to de-ref the resolver per the Action API contract.
1047 SafeRefPtr<Action::Resolver> resolver = std::move(mResolver);
1048 resolver->Resolve(aRv);
1051 // initiating thread only
1052 SafeRefPtr<Manager> mManager;
1053 const ListenerId mListenerId;
1055 // Set on initiating thread, read on target thread. State machine guarantees
1056 // these are not modified while being read by the target thread.
1057 const CacheId mCacheId;
1058 nsTArray<Entry> mList;
1059 uint32_t mExpectedAsyncCopyCompletions;
1061 // target thread only
1062 SafeRefPtr<Resolver> mResolver;
1063 nsCOMPtr<nsIFile> mDBDir;
1064 nsCOMPtr<mozIStorageConnection> mConn;
1065 nsCOMPtr<nsISerialEventTarget> mTarget;
1066 nsresult mAsyncResult;
1067 nsTArray<nsID> mBodyIdWrittenList;
1069 // Written to on target thread, accessed on initiating thread after target
1070 // thread activity is guaranteed complete
1071 nsTArray<nsID> mDeletedBodyIdList;
1073 // accessed from any thread while mMutex locked
1074 Mutex mMutex;
1075 nsTArray<nsCOMPtr<nsISupports>> mCopyContextList;
1077 Maybe<QuotaInfo> mQuotaInfo;
1078 // Track how much pad amount has been added for new entries so that it can be
1079 // removed if an error occurs.
1080 int64_t mUpdatedPaddingSize;
1081 // Track any pad amount associated with overwritten entries.
1082 int64_t mDeletedPaddingSize;
1085 // ----------------------------------------------------------------------------
1087 class Manager::CacheDeleteAction final : public Manager::BaseAction {
1088 public:
1089 CacheDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1090 CacheId aCacheId, const CacheDeleteArgs& aArgs)
1091 : BaseAction(std::move(aManager), aListenerId),
1092 mCacheId(aCacheId),
1093 mArgs(aArgs),
1094 mSuccess(false) {}
1096 virtual nsresult RunSyncWithDBOnTarget(
1097 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
1098 mozIStorageConnection* aConn) override {
1099 mQuotaInfo.emplace(aQuotaInfo);
1101 mozStorageTransaction trans(aConn, false,
1102 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1104 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1106 QM_TRY_UNWRAP(
1107 auto maybeDeletionInfo,
1108 db::CacheDelete(*aConn, mCacheId, mArgs.request(), mArgs.params()));
1110 mSuccess = maybeDeletionInfo.isSome();
1111 if (mSuccess) {
1112 mDeletionInfo = std::move(maybeDeletionInfo.ref());
1115 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
1116 aDBDir, aConn, /* aIncreaceSize */ 0,
1117 mDeletionInfo.mDeletedPaddingSize,
1118 [&trans]() mutable { return trans.Commit(); })),
1119 QM_PROPAGATE, [this](const nsresult) { mSuccess = false; });
1121 return NS_OK;
1124 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1125 // If the transaction fails, we shouldn't delete the body files and decrease
1126 // their padding size.
1127 if (aRv.Failed()) {
1128 mDeletionInfo.mDeletedBodyIdList.Clear();
1129 mDeletionInfo.mDeletedPaddingSize = 0;
1132 mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
1134 if (mDeletionInfo.mDeletedPaddingSize > 0) {
1135 DecreaseUsageForQuotaInfo(mQuotaInfo.ref(),
1136 mDeletionInfo.mDeletedPaddingSize);
1139 aListener->OnOpComplete(std::move(aRv), CacheDeleteResult(mSuccess));
1142 virtual bool MatchesCacheId(CacheId aCacheId) const override {
1143 return aCacheId == mCacheId;
1146 private:
1147 const CacheId mCacheId;
1148 const CacheDeleteArgs mArgs;
1149 bool mSuccess;
1150 DeletionInfo mDeletionInfo;
1151 Maybe<QuotaInfo> mQuotaInfo;
1154 // ----------------------------------------------------------------------------
1156 class Manager::CacheKeysAction final : public Manager::BaseAction {
1157 public:
1158 CacheKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1159 CacheId aCacheId, const CacheKeysArgs& aArgs,
1160 SafeRefPtr<StreamList> aStreamList)
1161 : BaseAction(std::move(aManager), aListenerId),
1162 mCacheId(aCacheId),
1163 mArgs(aArgs),
1164 mStreamList(std::move(aStreamList)) {}
1166 virtual nsresult RunSyncWithDBOnTarget(
1167 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
1168 mozIStorageConnection* aConn) override {
1169 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1171 QM_TRY_UNWRAP(
1172 mSavedRequests,
1173 db::CacheKeys(*aConn, mCacheId, mArgs.maybeRequest(), mArgs.params()));
1175 for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) {
1176 if (!mSavedRequests[i].mHasBodyId ||
1177 IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
1178 mSavedRequests[i].mHasBodyId = false;
1179 continue;
1182 nsCOMPtr<nsIInputStream> stream;
1183 if (mArgs.openMode() == OpenMode::Eager) {
1184 QM_TRY_UNWRAP(stream,
1185 BodyOpen(aQuotaInfo, *aDBDir, mSavedRequests[i].mBodyId));
1188 mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream));
1191 return NS_OK;
1194 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1195 mStreamList->Activate(mCacheId);
1196 aListener->OnOpComplete(std::move(aRv), CacheKeysResult(), mSavedRequests,
1197 *mStreamList);
1198 mStreamList = nullptr;
1201 virtual bool MatchesCacheId(CacheId aCacheId) const override {
1202 return aCacheId == mCacheId;
1205 private:
1206 const CacheId mCacheId;
1207 const CacheKeysArgs mArgs;
1208 SafeRefPtr<StreamList> mStreamList;
1209 nsTArray<SavedRequest> mSavedRequests;
1212 // ----------------------------------------------------------------------------
1214 class Manager::StorageMatchAction final : public Manager::BaseAction {
1215 public:
1216 StorageMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1217 Namespace aNamespace, const StorageMatchArgs& aArgs,
1218 SafeRefPtr<StreamList> aStreamList)
1219 : BaseAction(std::move(aManager), aListenerId),
1220 mNamespace(aNamespace),
1221 mArgs(aArgs),
1222 mStreamList(std::move(aStreamList)),
1223 mFoundResponse(false) {}
1225 virtual nsresult RunSyncWithDBOnTarget(
1226 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
1227 mozIStorageConnection* aConn) override {
1228 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1230 auto maybeResponse =
1231 db::StorageMatch(*aConn, mNamespace, mArgs.request(), mArgs.params());
1232 if (NS_WARN_IF(maybeResponse.isErr())) {
1233 return maybeResponse.unwrapErr();
1236 mFoundResponse = maybeResponse.inspect().isSome();
1237 if (mFoundResponse) {
1238 mSavedResponse = maybeResponse.unwrap().ref();
1241 if (!mFoundResponse || !mSavedResponse.mHasBodyId ||
1242 IsHeadRequest(mArgs.request(), mArgs.params())) {
1243 mSavedResponse.mHasBodyId = false;
1244 return NS_OK;
1247 nsCOMPtr<nsIInputStream> stream;
1248 if (mArgs.openMode() == OpenMode::Eager) {
1249 QM_TRY_UNWRAP(stream,
1250 BodyOpen(aQuotaInfo, *aDBDir, mSavedResponse.mBodyId));
1253 mStreamList->Add(mSavedResponse.mBodyId, std::move(stream));
1255 return NS_OK;
1258 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1259 if (!mFoundResponse) {
1260 aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()));
1261 } else {
1262 mStreamList->Activate(mSavedResponse.mCacheId);
1263 aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()),
1264 mSavedResponse, *mStreamList);
1266 mStreamList = nullptr;
1269 private:
1270 const Namespace mNamespace;
1271 const StorageMatchArgs mArgs;
1272 SafeRefPtr<StreamList> mStreamList;
1273 bool mFoundResponse;
1274 SavedResponse mSavedResponse;
1277 // ----------------------------------------------------------------------------
1279 class Manager::StorageHasAction final : public Manager::BaseAction {
1280 public:
1281 StorageHasAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1282 Namespace aNamespace, const StorageHasArgs& aArgs)
1283 : BaseAction(std::move(aManager), aListenerId),
1284 mNamespace(aNamespace),
1285 mArgs(aArgs),
1286 mCacheFound(false) {}
1288 virtual nsresult RunSyncWithDBOnTarget(
1289 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
1290 mozIStorageConnection* aConn) override {
1291 QM_TRY_INSPECT(const auto& maybeCacheId,
1292 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1294 mCacheFound = maybeCacheId.isSome();
1296 return NS_OK;
1299 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1300 aListener->OnOpComplete(std::move(aRv), StorageHasResult(mCacheFound));
1303 private:
1304 const Namespace mNamespace;
1305 const StorageHasArgs mArgs;
1306 bool mCacheFound;
1309 // ----------------------------------------------------------------------------
1311 class Manager::StorageOpenAction final : public Manager::BaseAction {
1312 public:
1313 StorageOpenAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1314 Namespace aNamespace, const StorageOpenArgs& aArgs)
1315 : BaseAction(std::move(aManager), aListenerId),
1316 mNamespace(aNamespace),
1317 mArgs(aArgs),
1318 mCacheId(INVALID_CACHE_ID) {}
1320 virtual nsresult RunSyncWithDBOnTarget(
1321 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
1322 mozIStorageConnection* aConn) override {
1323 // Cache does not exist, create it instead
1324 mozStorageTransaction trans(aConn, false,
1325 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1327 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1329 // Look for existing cache
1330 QM_TRY_INSPECT(const auto& maybeCacheId,
1331 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1333 if (maybeCacheId.isSome()) {
1334 mCacheId = maybeCacheId.ref();
1335 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
1336 return NS_OK;
1339 QM_TRY_UNWRAP(mCacheId, db::CreateCacheId(*aConn));
1341 QM_TRY(MOZ_TO_RESULT(
1342 db::StoragePutCache(*aConn, mNamespace, mArgs.key(), mCacheId)));
1344 QM_TRY(MOZ_TO_RESULT(trans.Commit()));
1346 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
1347 return NS_OK;
1350 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1351 MOZ_DIAGNOSTIC_ASSERT(aRv.Failed() || mCacheId != INVALID_CACHE_ID);
1352 aListener->OnOpComplete(std::move(aRv),
1353 StorageOpenResult(nullptr, nullptr, mNamespace),
1354 mCacheId);
1357 private:
1358 const Namespace mNamespace;
1359 const StorageOpenArgs mArgs;
1360 CacheId mCacheId;
1363 // ----------------------------------------------------------------------------
1365 class Manager::StorageDeleteAction final : public Manager::BaseAction {
1366 public:
1367 StorageDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1368 Namespace aNamespace, const StorageDeleteArgs& aArgs)
1369 : BaseAction(std::move(aManager), aListenerId),
1370 mNamespace(aNamespace),
1371 mArgs(aArgs),
1372 mCacheDeleted(false),
1373 mCacheId(INVALID_CACHE_ID) {}
1375 virtual nsresult RunSyncWithDBOnTarget(
1376 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
1377 mozIStorageConnection* aConn) override {
1378 mozStorageTransaction trans(aConn, false,
1379 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1381 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1383 QM_TRY_INSPECT(const auto& maybeCacheId,
1384 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1386 if (maybeCacheId.isNothing()) {
1387 mCacheDeleted = false;
1388 return NS_OK;
1390 mCacheId = maybeCacheId.ref();
1392 // Don't delete the removing padding size here, we'll delete it on
1393 // DeleteOrphanedCacheAction.
1394 QM_TRY(
1395 MOZ_TO_RESULT(db::StorageForgetCache(*aConn, mNamespace, mArgs.key())));
1397 QM_TRY(MOZ_TO_RESULT(trans.Commit()));
1399 mCacheDeleted = true;
1400 return NS_OK;
1403 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1404 if (mCacheDeleted) {
1405 // If content is referencing this cache, mark it orphaned to be
1406 // deleted later.
1407 if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) {
1408 // no outstanding references, delete immediately
1409 const auto pinnedContext =
1410 SafeRefPtr{mManager->mContext, AcquireStrongRefFromRawPtr{}};
1412 if (pinnedContext->IsCanceled()) {
1413 pinnedContext->NoteOrphanedData();
1414 } else {
1415 pinnedContext->CancelForCacheId(mCacheId);
1416 pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
1417 mManager.clonePtr(), mCacheId));
1422 aListener->OnOpComplete(std::move(aRv), StorageDeleteResult(mCacheDeleted));
1425 private:
1426 const Namespace mNamespace;
1427 const StorageDeleteArgs mArgs;
1428 bool mCacheDeleted;
1429 CacheId mCacheId;
1432 // ----------------------------------------------------------------------------
1434 class Manager::StorageKeysAction final : public Manager::BaseAction {
1435 public:
1436 StorageKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1437 Namespace aNamespace)
1438 : BaseAction(std::move(aManager), aListenerId), mNamespace(aNamespace) {}
1440 virtual nsresult RunSyncWithDBOnTarget(
1441 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
1442 mozIStorageConnection* aConn) override {
1443 QM_TRY_UNWRAP(mKeys, db::StorageGetKeys(*aConn, mNamespace));
1445 return NS_OK;
1448 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1449 if (aRv.Failed()) {
1450 mKeys.Clear();
1452 aListener->OnOpComplete(std::move(aRv), StorageKeysResult(mKeys));
1455 private:
1456 const Namespace mNamespace;
1457 nsTArray<nsString> mKeys;
1460 // ----------------------------------------------------------------------------
1462 class Manager::OpenStreamAction final : public Manager::BaseAction {
1463 public:
1464 OpenStreamAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1465 InputStreamResolver&& aResolver, const nsID& aBodyId)
1466 : BaseAction(std::move(aManager), aListenerId),
1467 mResolver(std::move(aResolver)),
1468 mBodyId(aBodyId) {}
1470 virtual nsresult RunSyncWithDBOnTarget(
1471 const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
1472 mozIStorageConnection* aConn) override {
1473 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1475 QM_TRY_UNWRAP(mBodyStream, BodyOpen(aQuotaInfo, *aDBDir, mBodyId));
1477 return NS_OK;
1480 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1481 if (aRv.Failed()) {
1482 // Ignore the reason for fail and just pass a null input stream to let it
1483 // fail.
1484 aRv.SuppressException();
1485 mResolver(nullptr);
1486 } else {
1487 mResolver(std::move(mBodyStream));
1490 mResolver = nullptr;
1493 private:
1494 InputStreamResolver mResolver;
1495 const nsID mBodyId;
1496 nsCOMPtr<nsIInputStream> mBodyStream;
1499 // ----------------------------------------------------------------------------
1501 // static
1502 Manager::ListenerId Manager::sNextListenerId = 0;
1504 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1505 const CacheOpResult& aResult) {
1506 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID, Nothing());
1509 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1510 const CacheOpResult& aResult,
1511 CacheId aOpenedCacheId) {
1512 OnOpComplete(std::move(aRv), aResult, aOpenedCacheId, Nothing());
1515 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1516 const CacheOpResult& aResult,
1517 const SavedResponse& aSavedResponse,
1518 StreamList& aStreamList) {
1519 AutoTArray<SavedResponse, 1> responseList;
1520 responseList.AppendElement(aSavedResponse);
1521 OnOpComplete(
1522 std::move(aRv), aResult, INVALID_CACHE_ID,
1523 Some(StreamInfo{responseList, nsTArray<SavedRequest>(), aStreamList}));
1526 void Manager::Listener::OnOpComplete(
1527 ErrorResult&& aRv, const CacheOpResult& aResult,
1528 const nsTArray<SavedResponse>& aSavedResponseList,
1529 StreamList& aStreamList) {
1530 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
1531 Some(StreamInfo{aSavedResponseList, nsTArray<SavedRequest>(),
1532 aStreamList}));
1535 void Manager::Listener::OnOpComplete(
1536 ErrorResult&& aRv, const CacheOpResult& aResult,
1537 const nsTArray<SavedRequest>& aSavedRequestList, StreamList& aStreamList) {
1538 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
1539 Some(StreamInfo{nsTArray<SavedResponse>(), aSavedRequestList,
1540 aStreamList}));
1543 // static
1544 Result<SafeRefPtr<Manager>, nsresult> Manager::AcquireCreateIfNonExistent(
1545 const SafeRefPtr<ManagerId>& aManagerId) {
1546 mozilla::ipc::AssertIsOnBackgroundThread();
1547 return Factory::AcquireCreateIfNonExistent(aManagerId);
1550 // static
1551 void Manager::InitiateShutdown() {
1552 mozilla::ipc::AssertIsOnBackgroundThread();
1554 Factory::ShutdownAll();
1557 // static
1558 bool Manager::IsShutdownAllComplete() {
1559 mozilla::ipc::AssertIsOnBackgroundThread();
1561 return Factory::IsShutdownAllComplete();
1564 // static
1565 nsCString Manager::GetShutdownStatus() {
1566 mozilla::ipc::AssertIsOnBackgroundThread();
1568 return Factory::GetShutdownStatus();
1571 // static
1572 void Manager::Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
1573 mozilla::ipc::AssertIsOnBackgroundThread();
1575 Factory::Abort(aDirectoryLockIds);
1578 // static
1579 void Manager::AbortAll() {
1580 mozilla::ipc::AssertIsOnBackgroundThread();
1582 Factory::AbortAll();
1585 void Manager::RemoveListener(Listener* aListener) {
1586 NS_ASSERT_OWNINGTHREAD(Manager);
1587 // There may not be a listener here in the case where an actor is killed
1588 // before it can perform any actual async requests on Manager.
1589 mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
1590 MOZ_ASSERT(
1591 !mListeners.Contains(aListener, ListenerEntryListenerComparator()));
1592 MaybeAllowContextToClose();
1595 void Manager::RemoveContext(Context& aContext) {
1596 NS_ASSERT_OWNINGTHREAD(Manager);
1597 MOZ_DIAGNOSTIC_ASSERT(mContext);
1598 MOZ_DIAGNOSTIC_ASSERT(mContext == &aContext);
1600 // Whether the Context destruction was triggered from the Manager going
1601 // idle or the underlying storage being invalidated, we should know we
1602 // are closing before the Context is destroyed.
1603 MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
1605 // Before forgetting the Context, check to see if we have any outstanding
1606 // cache or body objects waiting for deletion. If so, note that we've
1607 // orphaned data so it will be cleaned up on the next open.
1608 if (std::any_of(
1609 mCacheIdRefs.cbegin(), mCacheIdRefs.cend(),
1610 [](const auto& cacheIdRef) { return cacheIdRef.mOrphaned; }) ||
1611 std::any_of(mBodyIdRefs.cbegin(), mBodyIdRefs.cend(),
1612 [](const auto& bodyIdRef) { return bodyIdRef.mOrphaned; })) {
1613 aContext.NoteOrphanedData();
1616 mContext = nullptr;
1618 // Once the context is gone, we can immediately remove ourself from the
1619 // Factory list. We don't need to block shutdown by staying in the list
1620 // any more.
1621 Factory::Remove(*this);
1624 void Manager::NoteClosing() {
1625 NS_ASSERT_OWNINGTHREAD(Manager);
1626 // This can be called more than once legitimately through different paths.
1627 mState = Closing;
1630 Manager::State Manager::GetState() const {
1631 NS_ASSERT_OWNINGTHREAD(Manager);
1632 return mState;
1635 void Manager::AddRefCacheId(CacheId aCacheId) {
1636 NS_ASSERT_OWNINGTHREAD(Manager);
1638 const auto end = mCacheIdRefs.end();
1639 const auto foundIt =
1640 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
1641 if (foundIt != end) {
1642 foundIt->mCount += 1;
1643 return;
1646 mCacheIdRefs.AppendElement(CacheIdRefCounter{aCacheId, 1, false});
1649 void Manager::ReleaseCacheId(CacheId aCacheId) {
1650 NS_ASSERT_OWNINGTHREAD(Manager);
1652 const auto end = mCacheIdRefs.end();
1653 const auto foundIt =
1654 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
1655 if (foundIt != end) {
1656 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1657 const uint32_t oldRef = foundIt->mCount;
1658 #endif
1659 foundIt->mCount -= 1;
1660 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
1661 if (foundIt->mCount == 0) {
1662 const bool orphaned = foundIt->mOrphaned;
1663 mCacheIdRefs.RemoveElementAt(foundIt);
1664 const auto pinnedContext =
1665 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1666 // If the context is already gone, then orphan flag should have been
1667 // set in RemoveContext().
1668 if (orphaned && pinnedContext) {
1669 if (pinnedContext->IsCanceled()) {
1670 pinnedContext->NoteOrphanedData();
1671 } else {
1672 pinnedContext->CancelForCacheId(aCacheId);
1673 pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
1674 SafeRefPtrFromThis(), aCacheId));
1678 MaybeAllowContextToClose();
1679 return;
1682 MOZ_ASSERT_UNREACHABLE("Attempt to release CacheId that is not referenced!");
1685 void Manager::AddRefBodyId(const nsID& aBodyId) {
1686 NS_ASSERT_OWNINGTHREAD(Manager);
1688 const auto end = mBodyIdRefs.end();
1689 const auto foundIt =
1690 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
1691 if (foundIt != end) {
1692 foundIt->mCount += 1;
1693 return;
1696 mBodyIdRefs.AppendElement(BodyIdRefCounter{aBodyId, 1, false});
1699 void Manager::ReleaseBodyId(const nsID& aBodyId) {
1700 NS_ASSERT_OWNINGTHREAD(Manager);
1702 const auto end = mBodyIdRefs.end();
1703 const auto foundIt =
1704 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
1705 if (foundIt != end) {
1706 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1707 const uint32_t oldRef = foundIt->mCount;
1708 #endif
1709 foundIt->mCount -= 1;
1710 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
1711 if (foundIt->mCount < 1) {
1712 const bool orphaned = foundIt->mOrphaned;
1713 mBodyIdRefs.RemoveElementAt(foundIt);
1714 const auto pinnedContext =
1715 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1716 // If the context is already gone, then orphan flag should have been
1717 // set in RemoveContext().
1718 if (orphaned && pinnedContext) {
1719 if (pinnedContext->IsCanceled()) {
1720 pinnedContext->NoteOrphanedData();
1721 } else {
1722 pinnedContext->Dispatch(
1723 MakeSafeRefPtr<DeleteOrphanedBodyAction>(aBodyId));
1727 MaybeAllowContextToClose();
1728 return;
1731 MOZ_ASSERT_UNREACHABLE("Attempt to release BodyId that is not referenced!");
1734 const ManagerId& Manager::GetManagerId() const { return *mManagerId; }
1736 void Manager::AddStreamList(StreamList& aStreamList) {
1737 NS_ASSERT_OWNINGTHREAD(Manager);
1738 mStreamLists.AppendElement(WrapNotNullUnchecked(&aStreamList));
1741 void Manager::RemoveStreamList(StreamList& aStreamList) {
1742 NS_ASSERT_OWNINGTHREAD(Manager);
1743 mStreamLists.RemoveElement(&aStreamList);
1746 void Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId,
1747 const CacheOpArgs& aOpArgs) {
1748 NS_ASSERT_OWNINGTHREAD(Manager);
1749 MOZ_DIAGNOSTIC_ASSERT(aListener);
1750 MOZ_DIAGNOSTIC_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs);
1752 if (NS_WARN_IF(mState == Closing)) {
1753 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
1754 return;
1757 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1758 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1760 auto action = [this, aListener, aCacheId, &aOpArgs,
1761 &pinnedContext]() -> SafeRefPtr<Action> {
1762 const ListenerId listenerId = SaveListener(aListener);
1764 if (CacheOpArgs::TCacheDeleteArgs == aOpArgs.type()) {
1765 return MakeSafeRefPtr<CacheDeleteAction>(SafeRefPtrFromThis(), listenerId,
1766 aCacheId,
1767 aOpArgs.get_CacheDeleteArgs());
1770 auto streamList = MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
1771 pinnedContext.clonePtr());
1773 switch (aOpArgs.type()) {
1774 case CacheOpArgs::TCacheMatchArgs:
1775 return MakeSafeRefPtr<CacheMatchAction>(
1776 SafeRefPtrFromThis(), listenerId, aCacheId,
1777 aOpArgs.get_CacheMatchArgs(), std::move(streamList));
1778 case CacheOpArgs::TCacheMatchAllArgs:
1779 return MakeSafeRefPtr<CacheMatchAllAction>(
1780 SafeRefPtrFromThis(), listenerId, aCacheId,
1781 aOpArgs.get_CacheMatchAllArgs(), std::move(streamList));
1782 case CacheOpArgs::TCacheKeysArgs:
1783 return MakeSafeRefPtr<CacheKeysAction>(
1784 SafeRefPtrFromThis(), listenerId, aCacheId,
1785 aOpArgs.get_CacheKeysArgs(), std::move(streamList));
1786 default:
1787 MOZ_CRASH("Unknown Cache operation!");
1789 }();
1791 pinnedContext->Dispatch(std::move(action));
1794 void Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace,
1795 const CacheOpArgs& aOpArgs) {
1796 NS_ASSERT_OWNINGTHREAD(Manager);
1797 MOZ_DIAGNOSTIC_ASSERT(aListener);
1799 if (NS_WARN_IF(mState == Closing)) {
1800 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
1801 return;
1804 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1805 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1807 auto action = [this, aListener, aNamespace, &aOpArgs,
1808 &pinnedContext]() -> SafeRefPtr<Action> {
1809 const ListenerId listenerId = SaveListener(aListener);
1811 switch (aOpArgs.type()) {
1812 case CacheOpArgs::TStorageMatchArgs:
1813 return MakeSafeRefPtr<StorageMatchAction>(
1814 SafeRefPtrFromThis(), listenerId, aNamespace,
1815 aOpArgs.get_StorageMatchArgs(),
1816 MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
1817 pinnedContext.clonePtr()));
1818 case CacheOpArgs::TStorageHasArgs:
1819 return MakeSafeRefPtr<StorageHasAction>(SafeRefPtrFromThis(),
1820 listenerId, aNamespace,
1821 aOpArgs.get_StorageHasArgs());
1822 case CacheOpArgs::TStorageOpenArgs:
1823 return MakeSafeRefPtr<StorageOpenAction>(SafeRefPtrFromThis(),
1824 listenerId, aNamespace,
1825 aOpArgs.get_StorageOpenArgs());
1826 case CacheOpArgs::TStorageDeleteArgs:
1827 return MakeSafeRefPtr<StorageDeleteAction>(
1828 SafeRefPtrFromThis(), listenerId, aNamespace,
1829 aOpArgs.get_StorageDeleteArgs());
1830 case CacheOpArgs::TStorageKeysArgs:
1831 return MakeSafeRefPtr<StorageKeysAction>(SafeRefPtrFromThis(),
1832 listenerId, aNamespace);
1833 default:
1834 MOZ_CRASH("Unknown CacheStorage operation!");
1836 }();
1838 pinnedContext->Dispatch(std::move(action));
1841 void Manager::ExecuteOpenStream(Listener* aListener,
1842 InputStreamResolver&& aResolver,
1843 const nsID& aBodyId) {
1844 NS_ASSERT_OWNINGTHREAD(Manager);
1845 MOZ_DIAGNOSTIC_ASSERT(aListener);
1846 MOZ_DIAGNOSTIC_ASSERT(aResolver);
1848 if (NS_WARN_IF(mState == Closing)) {
1849 aResolver(nullptr);
1850 return;
1853 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1854 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1856 // We save the listener simply to track the existence of the caller here.
1857 // Our returned value will really be passed to the resolver when the
1858 // operation completes. In the future we should remove the Listener
1859 // mechanism in favor of std::function or MozPromise.
1860 ListenerId listenerId = SaveListener(aListener);
1862 pinnedContext->Dispatch(MakeSafeRefPtr<OpenStreamAction>(
1863 SafeRefPtrFromThis(), listenerId, std::move(aResolver), aBodyId));
1866 void Manager::ExecutePutAll(
1867 Listener* aListener, CacheId aCacheId,
1868 const nsTArray<CacheRequestResponse>& aPutList,
1869 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
1870 const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList) {
1871 NS_ASSERT_OWNINGTHREAD(Manager);
1872 MOZ_DIAGNOSTIC_ASSERT(aListener);
1874 if (NS_WARN_IF(mState == Closing)) {
1875 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult());
1876 return;
1879 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1880 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1882 ListenerId listenerId = SaveListener(aListener);
1883 pinnedContext->Dispatch(MakeSafeRefPtr<CachePutAllAction>(
1884 SafeRefPtrFromThis(), listenerId, aCacheId, aPutList, aRequestStreamList,
1885 aResponseStreamList));
1888 Manager::Manager(SafeRefPtr<ManagerId> aManagerId, nsIThread* aIOThread,
1889 const ConstructorGuard&)
1890 : mManagerId(std::move(aManagerId)),
1891 mIOThread(aIOThread),
1892 mContext(nullptr),
1893 mShuttingDown(false),
1894 mState(Open) {
1895 MOZ_DIAGNOSTIC_ASSERT(mManagerId);
1896 MOZ_DIAGNOSTIC_ASSERT(mIOThread);
1899 Manager::~Manager() {
1900 NS_ASSERT_OWNINGTHREAD(Manager);
1901 MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
1902 MOZ_DIAGNOSTIC_ASSERT(!mContext);
1904 nsCOMPtr<nsIThread> ioThread;
1905 mIOThread.swap(ioThread);
1907 // Don't spin the event loop in the destructor waiting for the thread to
1908 // shutdown. Defer this to the main thread, instead.
1909 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(
1910 "nsIThread::AsyncShutdown", ioThread, &nsIThread::AsyncShutdown)));
1913 void Manager::Init(Maybe<Manager&> aOldManager) {
1914 NS_ASSERT_OWNINGTHREAD(Manager);
1916 // Create the context immediately. Since there can at most be one Context
1917 // per Manager now, this lets us cleanly call Factory::Remove() once the
1918 // Context goes away.
1919 SafeRefPtr<Context> ref = Context::Create(
1920 SafeRefPtrFromThis(), mIOThread->SerialEventTarget(),
1921 MakeSafeRefPtr<SetupAction>(),
1922 aOldManager ? SomeRef(*aOldManager->mContext) : Nothing());
1923 mContext = ref.unsafeGetRawPtr();
1926 void Manager::Shutdown() {
1927 NS_ASSERT_OWNINGTHREAD(Manager);
1929 // Ignore duplicate attempts to shutdown. This can occur when we start
1930 // a browser initiated shutdown and then run ~Manager() which also
1931 // calls Shutdown().
1932 if (mShuttingDown) {
1933 return;
1936 mShuttingDown = true;
1938 // Note that we are closing to prevent any new requests from coming in and
1939 // creating a new Context. We must ensure all Contexts and IO operations are
1940 // complete before shutdown proceeds.
1941 NoteClosing();
1943 // If there is a context, then cancel and only note that we are done after
1944 // its cleaned up.
1945 if (mContext) {
1946 const auto pinnedContext =
1947 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1948 pinnedContext->CancelAll();
1949 return;
1953 Maybe<DirectoryLock&> Manager::MaybeDirectoryLockRef() const {
1954 NS_ASSERT_OWNINGTHREAD(Manager);
1955 MOZ_DIAGNOSTIC_ASSERT(mContext);
1957 return mContext->MaybeDirectoryLockRef();
1960 void Manager::Abort() {
1961 NS_ASSERT_OWNINGTHREAD(Manager);
1962 MOZ_DIAGNOSTIC_ASSERT(mContext);
1964 // Note that we are closing to prevent any new requests from coming in and
1965 // creating a new Context. We must ensure all Contexts and IO operations are
1966 // complete before origin clear proceeds.
1967 NoteClosing();
1969 // Cancel and only note that we are done after the context is cleaned up.
1970 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1971 pinnedContext->CancelAll();
1974 Manager::ListenerId Manager::SaveListener(Listener* aListener) {
1975 NS_ASSERT_OWNINGTHREAD(Manager);
1977 // Once a Listener is added, we keep a reference to it until its
1978 // removed. Since the same Listener might make multiple requests,
1979 // ensure we only have a single reference in our list.
1980 ListenerList::index_type index =
1981 mListeners.IndexOf(aListener, 0, ListenerEntryListenerComparator());
1982 if (index != ListenerList::NoIndex) {
1983 return mListeners[index].mId;
1986 ListenerId id = sNextListenerId;
1987 sNextListenerId += 1;
1989 mListeners.AppendElement(ListenerEntry(id, aListener));
1990 return id;
1993 Manager::Listener* Manager::GetListener(ListenerId aListenerId) const {
1994 NS_ASSERT_OWNINGTHREAD(Manager);
1995 ListenerList::index_type index =
1996 mListeners.IndexOf(aListenerId, 0, ListenerEntryIdComparator());
1997 if (index != ListenerList::NoIndex) {
1998 return mListeners[index].mListener;
2001 // This can legitimately happen if the actor is deleted while a request is
2002 // in process. For example, the child process OOMs.
2003 return nullptr;
2006 bool Manager::SetCacheIdOrphanedIfRefed(CacheId aCacheId) {
2007 NS_ASSERT_OWNINGTHREAD(Manager);
2009 const auto end = mCacheIdRefs.end();
2010 const auto foundIt =
2011 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
2012 if (foundIt != end) {
2013 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
2014 MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
2015 foundIt->mOrphaned = true;
2016 return true;
2019 return false;
2022 // TODO: provide way to set body non-orphaned if its added back to a cache (bug
2023 // 1110479)
2025 bool Manager::SetBodyIdOrphanedIfRefed(const nsID& aBodyId) {
2026 NS_ASSERT_OWNINGTHREAD(Manager);
2028 const auto end = mBodyIdRefs.end();
2029 const auto foundIt =
2030 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
2031 if (foundIt != end) {
2032 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
2033 MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
2034 foundIt->mOrphaned = true;
2035 return true;
2038 return false;
2041 void Manager::NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList) {
2042 NS_ASSERT_OWNINGTHREAD(Manager);
2044 // XXX TransformIfIntoNewArray might be generalized to allow specifying the
2045 // type of nsTArray to create, so that it can create an AutoTArray as well; an
2046 // TransformIf (without AbortOnErr) might be added, which could be used here.
2047 DeleteOrphanedBodyAction::DeletedBodyIdList deleteNowList;
2048 deleteNowList.SetCapacity(aDeletedBodyIdList.Length());
2050 std::copy_if(aDeletedBodyIdList.cbegin(), aDeletedBodyIdList.cend(),
2051 MakeBackInserter(deleteNowList),
2052 [this](const auto& deletedBodyId) {
2053 return !SetBodyIdOrphanedIfRefed(deletedBodyId);
2056 // TODO: note that we need to check these bodies for staleness on startup (bug
2057 // 1110446)
2058 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2059 if (!deleteNowList.IsEmpty() && pinnedContext &&
2060 !pinnedContext->IsCanceled()) {
2061 pinnedContext->Dispatch(
2062 MakeSafeRefPtr<DeleteOrphanedBodyAction>(std::move(deleteNowList)));
2066 void Manager::MaybeAllowContextToClose() {
2067 NS_ASSERT_OWNINGTHREAD(Manager);
2069 // If we have an active context, but we have no more users of the Manager,
2070 // then let it shut itself down. We must wait for all possible users of
2071 // Cache state information to complete before doing this. Once we allow
2072 // the Context to close we may not reliably get notified of storage
2073 // invalidation.
2074 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2075 if (pinnedContext && mListeners.IsEmpty() && mCacheIdRefs.IsEmpty() &&
2076 mBodyIdRefs.IsEmpty()) {
2077 // Mark this Manager as invalid so that it won't get used again. We don't
2078 // want to start any new operations once we allow the Context to close since
2079 // it may race with the underlying storage getting invalidated.
2080 NoteClosing();
2082 pinnedContext->AllowToClose();
2086 } // namespace mozilla::dom::cache