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"
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
;
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
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
)));
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
{
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
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
));
109 const CheckedInt64
& overallDeletedPaddingSize
,
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
;
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
)));
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
{
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
,
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
,
190 MOZ_TO_RESULT(BodyDeleteFiles(aQuotaInfo
, *dbDir
, mDeletedBodyIdList
)),
193 aResolver
->Resolve(NS_OK
);
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");
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
; };
225 // ----------------------------------------------------------------------------
227 // Singleton class to track Manager instances and ensure there is only
228 // one for each unique ManagerId.
229 class Manager::Factory
{
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
) {
243 "Attempt to AcquireCreateIfNonExistent a Manager during QM "
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
);
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
,
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()));
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();
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()) {
331 SafeRefPtr
{manager
.get(), AcquireStrongRefFromRawPtr
{}};
332 pinnedManager
->Shutdown();
336 MaybeDestroyInstance();
339 static bool IsShutdownAllComplete() {
340 mozilla::ipc::AssertIsOnBackgroundThread();
344 static nsCString
GetShutdownStatus() {
345 mozilla::ipc::AssertIsOnBackgroundThread();
349 if (sFactory
&& !sFactory
->mManagerList
.IsEmpty()) {
352 IntToCString(static_cast<uint64_t>(sFactory
->mManagerList
.Length())) +
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
364 data
.AppendLiteral(", ");
367 data
.AppendLiteral(" )");
374 Factory() : mInSyncAbortOrShutdown(false) {
375 MOZ_COUNT_CTOR(cache::Manager::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();
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
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
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
) {
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
{}}
438 template <typename Condition
>
439 static void AbortMatching(const Condition
& aCondition
) {
440 mozilla::ipc::AssertIsOnBackgroundThread();
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
)) {
458 SafeRefPtr
{manager
.get(), AcquireStrongRefFromRawPtr
{}};
459 pinnedManager
->Abort();
464 MaybeDestroyInstance();
467 // Singleton created on demand and deleted when last Manager is cleared
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
;
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
491 class Manager::BaseAction
: public SyncDBAction
{
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
);
504 Complete(listener
, ErrorResult(aRv
));
507 // ensure we release the manager on the initiating thread
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
{
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(); })));
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
565 SafeRefPtr
<Manager
> mManager
;
566 const CacheId mCacheId
;
567 DeletionInfo mDeletionInfo
;
568 Maybe
<QuotaInfo
> mQuotaInfo
;
571 // ----------------------------------------------------------------------------
573 class Manager::CacheMatchAction final
: public Manager::BaseAction
{
575 CacheMatchAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
576 CacheId aCacheId
, const CacheMatchArgs
& aArgs
,
577 SafeRefPtr
<StreamList
> aStreamList
)
578 : BaseAction(std::move(aManager
), aListenerId
),
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
);
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;
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
));
614 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
615 if (!mFoundResponse
) {
616 aListener
->OnOpComplete(std::move(aRv
), CacheMatchResult(Nothing()));
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
;
630 const CacheId mCacheId
;
631 const CacheMatchArgs mArgs
;
632 SafeRefPtr
<StreamList
> mStreamList
;
634 SavedResponse mResponse
;
637 // ----------------------------------------------------------------------------
639 class Manager::CacheMatchAllAction final
: public Manager::BaseAction
{
641 CacheMatchAllAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
642 CacheId aCacheId
, const CacheMatchAllArgs
& aArgs
,
643 SafeRefPtr
<StreamList
> aStreamList
)
644 : BaseAction(std::move(aManager
), aListenerId
),
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(),
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;
665 nsCOMPtr
<nsIInputStream
> stream
;
666 if (mArgs
.openMode() == OpenMode::Eager
) {
668 stream
, BodyOpen(aQuotaInfo
, *aDBDir
, mSavedResponses
[i
].mBodyId
));
671 mStreamList
->Add(mSavedResponses
[i
].mBodyId
, std::move(stream
));
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
;
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
{
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
),
711 mList(aPutList
.Length()),
712 mExpectedAsyncCopyCompletions(1),
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
];
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
750 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions
== 1);
752 mResolver
= std::move(aResolver
);
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
{
764 MOZ_TO_RESULT(StartStreamCopy(aQuotaInfo
, entry
, RequestStream
,
765 &mExpectedAsyncCopyCompletions
)));
768 MOZ_TO_RESULT(StartStreamCopy(aQuotaInfo
, entry
, ResponseStream
,
769 &mExpectedAsyncCopyCompletions
)));
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
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.
796 // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
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
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();
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) {
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
);
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
)));
869 db::CachePut(*mConn
, mCacheId
, e
.mRequest
,
870 e
.mRequestStream
? &e
.mRequestBodyId
: nullptr,
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
>=
879 mDeletedPaddingSize
+= deletedPaddingSize
;
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(); })));
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
);
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
;
935 CacheRequest mRequest
;
936 nsCOMPtr
<nsIInputStream
> mRequestStream
;
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
);
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
;
967 QM_TRY_INSPECT((const auto& [bodyId
, copyContext
]),
968 BodyStartWriteStream(aQuotaInfo
, *mDBDir
, *source
, this,
969 AsyncCopyCompleteFunc
));
971 if (aStreamId
== RequestStream
) {
972 aEntry
.mRequestBodyId
= bodyId
;
974 aEntry
.mResponseBodyId
= bodyId
;
977 mBodyIdWrittenList
.AppendElement(bodyId
);
980 MutexAutoLock
lock(mMutex
);
981 mCopyContextList
.AppendElement(copyContext
);
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.
1025 MutexAutoLock
lock(mMutex
);
1026 MOZ_ASSERT(mCopyContextList
.IsEmpty());
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.
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
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
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
{
1089 CacheDeleteAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1090 CacheId aCacheId
, const CacheDeleteArgs
& aArgs
)
1091 : BaseAction(std::move(aManager
), aListenerId
),
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()));
1107 auto maybeDeletionInfo
,
1108 db::CacheDelete(*aConn
, mCacheId
, mArgs
.request(), mArgs
.params()));
1110 mSuccess
= maybeDeletionInfo
.isSome();
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; });
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.
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
;
1147 const CacheId mCacheId
;
1148 const CacheDeleteArgs mArgs
;
1150 DeletionInfo mDeletionInfo
;
1151 Maybe
<QuotaInfo
> mQuotaInfo
;
1154 // ----------------------------------------------------------------------------
1156 class Manager::CacheKeysAction final
: public Manager::BaseAction
{
1158 CacheKeysAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1159 CacheId aCacheId
, const CacheKeysArgs
& aArgs
,
1160 SafeRefPtr
<StreamList
> aStreamList
)
1161 : BaseAction(std::move(aManager
), aListenerId
),
1164 mStreamList(std::move(aStreamList
)) {}
1166 virtual nsresult
RunSyncWithDBOnTarget(
1167 const QuotaInfo
& aQuotaInfo
, nsIFile
* aDBDir
,
1168 mozIStorageConnection
* aConn
) override
{
1169 MOZ_DIAGNOSTIC_ASSERT(aDBDir
);
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;
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
));
1194 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1195 mStreamList
->Activate(mCacheId
);
1196 aListener
->OnOpComplete(std::move(aRv
), CacheKeysResult(), mSavedRequests
,
1198 mStreamList
= nullptr;
1201 virtual bool MatchesCacheId(CacheId aCacheId
) const override
{
1202 return aCacheId
== mCacheId
;
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
{
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
),
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;
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
));
1258 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1259 if (!mFoundResponse
) {
1260 aListener
->OnOpComplete(std::move(aRv
), StorageMatchResult(Nothing()));
1262 mStreamList
->Activate(mSavedResponse
.mCacheId
);
1263 aListener
->OnOpComplete(std::move(aRv
), StorageMatchResult(Nothing()),
1264 mSavedResponse
, *mStreamList
);
1266 mStreamList
= nullptr;
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
{
1281 StorageHasAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1282 Namespace aNamespace
, const StorageHasArgs
& aArgs
)
1283 : BaseAction(std::move(aManager
), aListenerId
),
1284 mNamespace(aNamespace
),
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();
1299 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1300 aListener
->OnOpComplete(std::move(aRv
), StorageHasResult(mCacheFound
));
1304 const Namespace mNamespace
;
1305 const StorageHasArgs mArgs
;
1309 // ----------------------------------------------------------------------------
1311 class Manager::StorageOpenAction final
: public Manager::BaseAction
{
1313 StorageOpenAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1314 Namespace aNamespace
, const StorageOpenArgs
& aArgs
)
1315 : BaseAction(std::move(aManager
), aListenerId
),
1316 mNamespace(aNamespace
),
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
);
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
);
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
),
1358 const Namespace mNamespace
;
1359 const StorageOpenArgs mArgs
;
1363 // ----------------------------------------------------------------------------
1365 class Manager::StorageDeleteAction final
: public Manager::BaseAction
{
1367 StorageDeleteAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1368 Namespace aNamespace
, const StorageDeleteArgs
& aArgs
)
1369 : BaseAction(std::move(aManager
), aListenerId
),
1370 mNamespace(aNamespace
),
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;
1390 mCacheId
= maybeCacheId
.ref();
1392 // Don't delete the removing padding size here, we'll delete it on
1393 // DeleteOrphanedCacheAction.
1395 MOZ_TO_RESULT(db::StorageForgetCache(*aConn
, mNamespace
, mArgs
.key())));
1397 QM_TRY(MOZ_TO_RESULT(trans
.Commit()));
1399 mCacheDeleted
= true;
1403 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1404 if (mCacheDeleted
) {
1405 // If content is referencing this cache, mark it orphaned to be
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();
1415 pinnedContext
->CancelForCacheId(mCacheId
);
1416 pinnedContext
->Dispatch(MakeSafeRefPtr
<DeleteOrphanedCacheAction
>(
1417 mManager
.clonePtr(), mCacheId
));
1422 aListener
->OnOpComplete(std::move(aRv
), StorageDeleteResult(mCacheDeleted
));
1426 const Namespace mNamespace
;
1427 const StorageDeleteArgs mArgs
;
1432 // ----------------------------------------------------------------------------
1434 class Manager::StorageKeysAction final
: public Manager::BaseAction
{
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
));
1448 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1452 aListener
->OnOpComplete(std::move(aRv
), StorageKeysResult(mKeys
));
1456 const Namespace mNamespace
;
1457 nsTArray
<nsString
> mKeys
;
1460 // ----------------------------------------------------------------------------
1462 class Manager::OpenStreamAction final
: public Manager::BaseAction
{
1464 OpenStreamAction(SafeRefPtr
<Manager
> aManager
, ListenerId aListenerId
,
1465 InputStreamResolver
&& aResolver
, const nsID
& aBodyId
)
1466 : BaseAction(std::move(aManager
), aListenerId
),
1467 mResolver(std::move(aResolver
)),
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
));
1480 virtual void Complete(Listener
* aListener
, ErrorResult
&& aRv
) override
{
1482 // Ignore the reason for fail and just pass a null input stream to let it
1484 aRv
.SuppressException();
1487 mResolver(std::move(mBodyStream
));
1490 mResolver
= nullptr;
1494 InputStreamResolver mResolver
;
1496 nsCOMPtr
<nsIInputStream
> mBodyStream
;
1499 // ----------------------------------------------------------------------------
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
);
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
>(),
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
,
1544 Result
<SafeRefPtr
<Manager
>, nsresult
> Manager::AcquireCreateIfNonExistent(
1545 const SafeRefPtr
<ManagerId
>& aManagerId
) {
1546 mozilla::ipc::AssertIsOnBackgroundThread();
1547 return Factory::AcquireCreateIfNonExistent(aManagerId
);
1551 void Manager::InitiateShutdown() {
1552 mozilla::ipc::AssertIsOnBackgroundThread();
1554 Factory::ShutdownAll();
1558 bool Manager::IsShutdownAllComplete() {
1559 mozilla::ipc::AssertIsOnBackgroundThread();
1561 return Factory::IsShutdownAllComplete();
1565 nsCString
Manager::GetShutdownStatus() {
1566 mozilla::ipc::AssertIsOnBackgroundThread();
1568 return Factory::GetShutdownStatus();
1572 void Manager::Abort(const Client::DirectoryLockIdTable
& aDirectoryLockIds
) {
1573 mozilla::ipc::AssertIsOnBackgroundThread();
1575 Factory::Abort(aDirectoryLockIds
);
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());
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.
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();
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
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.
1630 Manager::State
Manager::GetState() const {
1631 NS_ASSERT_OWNINGTHREAD(Manager
);
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;
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
;
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();
1672 pinnedContext
->CancelForCacheId(aCacheId
);
1673 pinnedContext
->Dispatch(MakeSafeRefPtr
<DeleteOrphanedCacheAction
>(
1674 SafeRefPtrFromThis(), aCacheId
));
1678 MaybeAllowContextToClose();
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;
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
;
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();
1722 pinnedContext
->Dispatch(
1723 MakeSafeRefPtr
<DeleteOrphanedBodyAction
>(aBodyId
));
1727 MaybeAllowContextToClose();
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());
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
,
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
));
1787 MOZ_CRASH("Unknown Cache operation!");
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());
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
);
1834 MOZ_CRASH("Unknown CacheStorage operation!");
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
)) {
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());
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
),
1893 mShuttingDown(false),
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
) {
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.
1943 // If there is a context, then cancel and only note that we are done after
1946 const auto pinnedContext
=
1947 SafeRefPtr
{mContext
, AcquireStrongRefFromRawPtr
{}};
1948 pinnedContext
->CancelAll();
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.
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
));
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.
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;
2022 // TODO: provide way to set body non-orphaned if its added back to a cache (bug
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;
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
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
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.
2082 pinnedContext
->AllowToClose();
2086 } // namespace mozilla::dom::cache