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"
34 #include "nsIThread.h"
35 #include "nsIUUIDGenerator.h"
36 #include "nsThreadUtils.h"
37 #include "nsTObserverArray.h"
38 #include "QuotaClientImpl.h"
41 namespace mozilla::dom::cache
{
43 using mozilla::dom::quota::CloneFileAndAppend
;
44 using mozilla::dom::quota::DirectoryLock
;
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
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
)));
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
;
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
{
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
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
));
130 const CheckedInt64
& overallDeletedPaddingSize
,
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
;
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
,
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
)));
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
{
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
,
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
),
214 QM_TRY(MOZ_TO_RESULT(BodyDeleteFiles(*aDirectoryMetadata
, *dbDir
,
215 mDeletedBodyIdList
)),
218 aResolver
->Resolve(NS_OK
);
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");
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
; };
250 // ----------------------------------------------------------------------------
252 // Singleton class to track Manager instances and ensure there is only
253 // one for each unique ManagerId.
254 class Manager::Factory
{
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
) {
268 "Attempt to AcquireCreateIfNonExistent a Manager during QM "
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
);
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
,
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()));
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();
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()) {
356 SafeRefPtr
{manager
.get(), AcquireStrongRefFromRawPtr
{}};
357 pinnedManager
->Shutdown();
361 MaybeDestroyInstance();
364 static bool IsShutdownAllComplete() {
365 mozilla::ipc::AssertIsOnBackgroundThread();
369 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
370 static void RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId
) {
372 sFactory
->mPotentiallyUnreleasedCSCP
.AppendElement(
373 aCacheStreamControlParentId
);
377 static void RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId
) {
379 sFactory
->mPotentiallyUnreleasedCSCP
.RemoveElement(
380 aCacheStreamControlParentId
);
384 static nsCString
GetShutdownStatus() {
385 mozilla::ipc::AssertIsOnBackgroundThread();
389 if (sFactory
&& !sFactory
->mManagerList
.IsEmpty()) {
392 IntToCString(static_cast<uint64_t>(sFactory
->mManagerList
.Length())) +
395 for (const auto& manager
: sFactory
->mManagerList
.NonObservingRange()) {
396 manager
->Stringify(data
);
399 data
.Append(kStringifyEndSet
);
400 if (sFactory
->mPotentiallyUnreleasedCSCP
.Length() > 0) {
402 "There have been CSCP instances whose"
403 "Send__delete__ might not have freed them.");
411 Factory() : mInSyncAbortOrShutdown(false) {
412 MOZ_COUNT_CTOR(cache::Manager::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();
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
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
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
) {
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
{}}
475 template <typename Condition
>
476 static void AbortMatching(const Condition
& aCondition
) {
477 mozilla::ipc::AssertIsOnBackgroundThread();
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
)) {
495 SafeRefPtr
{manager
.get(), AcquireStrongRefFromRawPtr
{}};
496 pinnedManager
->Abort();
501 MaybeDestroyInstance();
504 // Singleton created on demand and deleted when last Manager is cleared
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
;
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
530 class Manager::BaseAction
: public SyncDBAction
{
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
);
543 Complete(listener
, ErrorResult(aRv
));
546 // ensure we release the manager on the initiating thread
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
{
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(); })));
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
604 SafeRefPtr
<Manager
> mManager
;
605 const CacheId mCacheId
;
606 DeletionInfo mDeletionInfo
;
607 Maybe
<CacheDirectoryMetadata
> mDirectoryMetadata
;
610 // ----------------------------------------------------------------------------
612 class Manager::CacheMatchAction final
: public Manager::BaseAction
{
614 CacheMatchAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
615 CacheId aCacheId
, const CacheMatchArgs
& aArgs
,
616 SafeRefPtr
<StreamList
> aStreamList
)
617 : BaseAction(std::move(aManager
), aListenerId
),
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
);
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;
643 const auto& bodyId
= mResponse
.mBodyId
;
645 nsCOMPtr
<nsIInputStream
> stream
;
646 if (mArgs
.openMode() == OpenMode::Eager
) {
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,
656 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM
)) {
660 return NS_ERROR_ABORT
;
663 mStreamList
->Add(mResponse
.mBodyId
, std::move(stream
));
668 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
669 if (!mFoundResponse
) {
670 aListener
->OnOpComplete(std::move(aRv
), CacheMatchResult(Nothing()));
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
;
684 const CacheId mCacheId
;
685 const CacheMatchArgs mArgs
;
686 SafeRefPtr
<StreamList
> mStreamList
;
688 SavedResponse mResponse
;
691 // ----------------------------------------------------------------------------
693 class Manager::CacheMatchAllAction final
: public Manager::BaseAction
{
695 CacheMatchAllAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
696 CacheId aCacheId
, const CacheMatchAllArgs
& aArgs
,
697 SafeRefPtr
<StreamList
> aStreamList
)
698 : BaseAction(std::move(aManager
), aListenerId
),
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(),
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;
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,
732 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM
)) {
736 return NS_ERROR_ABORT
;
739 mStreamList
->Add(mSavedResponses
[i
].mBodyId
, std::move(stream
));
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
;
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
{
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
),
779 mList(aPutList
.Length()),
780 mExpectedAsyncCopyCompletions(1),
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
];
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
819 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions
== 1);
821 mResolver
= std::move(aResolver
);
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
)));
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
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.
865 // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
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
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();
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) {
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
);
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
;
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
;
941 e
.mResponse
.bodyDiskSize() = 0;
946 db::CachePut(*mConn
, mCacheId
, e
.mRequest
,
947 e
.mRequestStream
? &e
.mRequestBodyId
: nullptr,
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
>=
956 mDeletedPaddingSize
+= deletedPaddingSize
;
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(); })));
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
);
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
;
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
);
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
;
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"));
1051 QM_TRY(MOZ_TO_RESULT(idGen
->GenerateUUIDInPlace(&bodyId
)));
1053 Maybe
<CipherKey
> maybeKey
=
1054 GetOrCreateCipherKey(WrapNotNull(mManager
->mContext
), bodyId
,
1055 /* aCreate */ true);
1058 const auto& copyContext
,
1059 BodyStartWriteStream(aDirectoryMetadata
, *mDBDir
, bodyId
, maybeKey
,
1060 *source
, this, AsyncCopyCompleteFunc
));
1062 if (aStreamId
== RequestStream
) {
1063 aEntry
.mRequestBodyId
= bodyId
;
1065 aEntry
.mResponseBodyId
= bodyId
;
1068 mBodyIdWrittenList
.AppendElement(bodyId
);
1071 MutexAutoLock
lock(mMutex
);
1072 mCopyContextList
.AppendElement(copyContext
);
1075 *aCopyCountOut
+= 1;
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.
1116 MutexAutoLock
lock(mMutex
);
1117 MOZ_ASSERT(mCopyContextList
.IsEmpty());
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.
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
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
{
1181 CacheDeleteAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1182 CacheId aCacheId
, const CacheDeleteArgs
& aArgs
)
1183 : BaseAction(std::move(aManager
), aListenerId
),
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()));
1199 auto maybeDeletionInfo
,
1200 db::CacheDelete(*aConn
, mCacheId
, mArgs
.request(), mArgs
.params()));
1202 mSuccess
= maybeDeletionInfo
.isSome();
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; });
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.
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
;
1239 const CacheId mCacheId
;
1240 const CacheDeleteArgs mArgs
;
1242 DeletionInfo mDeletionInfo
;
1243 Maybe
<CacheDirectoryMetadata
> mDirectoryMetadata
;
1246 // ----------------------------------------------------------------------------
1248 class Manager::CacheKeysAction final
: public Manager::BaseAction
{
1250 CacheKeysAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1251 CacheId aCacheId
, const CacheKeysArgs
& aArgs
,
1252 SafeRefPtr
<StreamList
> aStreamList
)
1253 : BaseAction(std::move(aManager
), aListenerId
),
1256 mStreamList(std::move(aStreamList
)) {}
1258 virtual nsresult
RunSyncWithDBOnTarget(
1259 const CacheDirectoryMetadata
& aDirectoryMetadata
, nsIFile
* aDBDir
,
1260 mozIStorageConnection
* aConn
) override
{
1261 MOZ_DIAGNOSTIC_ASSERT(aDBDir
);
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;
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,
1287 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM
)) {
1291 return NS_ERROR_ABORT
;
1294 mStreamList
->Add(mSavedRequests
[i
].mBodyId
, std::move(stream
));
1300 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1301 mStreamList
->Activate(mCacheId
);
1302 aListener
->OnOpComplete(std::move(aRv
), CacheKeysResult(), mSavedRequests
,
1304 mStreamList
= nullptr;
1307 virtual bool MatchesCacheId(CacheId aCacheId
) const override
{
1308 return aCacheId
== mCacheId
;
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
{
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
),
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;
1353 const auto& bodyId
= mSavedResponse
.mBodyId
;
1355 nsCOMPtr
<nsIInputStream
> stream
;
1356 if (mArgs
.openMode() == OpenMode::Eager
) {
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,
1366 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownQM
)) {
1370 return NS_ERROR_ABORT
;
1373 mStreamList
->Add(mSavedResponse
.mBodyId
, std::move(stream
));
1378 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1379 if (!mFoundResponse
) {
1380 aListener
->OnOpComplete(std::move(aRv
), StorageMatchResult(Nothing()));
1382 mStreamList
->Activate(mSavedResponse
.mCacheId
);
1383 aListener
->OnOpComplete(std::move(aRv
), StorageMatchResult(Nothing()),
1384 mSavedResponse
, *mStreamList
);
1386 mStreamList
= nullptr;
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
{
1401 StorageHasAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1402 Namespace aNamespace
, const StorageHasArgs
& aArgs
)
1403 : BaseAction(std::move(aManager
), aListenerId
),
1404 mNamespace(aNamespace
),
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();
1419 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1420 aListener
->OnOpComplete(std::move(aRv
), StorageHasResult(mCacheFound
));
1424 const Namespace mNamespace
;
1425 const StorageHasArgs mArgs
;
1429 // ----------------------------------------------------------------------------
1431 class Manager::StorageOpenAction final
: public Manager::BaseAction
{
1433 StorageOpenAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1434 Namespace aNamespace
, const StorageOpenArgs
& aArgs
)
1435 : BaseAction(std::move(aManager
), aListenerId
),
1436 mNamespace(aNamespace
),
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
);
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
);
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
),
1478 const Namespace mNamespace
;
1479 const StorageOpenArgs mArgs
;
1483 // ----------------------------------------------------------------------------
1485 class Manager::StorageDeleteAction final
: public Manager::BaseAction
{
1487 StorageDeleteAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1488 Namespace aNamespace
, const StorageDeleteArgs
& aArgs
)
1489 : BaseAction(std::move(aManager
), aListenerId
),
1490 mNamespace(aNamespace
),
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;
1510 mCacheId
= maybeCacheId
.ref();
1512 // Don't delete the removing padding size here, we'll delete it on
1513 // DeleteOrphanedCacheAction.
1515 MOZ_TO_RESULT(db::StorageForgetCache(*aConn
, mNamespace
, mArgs
.key())));
1517 QM_TRY(MOZ_TO_RESULT(trans
.Commit()));
1519 mCacheDeleted
= true;
1523 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1524 if (mCacheDeleted
) {
1525 // If content is referencing this cache, mark it orphaned to be
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();
1535 pinnedContext
->CancelForCacheId(mCacheId
);
1536 pinnedContext
->Dispatch(MakeSafeRefPtr
<DeleteOrphanedCacheAction
>(
1537 mManager
.clonePtr(), mCacheId
));
1542 aListener
->OnOpComplete(std::move(aRv
), StorageDeleteResult(mCacheDeleted
));
1546 const Namespace mNamespace
;
1547 const StorageDeleteArgs mArgs
;
1552 // ----------------------------------------------------------------------------
1554 class Manager::StorageKeysAction final
: public Manager::BaseAction
{
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
));
1568 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1572 aListener
->OnOpComplete(std::move(aRv
), StorageKeysResult(mKeys
));
1576 const Namespace mNamespace
;
1577 nsTArray
<nsString
> mKeys
;
1580 // ----------------------------------------------------------------------------
1582 class Manager::OpenStreamAction final
: public Manager::BaseAction
{
1584 OpenStreamAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1585 InputStreamResolver
&& aResolver
, const nsID
& aBodyId
)
1586 : BaseAction(std::move(aManager
), aListenerId
),
1587 mResolver(std::move(aResolver
)),
1590 virtual nsresult
RunSyncWithDBOnTarget(
1591 const CacheDirectoryMetadata
& aDirectoryMetadata
, nsIFile
* aDBDir
,
1592 mozIStorageConnection
* aConn
) override
{
1593 MOZ_DIAGNOSTIC_ASSERT(aDBDir
);
1597 BodyOpen(aDirectoryMetadata
, *aDBDir
, mBodyId
,
1598 GetOrCreateCipherKey(WrapNotNull(mManager
->mContext
), mBodyId
,
1599 /* aCreate */ false)));
1604 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1606 // Ignore the reason for fail and just pass a null input stream to let it
1608 aRv
.SuppressException();
1611 mResolver(std::move(mBodyStream
));
1614 mResolver
= nullptr;
1618 InputStreamResolver mResolver
;
1620 nsCOMPtr
<nsIInputStream
> mBodyStream
;
1623 // ----------------------------------------------------------------------------
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
);
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
>(),
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
,
1668 Result
<SafeRefPtr
<Manager
>, nsresult
> Manager::AcquireCreateIfNonExistent(
1669 const SafeRefPtr
<ManagerId
>& aManagerId
) {
1670 mozilla::ipc::AssertIsOnBackgroundThread();
1671 return Factory::AcquireCreateIfNonExistent(aManagerId
);
1675 void Manager::InitiateShutdown() {
1676 mozilla::ipc::AssertIsOnBackgroundThread();
1678 Factory::AbortAll();
1680 Factory::ShutdownAll();
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
);
1701 nsCString
Manager::GetShutdownStatus() {
1702 mozilla::ipc::AssertIsOnBackgroundThread();
1704 return Factory::GetShutdownStatus();
1708 void Manager::Abort(const Client::DirectoryLockIdTable
& aDirectoryLockIds
) {
1709 mozilla::ipc::AssertIsOnBackgroundThread();
1711 Factory::Abort(aDirectoryLockIds
);
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());
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.
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();
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
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.
1766 Manager::State
Manager::GetState() const {
1767 NS_ASSERT_OWNINGTHREAD(Manager
);
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;
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
;
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();
1808 pinnedContext
->CancelForCacheId(aCacheId
);
1809 pinnedContext
->Dispatch(MakeSafeRefPtr
<DeleteOrphanedCacheAction
>(
1810 SafeRefPtrFromThis(), aCacheId
));
1814 MaybeAllowContextToClose();
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;
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
;
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();
1858 pinnedContext
->Dispatch(
1859 MakeSafeRefPtr
<DeleteOrphanedBodyAction
>(aBodyId
));
1863 MaybeAllowContextToClose();
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());
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
,
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
));
1923 MOZ_CRASH("Unknown Cache operation!");
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());
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
);
1970 MOZ_CRASH("Unknown CacheStorage operation!");
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
)) {
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());
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
),
2029 mShuttingDown(false),
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
) {
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.
2078 // If there is a context, then cancel and only note that we are done after
2081 const auto pinnedContext
=
2082 SafeRefPtr
{mContext
, AcquireStrongRefFromRawPtr
{}};
2083 pinnedContext
->CancelAll();
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.
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
));
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.
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;
2157 // TODO: provide way to set body non-orphaned if its added back to a cache (bug
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;
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
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
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.
2217 pinnedContext
->AllowToClose();
2221 void Manager::DoStringify(nsACString
& aData
) {
2222 aData
.Append("Manager "_ns
+ kStringifyStartInstance
+
2225 quota::AnonymizedOriginString(GetManagerId().QuotaOrigin()) +
2226 kStringifyDelimiter
+
2228 "State:"_ns
+ IntToCString(mState
) + kStringifyDelimiter
);
2230 aData
.AppendLiteral("Context:");
2232 mContext
->Stringify(aData
);
2234 aData
.AppendLiteral("0");
2237 aData
.Append(kStringifyEndInstance
);
2240 } // namespace mozilla::dom::cache