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/. */
6
7 #include "mozilla/dom/cache/Manager.h"
8
9 #include "mozilla/AppShutdown.h"
10 #include "mozilla/AutoRestore.h"
11 #include "mozilla/Mutex.h"
12 #include "mozilla/StaticMutex.h"
13 #include "mozilla/StaticPtr.h"
14 #include "mozilla/Unused.h"
15 #include "mozilla/dom/cache/Context.h"
16 #include "mozilla/dom/cache/DBAction.h"
17 #include "mozilla/dom/cache/DBSchema.h"
18 #include "mozilla/dom/cache/FileUtils.h"
19 #include "mozilla/dom/cache/ManagerId.h"
20 #include "mozilla/dom/cache/CacheTypes.h"
21 #include "mozilla/dom/cache/SavedTypes.h"
22 #include "mozilla/dom/cache/StreamList.h"
23 #include "mozilla/dom/cache/Types.h"
24 #include "mozilla/dom/quota/Client.h"
25 #include "mozilla/dom/quota/ClientImpl.h"
26 #include "mozilla/dom/quota/QuotaManager.h"
27 #include "mozilla/ipc/BackgroundParent.h"
28 #include "mozStorageHelper.h"
29 #include "nsIInputStream.h"
30 #include "nsID.h"
31 #include "nsIFile.h"
32 #include "nsIThread.h"
33 #include "nsThreadUtils.h"
34 #include "nsTObserverArray.h"
35 #include "QuotaClientImpl.h"
36
37 namespace mozilla::dom::cache {
38
39 using mozilla::dom::quota::Client;
40 using mozilla::dom::quota::CloneFileAndAppend;
41 using mozilla::dom::quota::DirectoryLock;
42
43 namespace {
44
45 /**
46 * Note: The aCommitHook argument will be invoked while a lock is held. Callers
47 * should be careful not to pass a hook that might lock on something else and
48 * trigger a deadlock.
49 */
50 template <typename Callable>
MaybeUpdatePaddingFile(nsIFile * aBaseDir,mozIStorageConnection * aConn,const int64_t aIncreaseSize,const int64_t aDecreaseSize,Callable aCommitHook)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);
60
61 RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get();
62 MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient);
63
64 QM_TRY(MOZ_TO_RESULT(cacheQuotaClient->MaybeUpdatePaddingFileInternal(
65 *aBaseDir, *aConn, aIncreaseSize, aDecreaseSize, aCommitHook)));
66
67 return NS_OK;
68 }
69
70 // An Action that is executed when a Context is first created. It ensures that
71 // the directory and database are setup properly. This lets other actions
72 // not worry about these details.
73 class SetupAction final : public SyncDBAction {
74 public:
SetupAction()75 SetupAction() : SyncDBAction(DBAction::Create) {}
76
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)77 virtual nsresult RunSyncWithDBOnTarget(
78 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
79 mozIStorageConnection* aConn) override {
80 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
81
82 QM_TRY(MOZ_TO_RESULT(BodyCreateDir(*aDBDir)));
83
84 // executes in its own transaction
85 QM_TRY(MOZ_TO_RESULT(db::CreateOrMigrateSchema(*aConn)));
86
87 // If the Context marker file exists, then the last session was
88 // not cleanly shutdown. In these cases sqlite will ensure that
89 // the database is valid, but we might still orphan data. Both
90 // Cache objects and body files can be referenced by DOM objects
91 // after they are "removed" from their parent. So we need to
92 // look and see if any of these late access objects have been
93 // orphaned.
94 //
95 // Note, this must be done after any schema version updates to
96 // ensure our DBSchema methods work correctly.
97 if (MarkerFileExists(aDirectoryMetadata)) {
98 NS_WARNING("Cache not shutdown cleanly! Cleaning up stale data...");
99 mozStorageTransaction trans(aConn, false,
100 mozIStorageConnection::TRANSACTION_IMMEDIATE);
101
102 QM_TRY(MOZ_TO_RESULT(trans.Start()));
103
104 // Clean up orphaned Cache objects
105 QM_TRY_INSPECT(const auto& orphanedCacheIdList,
106 db::FindOrphanedCacheIds(*aConn));
107
108 QM_TRY_INSPECT(
109 const CheckedInt64& overallDeletedPaddingSize,
110 Reduce(
111 orphanedCacheIdList, CheckedInt64(0),
112 [aConn, &aDirectoryMetadata, &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));
117
118 QM_TRY(MOZ_TO_RESULT(
119 BodyDeleteFiles(aDirectoryMetadata, *aDBDir,
120 deletionInfo.mDeletedBodyIdList)));
121
122 if (deletionInfo.mDeletedPaddingSize > 0) {
123 DecreaseUsageForDirectoryMetadata(
124 aDirectoryMetadata, deletionInfo.mDeletedPaddingSize);
125 }
126
127 return oldValue + deletionInfo.mDeletedPaddingSize;
128 }));
129
130 // Clean up orphaned body objects
131 QM_TRY_INSPECT(const auto& knownBodyIdList, db::GetKnownBodyIds(*aConn));
132
133 QM_TRY(MOZ_TO_RESULT(BodyDeleteOrphanedFiles(aDirectoryMetadata, *aDBDir,
134 knownBodyIdList)));
135
136 // Commit() explicitly here, because we want to ensure the padding file
137 // has the correct content.
138 // We'll restore padding file below, so just warn here if failure happens.
139 //
140 // XXX Before, if MaybeUpdatePaddingFile failed but we didn't enter the if
141 // body below, we would have propagated the MaybeUpdatePaddingFile
142 // failure, but if we entered it and RestorePaddingFile succeeded, we
143 // would have returned NS_OK. Now, we will never propagate a
144 // MaybeUpdatePaddingFile failure.
145 QM_WARNONLY_TRY(QM_TO_RESULT(
146 MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0,
147 overallDeletedPaddingSize.value(),
148 [&trans]() { return trans.Commit(); })));
149 }
150
151 if (DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::TMP_FILE) ||
152 !DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::FILE)) {
153 QM_TRY(MOZ_TO_RESULT(RestorePaddingFile(aDBDir, aConn)));
154 }
155
156 return NS_OK;
157 }
158 };
159
160 // ----------------------------------------------------------------------------
161
162 // Action that is executed when we determine that content has stopped using
163 // a body file that has been orphaned.
164 class DeleteOrphanedBodyAction final : public Action {
165 public:
166 using DeletedBodyIdList = AutoTArray<nsID, 64>;
167
DeleteOrphanedBodyAction(DeletedBodyIdList && aDeletedBodyIdList)168 explicit DeleteOrphanedBodyAction(DeletedBodyIdList&& aDeletedBodyIdList)
169 : mDeletedBodyIdList(std::move(aDeletedBodyIdList)) {}
170
DeleteOrphanedBodyAction(const nsID & aBodyId)171 explicit DeleteOrphanedBodyAction(const nsID& aBodyId)
172 : mDeletedBodyIdList{aBodyId} {}
173
RunOnTarget(SafeRefPtr<Resolver> aResolver,const Maybe<CacheDirectoryMetadata> & aDirectoryMetadata,Data *)174 void RunOnTarget(SafeRefPtr<Resolver> aResolver,
175 const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
176 Data*) override {
177 MOZ_DIAGNOSTIC_ASSERT(aResolver);
178 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata);
179 MOZ_DIAGNOSTIC_ASSERT(aDirectoryMetadata->mDir);
180
181 // Note that since DeleteOrphanedBodyAction isn't used while the context is
182 // being initialized, we don't need to check for cancellation here.
183
184 const auto resolve = [&aResolver](const nsresult rv) {
185 aResolver->Resolve(rv);
186 };
187
188 QM_TRY_INSPECT(const auto& dbDir,
189 CloneFileAndAppend(*aDirectoryMetadata->mDir, u"cache"_ns),
190 QM_VOID, resolve);
191
192 QM_TRY(MOZ_TO_RESULT(BodyDeleteFiles(*aDirectoryMetadata, *dbDir,
193 mDeletedBodyIdList)),
194 QM_VOID, resolve);
195
196 aResolver->Resolve(NS_OK);
197 }
198
199 private:
200 DeletedBodyIdList mDeletedBodyIdList;
201 };
202
IsHeadRequest(const CacheRequest & aRequest,const CacheQueryParams & aParams)203 bool IsHeadRequest(const CacheRequest& aRequest,
204 const CacheQueryParams& aParams) {
205 return !aParams.ignoreMethod() &&
206 aRequest.method().LowerCaseEqualsLiteral("head");
207 }
208
IsHeadRequest(const Maybe<CacheRequest> & aRequest,const CacheQueryParams & aParams)209 bool IsHeadRequest(const Maybe<CacheRequest>& aRequest,
210 const CacheQueryParams& aParams) {
211 if (aRequest.isSome()) {
212 return !aParams.ignoreMethod() &&
213 aRequest.ref().method().LowerCaseEqualsLiteral("head");
214 }
215 return false;
216 }
217
MatchByCacheId(CacheId aCacheId)218 auto MatchByCacheId(CacheId aCacheId) {
219 return [aCacheId](const auto& entry) { return entry.mCacheId == aCacheId; };
220 }
221
MatchByBodyId(const nsID & aBodyId)222 auto MatchByBodyId(const nsID& aBodyId) {
223 return [&aBodyId](const auto& entry) { return entry.mBodyId == aBodyId; };
224 }
225
226 } // namespace
227
228 // ----------------------------------------------------------------------------
229
230 // Singleton class to track Manager instances and ensure there is only
231 // one for each unique ManagerId.
232 class Manager::Factory {
233 public:
234 friend class StaticAutoPtr<Manager::Factory>;
235
AcquireCreateIfNonExistent(const SafeRefPtr<ManagerId> & aManagerId)236 static Result<SafeRefPtr<Manager>, nsresult> AcquireCreateIfNonExistent(
237 const SafeRefPtr<ManagerId>& aManagerId) {
238 mozilla::ipc::AssertIsOnBackgroundThread();
239
240 // If we get here during/after quota manager shutdown, we bail out.
241 MOZ_ASSERT(AppShutdown::GetCurrentShutdownPhase() <
242 ShutdownPhase::AppShutdownQM);
243 if (AppShutdown::GetCurrentShutdownPhase() >=
244 ShutdownPhase::AppShutdownQM) {
245 NS_WARNING(
246 "Attempt to AcquireCreateIfNonExistent a Manager during QM "
247 "shutdown.");
248 return Err(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
249 }
250
251 // Ensure there is a factory instance. This forces the Acquire() call
252 // below to use the same factory.
253 QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()));
254
255 SafeRefPtr<Manager> ref = Acquire(*aManagerId);
256 if (!ref) {
257 // TODO: replace this with a thread pool (bug 1119864)
258 // XXX Can't use QM_TRY_INSPECT because that causes a clang-plugin
259 // error of the NoNewThreadsChecker.
260 nsCOMPtr<nsIThread> ioThread;
261 QM_TRY(MOZ_TO_RESULT(
262 NS_NewNamedThread("DOMCacheThread", getter_AddRefs(ioThread))));
263
264 ref = MakeSafeRefPtr<Manager>(aManagerId.clonePtr(), ioThread,
265 ConstructorGuard{});
266
267 // There may be an old manager for this origin in the process of
268 // cleaning up. We need to tell the new manager about this so
269 // that it won't actually start until the old manager is done.
270 const SafeRefPtr<Manager> oldManager = Acquire(*aManagerId, Closing);
271 ref->Init(oldManager.maybeDeref());
272
273 MOZ_ASSERT(!sFactory->mManagerList.Contains(ref));
274 sFactory->mManagerList.AppendElement(
275 WrapNotNullUnchecked(ref.unsafeGetRawPtr()));
276 }
277
278 return ref;
279 }
280
Remove(Manager & aManager)281 static void Remove(Manager& aManager) {
282 mozilla::ipc::AssertIsOnBackgroundThread();
283 MOZ_DIAGNOSTIC_ASSERT(sFactory);
284
285 MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(&aManager));
286
287 // This might both happen in late shutdown such that this event
288 // is executed even after the QuotaManager singleton passed away
289 // or if the QuotaManager has not yet been created.
290 quota::QuotaManager::SafeMaybeRecordQuotaClientShutdownStep(
291 quota::Client::DOMCACHE, "Manager removed"_ns);
292
293 // clean up the factory singleton if there are no more managers
294 MaybeDestroyInstance();
295 }
296
Abort(const Client::DirectoryLockIdTable & aDirectoryLockIds)297 static void Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
298 mozilla::ipc::AssertIsOnBackgroundThread();
299
300 AbortMatching([&aDirectoryLockIds](const auto& manager) {
301 // Check if the Manager holds an acquired DirectoryLock. Origin clearing
302 // can't be blocked by this Manager if there is no acquired DirectoryLock.
303 // If there is an acquired DirectoryLock, check if the table contains the
304 // lock for the Manager.
305 return Client::IsLockForObjectAcquiredAndContainedInLockTable(
306 manager, aDirectoryLockIds);
307 });
308 }
309
AbortAll()310 static void AbortAll() {
311 mozilla::ipc::AssertIsOnBackgroundThread();
312
313 AbortMatching([](const auto&) { return true; });
314 }
315
ShutdownAll()316 static void ShutdownAll() {
317 mozilla::ipc::AssertIsOnBackgroundThread();
318
319 if (!sFactory) {
320 return;
321 }
322
323 MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
324
325 {
326 // Note that we are synchronously calling shutdown code here. If any
327 // of the shutdown code synchronously decides to delete the Factory
328 // we need to delay that delete until the end of this method.
329 AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
330 sFactory->mInSyncAbortOrShutdown = true;
331
332 for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
333 auto pinnedManager =
334 SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
335 pinnedManager->Shutdown();
336 }
337 }
338
339 MaybeDestroyInstance();
340 }
341
IsShutdownAllComplete()342 static bool IsShutdownAllComplete() {
343 mozilla::ipc::AssertIsOnBackgroundThread();
344 return !sFactory;
345 }
346
347 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId)348 static void RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId) {
349 if (sFactory) {
350 sFactory->mPotentiallyUnreleasedCSCP.AppendElement(
351 aCacheStreamControlParentId);
352 }
353 }
354
RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId)355 static void RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId) {
356 if (sFactory) {
357 sFactory->mPotentiallyUnreleasedCSCP.RemoveElement(
358 aCacheStreamControlParentId);
359 }
360 }
361 #endif
GetShutdownStatus()362 static nsCString GetShutdownStatus() {
363 mozilla::ipc::AssertIsOnBackgroundThread();
364
365 nsCString data;
366
367 if (sFactory && !sFactory->mManagerList.IsEmpty()) {
368 data.Append(
369 "Managers: "_ns +
370 IntToCString(static_cast<uint64_t>(sFactory->mManagerList.Length())) +
371 " ("_ns);
372
373 for (const auto& manager : sFactory->mManagerList.NonObservingRange()) {
374 data.Append(quota::AnonymizedOriginString(
375 manager->GetManagerId().QuotaOrigin()));
376
377 data.AppendLiteral(": ");
378
379 data.Append(manager->GetState() == State::Open ? "Open"_ns
380 : "Closing"_ns);
381
382 data.AppendLiteral(", ");
383 }
384
385 data.AppendLiteral(" ) ");
386 if (sFactory->mPotentiallyUnreleasedCSCP.Length() > 0) {
387 data.Append(
388 "There have been CSCP instances whose"
389 "Send__delete__ might not have freed them.");
390 }
391 }
392
393 return data;
394 }
395
396 private:
Factory()397 Factory() : mInSyncAbortOrShutdown(false) {
398 MOZ_COUNT_CTOR(cache::Manager::Factory);
399 }
400
~Factory()401 ~Factory() {
402 MOZ_COUNT_DTOR(cache::Manager::Factory);
403 MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
404 MOZ_DIAGNOSTIC_ASSERT(!mInSyncAbortOrShutdown);
405 }
406
MaybeCreateInstance()407 static nsresult MaybeCreateInstance() {
408 mozilla::ipc::AssertIsOnBackgroundThread();
409
410 if (!sFactory) {
411 // We cannot use ClearOnShutdown() here because we're not on the main
412 // thread. Instead, we delete sFactory in Factory::Remove() after the
413 // last manager is removed. ShutdownObserver ensures this happens
414 // before shutdown.
415 sFactory = new Factory();
416 }
417
418 // Never return sFactory to code outside Factory. We need to delete it
419 // out from under ourselves just before we return from Remove(). This
420 // would be (even more) dangerous if other code had a pointer to the
421 // factory itself.
422
423 return NS_OK;
424 }
425
MaybeDestroyInstance()426 static void MaybeDestroyInstance() {
427 mozilla::ipc::AssertIsOnBackgroundThread();
428 MOZ_DIAGNOSTIC_ASSERT(sFactory);
429
430 // If the factory is is still in use then we cannot delete yet. This
431 // could be due to managers still existing or because we are in the
432 // middle of aborting or shutting down. We need to be careful not to delete
433 // ourself synchronously during shutdown.
434 if (!sFactory->mManagerList.IsEmpty() || sFactory->mInSyncAbortOrShutdown) {
435 return;
436 }
437
438 sFactory = nullptr;
439 }
440
Acquire(const ManagerId & aManagerId,State aState=Open)441 static SafeRefPtr<Manager> Acquire(const ManagerId& aManagerId,
442 State aState = Open) {
443 mozilla::ipc::AssertIsOnBackgroundThread();
444
445 QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()), nullptr);
446
447 // Iterate in reverse to find the most recent, matching Manager. This
448 // is important when looking for a Closing Manager. If a new Manager
449 // chains to an old Manager we want it to be the most recent one.
450 const auto range = Reversed(sFactory->mManagerList.NonObservingRange());
451 const auto foundIt = std::find_if(
452 range.begin(), range.end(), [aState, &aManagerId](const auto& manager) {
453 return aState == manager->GetState() &&
454 *manager->mManagerId == aManagerId;
455 });
456 return foundIt != range.end()
457 ? SafeRefPtr{foundIt->get(), AcquireStrongRefFromRawPtr{}}
458 : nullptr;
459 }
460
461 template <typename Condition>
AbortMatching(const Condition & aCondition)462 static void AbortMatching(const Condition& aCondition) {
463 mozilla::ipc::AssertIsOnBackgroundThread();
464
465 if (!sFactory) {
466 return;
467 }
468
469 MOZ_DIAGNOSTIC_ASSERT(!sFactory->mManagerList.IsEmpty());
470
471 {
472 // Note that we are synchronously calling abort code here. If any
473 // of the shutdown code synchronously decides to delete the Factory
474 // we need to delay that delete until the end of this method.
475 AutoRestore<bool> restore(sFactory->mInSyncAbortOrShutdown);
476 sFactory->mInSyncAbortOrShutdown = true;
477
478 for (const auto& manager : sFactory->mManagerList.ForwardRange()) {
479 if (aCondition(*manager)) {
480 auto pinnedManager =
481 SafeRefPtr{manager.get(), AcquireStrongRefFromRawPtr{}};
482 pinnedManager->Abort();
483 }
484 }
485 }
486
487 MaybeDestroyInstance();
488 }
489
490 // Singleton created on demand and deleted when last Manager is cleared
491 // in Remove().
492 // PBackground thread only.
493 static StaticAutoPtr<Factory> sFactory;
494
495 // Weak references as we don't want to keep Manager objects alive forever.
496 // When a Manager is destroyed it calls Factory::Remove() to clear itself.
497 // PBackground thread only.
498 nsTObserverArray<NotNull<Manager*>> mManagerList;
499
500 // This flag is set when we are looping through the list and calling Abort()
501 // or Shutdown() on each Manager. We need to be careful not to synchronously
502 // trigger the deletion of the factory while still executing this loop.
503 bool mInSyncAbortOrShutdown;
504
505 nsTArray<int32_t> mPotentiallyUnreleasedCSCP;
506 };
507
508 // static
509 StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
510
511 // ----------------------------------------------------------------------------
512
513 // Abstract class to help implement the various Actions. The vast majority
514 // of Actions are synchronous and need to report back to a Listener on the
515 // Manager.
516 class Manager::BaseAction : public SyncDBAction {
517 protected:
BaseAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId)518 BaseAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId)
519 : SyncDBAction(DBAction::Existing),
520 mManager(std::move(aManager)),
521 mListenerId(aListenerId) {}
522
523 virtual void Complete(Listener* aListener, ErrorResult&& aRv) = 0;
524
CompleteOnInitiatingThread(nsresult aRv)525 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
526 NS_ASSERT_OWNINGTHREAD(Manager::BaseAction);
527 Listener* listener = mManager->GetListener(mListenerId);
528 if (listener) {
529 Complete(listener, ErrorResult(aRv));
530 }
531
532 // ensure we release the manager on the initiating thread
533 mManager = nullptr;
534 }
535
536 SafeRefPtr<Manager> mManager;
537 const ListenerId mListenerId;
538 };
539
540 // ----------------------------------------------------------------------------
541
542 // Action that is executed when we determine that content has stopped using
543 // a Cache object that has been orphaned.
544 class Manager::DeleteOrphanedCacheAction final : public SyncDBAction {
545 public:
DeleteOrphanedCacheAction(SafeRefPtr<Manager> aManager,CacheId aCacheId)546 DeleteOrphanedCacheAction(SafeRefPtr<Manager> aManager, CacheId aCacheId)
547 : SyncDBAction(DBAction::Existing),
548 mManager(std::move(aManager)),
549 mCacheId(aCacheId) {}
550
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)551 virtual nsresult RunSyncWithDBOnTarget(
552 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
553 mozIStorageConnection* aConn) override {
554 mDirectoryMetadata.emplace(aDirectoryMetadata);
555
556 mozStorageTransaction trans(aConn, false,
557 mozIStorageConnection::TRANSACTION_IMMEDIATE);
558
559 QM_TRY(MOZ_TO_RESULT(trans.Start()));
560
561 QM_TRY_UNWRAP(mDeletionInfo, db::DeleteCacheId(*aConn, mCacheId));
562
563 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
564 aDBDir, aConn, /* aIncreaceSize */ 0, mDeletionInfo.mDeletedPaddingSize,
565 [&trans]() mutable { return trans.Commit(); })));
566
567 return NS_OK;
568 }
569
CompleteOnInitiatingThread(nsresult aRv)570 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
571 // If the transaction fails, we shouldn't delete the body files and decrease
572 // their padding size.
573 if (NS_FAILED(aRv)) {
574 mDeletionInfo.mDeletedBodyIdList.Clear();
575 mDeletionInfo.mDeletedPaddingSize = 0;
576 }
577
578 mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
579
580 if (mDeletionInfo.mDeletedPaddingSize > 0) {
581 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
582 mDeletionInfo.mDeletedPaddingSize);
583 }
584
585 // ensure we release the manager on the initiating thread
586 mManager = nullptr;
587 }
588
589 private:
590 SafeRefPtr<Manager> mManager;
591 const CacheId mCacheId;
592 DeletionInfo mDeletionInfo;
593 Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
594 };
595
596 // ----------------------------------------------------------------------------
597
598 class Manager::CacheMatchAction final : public Manager::BaseAction {
599 public:
CacheMatchAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const CacheMatchArgs & aArgs,SafeRefPtr<StreamList> aStreamList)600 CacheMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
601 CacheId aCacheId, const CacheMatchArgs& aArgs,
602 SafeRefPtr<StreamList> aStreamList)
603 : BaseAction(std::move(aManager), aListenerId),
604 mCacheId(aCacheId),
605 mArgs(aArgs),
606 mStreamList(std::move(aStreamList)),
607 mFoundResponse(false) {}
608
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)609 virtual nsresult RunSyncWithDBOnTarget(
610 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
611 mozIStorageConnection* aConn) override {
612 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
613
614 QM_TRY_INSPECT(
615 const auto& maybeResponse,
616 db::CacheMatch(*aConn, mCacheId, mArgs.request(), mArgs.params()));
617
618 mFoundResponse = maybeResponse.isSome();
619 if (mFoundResponse) {
620 mResponse = std::move(maybeResponse.ref());
621 }
622
623 if (!mFoundResponse || !mResponse.mHasBodyId ||
624 IsHeadRequest(mArgs.request(), mArgs.params())) {
625 mResponse.mHasBodyId = false;
626 return NS_OK;
627 }
628
629 nsCOMPtr<nsIInputStream> stream;
630 if (mArgs.openMode() == OpenMode::Eager) {
631 QM_TRY_UNWRAP(stream,
632 BodyOpen(aDirectoryMetadata, *aDBDir, mResponse.mBodyId));
633 }
634
635 mStreamList->Add(mResponse.mBodyId, std::move(stream));
636
637 return NS_OK;
638 }
639
Complete(Listener * aListener,ErrorResult && aRv)640 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
641 if (!mFoundResponse) {
642 aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()));
643 } else {
644 mStreamList->Activate(mCacheId);
645 aListener->OnOpComplete(std::move(aRv), CacheMatchResult(Nothing()),
646 mResponse, *mStreamList);
647 }
648 mStreamList = nullptr;
649 }
650
MatchesCacheId(CacheId aCacheId) const651 virtual bool MatchesCacheId(CacheId aCacheId) const override {
652 return aCacheId == mCacheId;
653 }
654
655 private:
656 const CacheId mCacheId;
657 const CacheMatchArgs mArgs;
658 SafeRefPtr<StreamList> mStreamList;
659 bool mFoundResponse;
660 SavedResponse mResponse;
661 };
662
663 // ----------------------------------------------------------------------------
664
665 class Manager::CacheMatchAllAction final : public Manager::BaseAction {
666 public:
CacheMatchAllAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const CacheMatchAllArgs & aArgs,SafeRefPtr<StreamList> aStreamList)667 CacheMatchAllAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
668 CacheId aCacheId, const CacheMatchAllArgs& aArgs,
669 SafeRefPtr<StreamList> aStreamList)
670 : BaseAction(std::move(aManager), aListenerId),
671 mCacheId(aCacheId),
672 mArgs(aArgs),
673 mStreamList(std::move(aStreamList)) {}
674
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)675 virtual nsresult RunSyncWithDBOnTarget(
676 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
677 mozIStorageConnection* aConn) override {
678 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
679
680 QM_TRY_UNWRAP(mSavedResponses,
681 db::CacheMatchAll(*aConn, mCacheId, mArgs.maybeRequest(),
682 mArgs.params()));
683
684 for (uint32_t i = 0; i < mSavedResponses.Length(); ++i) {
685 if (!mSavedResponses[i].mHasBodyId ||
686 IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
687 mSavedResponses[i].mHasBodyId = false;
688 continue;
689 }
690
691 nsCOMPtr<nsIInputStream> stream;
692 if (mArgs.openMode() == OpenMode::Eager) {
693 QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
694 mSavedResponses[i].mBodyId));
695 }
696
697 mStreamList->Add(mSavedResponses[i].mBodyId, std::move(stream));
698 }
699
700 return NS_OK;
701 }
702
Complete(Listener * aListener,ErrorResult && aRv)703 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
704 mStreamList->Activate(mCacheId);
705 aListener->OnOpComplete(std::move(aRv), CacheMatchAllResult(),
706 mSavedResponses, *mStreamList);
707 mStreamList = nullptr;
708 }
709
MatchesCacheId(CacheId aCacheId) const710 virtual bool MatchesCacheId(CacheId aCacheId) const override {
711 return aCacheId == mCacheId;
712 }
713
714 private:
715 const CacheId mCacheId;
716 const CacheMatchAllArgs mArgs;
717 SafeRefPtr<StreamList> mStreamList;
718 nsTArray<SavedResponse> mSavedResponses;
719 };
720
721 // ----------------------------------------------------------------------------
722
723 // This is the most complex Action. It puts a request/response pair into the
724 // Cache. It does not complete until all of the body data has been saved to
725 // disk. This means its an asynchronous Action.
726 class Manager::CachePutAllAction final : public DBAction {
727 public:
CachePutAllAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const nsTArray<CacheRequestResponse> & aPutList,const nsTArray<nsCOMPtr<nsIInputStream>> & aRequestStreamList,const nsTArray<nsCOMPtr<nsIInputStream>> & aResponseStreamList)728 CachePutAllAction(
729 SafeRefPtr<Manager> aManager, ListenerId aListenerId, CacheId aCacheId,
730 const nsTArray<CacheRequestResponse>& aPutList,
731 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
732 const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
733 : DBAction(DBAction::Existing),
734 mManager(std::move(aManager)),
735 mListenerId(aListenerId),
736 mCacheId(aCacheId),
737 mList(aPutList.Length()),
738 mExpectedAsyncCopyCompletions(1),
739 mAsyncResult(NS_OK),
740 mMutex("cache::Manager::CachePutAllAction"),
741 mUpdatedPaddingSize(0),
742 mDeletedPaddingSize(0) {
743 MOZ_DIAGNOSTIC_ASSERT(!aPutList.IsEmpty());
744 MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aRequestStreamList.Length());
745 MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aResponseStreamList.Length());
746
747 for (uint32_t i = 0; i < aPutList.Length(); ++i) {
748 Entry* entry = mList.AppendElement();
749 entry->mRequest = aPutList[i].request();
750 entry->mRequestStream = aRequestStreamList[i];
751 entry->mResponse = aPutList[i].response();
752 entry->mResponseStream = aResponseStreamList[i];
753 }
754 }
755
756 private:
757 ~CachePutAllAction() = default;
758
RunWithDBOnTarget(SafeRefPtr<Resolver> aResolver,const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)759 virtual void RunWithDBOnTarget(
760 SafeRefPtr<Resolver> aResolver,
761 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
762 mozIStorageConnection* aConn) override {
763 MOZ_DIAGNOSTIC_ASSERT(aResolver);
764 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
765 MOZ_DIAGNOSTIC_ASSERT(aConn);
766 MOZ_DIAGNOSTIC_ASSERT(!mResolver);
767 MOZ_DIAGNOSTIC_ASSERT(!mDBDir);
768 MOZ_DIAGNOSTIC_ASSERT(!mConn);
769
770 MOZ_DIAGNOSTIC_ASSERT(!mTarget);
771 mTarget = GetCurrentSerialEventTarget();
772 MOZ_DIAGNOSTIC_ASSERT(mTarget);
773
774 // We should be pre-initialized to expect one async completion. This is
775 // the "manual" completion we call at the end of this method in all
776 // cases.
777 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions == 1);
778
779 mResolver = std::move(aResolver);
780 mDBDir = aDBDir;
781 mConn = aConn;
782 mDirectoryMetadata.emplace(aDirectoryMetadata);
783
784 // File bodies are streamed to disk via asynchronous copying. Start
785 // this copying now. Each copy will eventually result in a call
786 // to OnAsyncCopyComplete().
787 const nsresult rv = [this, &aDirectoryMetadata]() -> nsresult {
788 QM_TRY(CollectEachInRange(
789 mList, [this, &aDirectoryMetadata](auto& entry) -> nsresult {
790 QM_TRY(MOZ_TO_RESULT(
791 StartStreamCopy(aDirectoryMetadata, entry, RequestStream,
792 &mExpectedAsyncCopyCompletions)));
793
794 QM_TRY(MOZ_TO_RESULT(
795 StartStreamCopy(aDirectoryMetadata, entry, ResponseStream,
796 &mExpectedAsyncCopyCompletions)));
797
798 return NS_OK;
799 }));
800
801 return NS_OK;
802 }();
803
804 // Always call OnAsyncCopyComplete() manually here. This covers the
805 // case where there is no async copying and also reports any startup
806 // errors correctly. If we hit an error, then OnAsyncCopyComplete()
807 // will cancel any async copying.
808 OnAsyncCopyComplete(rv);
809 }
810
811 // Called once for each asynchronous file copy whether it succeeds or
812 // fails. If a file copy is canceled, it still calls this method with
813 // an error code.
OnAsyncCopyComplete(nsresult aRv)814 void OnAsyncCopyComplete(nsresult aRv) {
815 MOZ_ASSERT(mTarget->IsOnCurrentThread());
816 MOZ_DIAGNOSTIC_ASSERT(mConn);
817 MOZ_DIAGNOSTIC_ASSERT(mResolver);
818 MOZ_DIAGNOSTIC_ASSERT(mExpectedAsyncCopyCompletions > 0);
819
820 // Explicitly check for cancellation here to catch a race condition.
821 // Consider:
822 //
823 // 1) NS_AsyncCopy() executes on IO thread, but has not saved its
824 // copy context yet.
825 // 2) CancelAllStreamCopying() occurs on PBackground thread
826 // 3) Copy context from (1) is saved on IO thread.
827 //
828 // Checking for cancellation here catches this condition when we
829 // first call OnAsyncCopyComplete() manually from RunWithDBOnTarget().
830 //
831 // This explicit cancellation check also handles the case where we
832 // are canceled just after all stream copying completes. We should
833 // abort the synchronous DB operations in this case if we have not
834 // started them yet.
835 if (NS_SUCCEEDED(aRv) && IsCanceled()) {
836 aRv = NS_ERROR_ABORT;
837 }
838
839 // If any of the async copies fail, we need to still wait for them all to
840 // complete. Cancel any other streams still working and remember the
841 // error. All canceled streams will call OnAsyncCopyComplete().
842 if (NS_FAILED(aRv) && NS_SUCCEEDED(mAsyncResult)) {
843 CancelAllStreamCopying();
844 mAsyncResult = aRv;
845 }
846
847 // Check to see if async copying is still on-going. If so, then simply
848 // return for now. We must wait for a later OnAsyncCopyComplete() call.
849 mExpectedAsyncCopyCompletions -= 1;
850 if (mExpectedAsyncCopyCompletions > 0) {
851 return;
852 }
853
854 // We have finished with all async copying. Indicate this by clearing all
855 // our copy contexts.
856 {
857 MutexAutoLock lock(mMutex);
858 mCopyContextList.Clear();
859 }
860
861 // An error occurred while async copying. Terminate the Action.
862 // DoResolve() will clean up any files we may have written.
863 if (NS_FAILED(mAsyncResult)) {
864 DoResolve(mAsyncResult);
865 return;
866 }
867
868 mozStorageTransaction trans(mConn, false,
869 mozIStorageConnection::TRANSACTION_IMMEDIATE);
870
871 QM_TRY(MOZ_TO_RESULT(trans.Start()), QM_VOID);
872
873 const nsresult rv = [this, &trans]() -> nsresult {
874 QM_TRY(CollectEachInRange(mList, [this](Entry& e) -> nsresult {
875 if (e.mRequestStream) {
876 QM_TRY(MOZ_TO_RESULT(BodyFinalizeWrite(*mDBDir, e.mRequestBodyId)));
877 }
878 if (e.mResponseStream) {
879 // Gerenate padding size for opaque response if needed.
880 if (e.mResponse.type() == ResponseType::Opaque) {
881 // It'll generate padding if we've not set it yet.
882 QM_TRY(MOZ_TO_RESULT(BodyMaybeUpdatePaddingSize(
883 *mDirectoryMetadata, *mDBDir, e.mResponseBodyId,
884 e.mResponse.paddingInfo(), &e.mResponse.paddingSize())));
885
886 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - e.mResponse.paddingSize() >=
887 mUpdatedPaddingSize);
888 mUpdatedPaddingSize += e.mResponse.paddingSize();
889 }
890
891 QM_TRY(MOZ_TO_RESULT(BodyFinalizeWrite(*mDBDir, e.mResponseBodyId)));
892 }
893
894 QM_TRY_UNWRAP(
895 auto deletionInfo,
896 db::CachePut(*mConn, mCacheId, e.mRequest,
897 e.mRequestStream ? &e.mRequestBodyId : nullptr,
898 e.mResponse,
899 e.mResponseStream ? &e.mResponseBodyId : nullptr));
900
901 const int64_t deletedPaddingSize = deletionInfo.mDeletedPaddingSize;
902 mDeletedBodyIdList = std::move(deletionInfo.mDeletedBodyIdList);
903
904 MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - mDeletedPaddingSize >=
905 deletedPaddingSize);
906 mDeletedPaddingSize += deletedPaddingSize;
907
908 return NS_OK;
909 }));
910
911 // Update padding file when it's necessary
912 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
913 mDBDir, mConn, mUpdatedPaddingSize, mDeletedPaddingSize,
914 [&trans]() mutable { return trans.Commit(); })));
915
916 return NS_OK;
917 }();
918
919 DoResolve(rv);
920 }
921
CompleteOnInitiatingThread(nsresult aRv)922 virtual void CompleteOnInitiatingThread(nsresult aRv) override {
923 NS_ASSERT_OWNINGTHREAD(Action);
924
925 for (uint32_t i = 0; i < mList.Length(); ++i) {
926 mList[i].mRequestStream = nullptr;
927 mList[i].mResponseStream = nullptr;
928 }
929
930 // If the transaction fails, we shouldn't delete the body files and decrease
931 // their padding size.
932 if (NS_FAILED(aRv)) {
933 mDeletedBodyIdList.Clear();
934 mDeletedPaddingSize = 0;
935 }
936
937 mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList);
938
939 if (mDeletedPaddingSize > 0) {
940 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
941 mDeletedPaddingSize);
942 }
943
944 Listener* listener = mManager->GetListener(mListenerId);
945 mManager = nullptr;
946 if (listener) {
947 listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult());
948 }
949 }
950
CancelOnInitiatingThread()951 virtual void CancelOnInitiatingThread() override {
952 NS_ASSERT_OWNINGTHREAD(Action);
953 Action::CancelOnInitiatingThread();
954 CancelAllStreamCopying();
955 }
956
MatchesCacheId(CacheId aCacheId) const957 virtual bool MatchesCacheId(CacheId aCacheId) const override {
958 NS_ASSERT_OWNINGTHREAD(Action);
959 return aCacheId == mCacheId;
960 }
961
962 struct Entry {
963 CacheRequest mRequest;
964 nsCOMPtr<nsIInputStream> mRequestStream;
965 nsID mRequestBodyId;
966 nsCOMPtr<nsISupports> mRequestCopyContext;
967
968 CacheResponse mResponse;
969 nsCOMPtr<nsIInputStream> mResponseStream;
970 nsID mResponseBodyId;
971 nsCOMPtr<nsISupports> mResponseCopyContext;
972 };
973
974 enum StreamId { RequestStream, ResponseStream };
975
StartStreamCopy(const CacheDirectoryMetadata & aDirectoryMetadata,Entry & aEntry,StreamId aStreamId,uint32_t * aCopyCountOut)976 nsresult StartStreamCopy(const CacheDirectoryMetadata& aDirectoryMetadata,
977 Entry& aEntry, StreamId aStreamId,
978 uint32_t* aCopyCountOut) {
979 MOZ_ASSERT(mTarget->IsOnCurrentThread());
980 MOZ_DIAGNOSTIC_ASSERT(aCopyCountOut);
981
982 if (IsCanceled()) {
983 return NS_ERROR_ABORT;
984 }
985
986 MOZ_DIAGNOSTIC_ASSERT(aStreamId == RequestStream ||
987 aStreamId == ResponseStream);
988
989 const auto& source = aStreamId == RequestStream ? aEntry.mRequestStream
990 : aEntry.mResponseStream;
991
992 if (!source) {
993 return NS_OK;
994 }
995
996 QM_TRY_INSPECT((const auto& [bodyId, copyContext]),
997 BodyStartWriteStream(aDirectoryMetadata, *mDBDir, *source,
998 this, AsyncCopyCompleteFunc));
999
1000 if (aStreamId == RequestStream) {
1001 aEntry.mRequestBodyId = bodyId;
1002 } else {
1003 aEntry.mResponseBodyId = bodyId;
1004 }
1005
1006 mBodyIdWrittenList.AppendElement(bodyId);
1007
1008 if (copyContext) {
1009 MutexAutoLock lock(mMutex);
1010 mCopyContextList.AppendElement(copyContext);
1011 }
1012
1013 *aCopyCountOut += 1;
1014
1015 return NS_OK;
1016 }
1017
CancelAllStreamCopying()1018 void CancelAllStreamCopying() {
1019 // May occur on either owning thread or target thread
1020 MutexAutoLock lock(mMutex);
1021 for (uint32_t i = 0; i < mCopyContextList.Length(); ++i) {
1022 MOZ_DIAGNOSTIC_ASSERT(mCopyContextList[i]);
1023 BodyCancelWrite(*mCopyContextList[i]);
1024 }
1025 mCopyContextList.Clear();
1026 }
1027
AsyncCopyCompleteFunc(void * aClosure,nsresult aRv)1028 static void AsyncCopyCompleteFunc(void* aClosure, nsresult aRv) {
1029 // May be on any thread, including STS event target.
1030 MOZ_DIAGNOSTIC_ASSERT(aClosure);
1031 // Weak ref as we are guaranteed to the action is alive until
1032 // CompleteOnInitiatingThread is called.
1033 CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure);
1034 action->CallOnAsyncCopyCompleteOnTargetThread(aRv);
1035 }
1036
CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv)1037 void CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv) {
1038 // May be on any thread, including STS event target. Non-owning runnable
1039 // here since we are guaranteed the Action will survive until
1040 // CompleteOnInitiatingThread is called.
1041 nsCOMPtr<nsIRunnable> runnable = NewNonOwningRunnableMethod<nsresult>(
1042 "dom::cache::Manager::CachePutAllAction::OnAsyncCopyComplete", this,
1043 &CachePutAllAction::OnAsyncCopyComplete, aRv);
1044 MOZ_ALWAYS_SUCCEEDS(
1045 mTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL));
1046 }
1047
DoResolve(nsresult aRv)1048 void DoResolve(nsresult aRv) {
1049 MOZ_ASSERT(mTarget->IsOnCurrentThread());
1050
1051 // DoResolve() must not be called until all async copying has completed.
1052 #ifdef DEBUG
1053 {
1054 MutexAutoLock lock(mMutex);
1055 MOZ_ASSERT(mCopyContextList.IsEmpty());
1056 }
1057 #endif
1058
1059 // Clean up any files we might have written before hitting the error.
1060 if (NS_FAILED(aRv)) {
1061 BodyDeleteFiles(*mDirectoryMetadata, *mDBDir, mBodyIdWrittenList);
1062 if (mUpdatedPaddingSize > 0) {
1063 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
1064 mUpdatedPaddingSize);
1065 }
1066 }
1067
1068 // Must be released on the target thread where it was opened.
1069 mConn = nullptr;
1070
1071 // Drop our ref to the target thread as we are done with this thread.
1072 // Also makes our thread assertions catch any incorrect method calls
1073 // after resolve.
1074 mTarget = nullptr;
1075
1076 // Make sure to de-ref the resolver per the Action API contract.
1077 SafeRefPtr<Action::Resolver> resolver = std::move(mResolver);
1078 resolver->Resolve(aRv);
1079 }
1080
1081 // initiating thread only
1082 SafeRefPtr<Manager> mManager;
1083 const ListenerId mListenerId;
1084
1085 // Set on initiating thread, read on target thread. State machine guarantees
1086 // these are not modified while being read by the target thread.
1087 const CacheId mCacheId;
1088 nsTArray<Entry> mList;
1089 uint32_t mExpectedAsyncCopyCompletions;
1090
1091 // target thread only
1092 SafeRefPtr<Resolver> mResolver;
1093 nsCOMPtr<nsIFile> mDBDir;
1094 nsCOMPtr<mozIStorageConnection> mConn;
1095 nsCOMPtr<nsISerialEventTarget> mTarget;
1096 nsresult mAsyncResult;
1097 nsTArray<nsID> mBodyIdWrittenList;
1098
1099 // Written to on target thread, accessed on initiating thread after target
1100 // thread activity is guaranteed complete
1101 nsTArray<nsID> mDeletedBodyIdList;
1102
1103 // accessed from any thread while mMutex locked
1104 Mutex mMutex;
1105 nsTArray<nsCOMPtr<nsISupports>> mCopyContextList;
1106
1107 Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
1108 // Track how much pad amount has been added for new entries so that it can be
1109 // removed if an error occurs.
1110 int64_t mUpdatedPaddingSize;
1111 // Track any pad amount associated with overwritten entries.
1112 int64_t mDeletedPaddingSize;
1113 };
1114
1115 // ----------------------------------------------------------------------------
1116
1117 class Manager::CacheDeleteAction final : public Manager::BaseAction {
1118 public:
CacheDeleteAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const CacheDeleteArgs & aArgs)1119 CacheDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1120 CacheId aCacheId, const CacheDeleteArgs& aArgs)
1121 : BaseAction(std::move(aManager), aListenerId),
1122 mCacheId(aCacheId),
1123 mArgs(aArgs),
1124 mSuccess(false) {}
1125
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1126 virtual nsresult RunSyncWithDBOnTarget(
1127 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1128 mozIStorageConnection* aConn) override {
1129 mDirectoryMetadata.emplace(aDirectoryMetadata);
1130
1131 mozStorageTransaction trans(aConn, false,
1132 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1133
1134 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1135
1136 QM_TRY_UNWRAP(
1137 auto maybeDeletionInfo,
1138 db::CacheDelete(*aConn, mCacheId, mArgs.request(), mArgs.params()));
1139
1140 mSuccess = maybeDeletionInfo.isSome();
1141 if (mSuccess) {
1142 mDeletionInfo = std::move(maybeDeletionInfo.ref());
1143 }
1144
1145 QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
1146 aDBDir, aConn, /* aIncreaceSize */ 0,
1147 mDeletionInfo.mDeletedPaddingSize,
1148 [&trans]() mutable { return trans.Commit(); })),
1149 QM_PROPAGATE, [this](const nsresult) { mSuccess = false; });
1150
1151 return NS_OK;
1152 }
1153
Complete(Listener * aListener,ErrorResult && aRv)1154 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1155 // If the transaction fails, we shouldn't delete the body files and decrease
1156 // their padding size.
1157 if (aRv.Failed()) {
1158 mDeletionInfo.mDeletedBodyIdList.Clear();
1159 mDeletionInfo.mDeletedPaddingSize = 0;
1160 }
1161
1162 mManager->NoteOrphanedBodyIdList(mDeletionInfo.mDeletedBodyIdList);
1163
1164 if (mDeletionInfo.mDeletedPaddingSize > 0) {
1165 DecreaseUsageForDirectoryMetadata(*mDirectoryMetadata,
1166 mDeletionInfo.mDeletedPaddingSize);
1167 }
1168
1169 aListener->OnOpComplete(std::move(aRv), CacheDeleteResult(mSuccess));
1170 }
1171
MatchesCacheId(CacheId aCacheId) const1172 virtual bool MatchesCacheId(CacheId aCacheId) const override {
1173 return aCacheId == mCacheId;
1174 }
1175
1176 private:
1177 const CacheId mCacheId;
1178 const CacheDeleteArgs mArgs;
1179 bool mSuccess;
1180 DeletionInfo mDeletionInfo;
1181 Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
1182 };
1183
1184 // ----------------------------------------------------------------------------
1185
1186 class Manager::CacheKeysAction final : public Manager::BaseAction {
1187 public:
CacheKeysAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,CacheId aCacheId,const CacheKeysArgs & aArgs,SafeRefPtr<StreamList> aStreamList)1188 CacheKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1189 CacheId aCacheId, const CacheKeysArgs& aArgs,
1190 SafeRefPtr<StreamList> aStreamList)
1191 : BaseAction(std::move(aManager), aListenerId),
1192 mCacheId(aCacheId),
1193 mArgs(aArgs),
1194 mStreamList(std::move(aStreamList)) {}
1195
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1196 virtual nsresult RunSyncWithDBOnTarget(
1197 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1198 mozIStorageConnection* aConn) override {
1199 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1200
1201 QM_TRY_UNWRAP(
1202 mSavedRequests,
1203 db::CacheKeys(*aConn, mCacheId, mArgs.maybeRequest(), mArgs.params()));
1204
1205 for (uint32_t i = 0; i < mSavedRequests.Length(); ++i) {
1206 if (!mSavedRequests[i].mHasBodyId ||
1207 IsHeadRequest(mArgs.maybeRequest(), mArgs.params())) {
1208 mSavedRequests[i].mHasBodyId = false;
1209 continue;
1210 }
1211
1212 nsCOMPtr<nsIInputStream> stream;
1213 if (mArgs.openMode() == OpenMode::Eager) {
1214 QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
1215 mSavedRequests[i].mBodyId));
1216 }
1217
1218 mStreamList->Add(mSavedRequests[i].mBodyId, std::move(stream));
1219 }
1220
1221 return NS_OK;
1222 }
1223
Complete(Listener * aListener,ErrorResult && aRv)1224 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1225 mStreamList->Activate(mCacheId);
1226 aListener->OnOpComplete(std::move(aRv), CacheKeysResult(), mSavedRequests,
1227 *mStreamList);
1228 mStreamList = nullptr;
1229 }
1230
MatchesCacheId(CacheId aCacheId) const1231 virtual bool MatchesCacheId(CacheId aCacheId) const override {
1232 return aCacheId == mCacheId;
1233 }
1234
1235 private:
1236 const CacheId mCacheId;
1237 const CacheKeysArgs mArgs;
1238 SafeRefPtr<StreamList> mStreamList;
1239 nsTArray<SavedRequest> mSavedRequests;
1240 };
1241
1242 // ----------------------------------------------------------------------------
1243
1244 class Manager::StorageMatchAction final : public Manager::BaseAction {
1245 public:
StorageMatchAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace,const StorageMatchArgs & aArgs,SafeRefPtr<StreamList> aStreamList)1246 StorageMatchAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1247 Namespace aNamespace, const StorageMatchArgs& aArgs,
1248 SafeRefPtr<StreamList> aStreamList)
1249 : BaseAction(std::move(aManager), aListenerId),
1250 mNamespace(aNamespace),
1251 mArgs(aArgs),
1252 mStreamList(std::move(aStreamList)),
1253 mFoundResponse(false) {}
1254
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1255 virtual nsresult RunSyncWithDBOnTarget(
1256 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1257 mozIStorageConnection* aConn) override {
1258 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1259
1260 auto maybeResponse =
1261 db::StorageMatch(*aConn, mNamespace, mArgs.request(), mArgs.params());
1262 if (NS_WARN_IF(maybeResponse.isErr())) {
1263 return maybeResponse.unwrapErr();
1264 }
1265
1266 mFoundResponse = maybeResponse.inspect().isSome();
1267 if (mFoundResponse) {
1268 mSavedResponse = maybeResponse.unwrap().ref();
1269 }
1270
1271 if (!mFoundResponse || !mSavedResponse.mHasBodyId ||
1272 IsHeadRequest(mArgs.request(), mArgs.params())) {
1273 mSavedResponse.mHasBodyId = false;
1274 return NS_OK;
1275 }
1276
1277 nsCOMPtr<nsIInputStream> stream;
1278 if (mArgs.openMode() == OpenMode::Eager) {
1279 QM_TRY_UNWRAP(stream, BodyOpen(aDirectoryMetadata, *aDBDir,
1280 mSavedResponse.mBodyId));
1281 }
1282
1283 mStreamList->Add(mSavedResponse.mBodyId, std::move(stream));
1284
1285 return NS_OK;
1286 }
1287
Complete(Listener * aListener,ErrorResult && aRv)1288 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1289 if (!mFoundResponse) {
1290 aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()));
1291 } else {
1292 mStreamList->Activate(mSavedResponse.mCacheId);
1293 aListener->OnOpComplete(std::move(aRv), StorageMatchResult(Nothing()),
1294 mSavedResponse, *mStreamList);
1295 }
1296 mStreamList = nullptr;
1297 }
1298
1299 private:
1300 const Namespace mNamespace;
1301 const StorageMatchArgs mArgs;
1302 SafeRefPtr<StreamList> mStreamList;
1303 bool mFoundResponse;
1304 SavedResponse mSavedResponse;
1305 };
1306
1307 // ----------------------------------------------------------------------------
1308
1309 class Manager::StorageHasAction final : public Manager::BaseAction {
1310 public:
StorageHasAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace,const StorageHasArgs & aArgs)1311 StorageHasAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1312 Namespace aNamespace, const StorageHasArgs& aArgs)
1313 : BaseAction(std::move(aManager), aListenerId),
1314 mNamespace(aNamespace),
1315 mArgs(aArgs),
1316 mCacheFound(false) {}
1317
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1318 virtual nsresult RunSyncWithDBOnTarget(
1319 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1320 mozIStorageConnection* aConn) override {
1321 QM_TRY_INSPECT(const auto& maybeCacheId,
1322 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1323
1324 mCacheFound = maybeCacheId.isSome();
1325
1326 return NS_OK;
1327 }
1328
Complete(Listener * aListener,ErrorResult && aRv)1329 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1330 aListener->OnOpComplete(std::move(aRv), StorageHasResult(mCacheFound));
1331 }
1332
1333 private:
1334 const Namespace mNamespace;
1335 const StorageHasArgs mArgs;
1336 bool mCacheFound;
1337 };
1338
1339 // ----------------------------------------------------------------------------
1340
1341 class Manager::StorageOpenAction final : public Manager::BaseAction {
1342 public:
StorageOpenAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace,const StorageOpenArgs & aArgs)1343 StorageOpenAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1344 Namespace aNamespace, const StorageOpenArgs& aArgs)
1345 : BaseAction(std::move(aManager), aListenerId),
1346 mNamespace(aNamespace),
1347 mArgs(aArgs),
1348 mCacheId(INVALID_CACHE_ID) {}
1349
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1350 virtual nsresult RunSyncWithDBOnTarget(
1351 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1352 mozIStorageConnection* aConn) override {
1353 // Cache does not exist, create it instead
1354 mozStorageTransaction trans(aConn, false,
1355 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1356
1357 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1358
1359 // Look for existing cache
1360 QM_TRY_INSPECT(const auto& maybeCacheId,
1361 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1362
1363 if (maybeCacheId.isSome()) {
1364 mCacheId = maybeCacheId.ref();
1365 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
1366 return NS_OK;
1367 }
1368
1369 QM_TRY_UNWRAP(mCacheId, db::CreateCacheId(*aConn));
1370
1371 QM_TRY(MOZ_TO_RESULT(
1372 db::StoragePutCache(*aConn, mNamespace, mArgs.key(), mCacheId)));
1373
1374 QM_TRY(MOZ_TO_RESULT(trans.Commit()));
1375
1376 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID);
1377 return NS_OK;
1378 }
1379
Complete(Listener * aListener,ErrorResult && aRv)1380 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1381 MOZ_DIAGNOSTIC_ASSERT(aRv.Failed() || mCacheId != INVALID_CACHE_ID);
1382 aListener->OnOpComplete(std::move(aRv),
1383 StorageOpenResult(nullptr, nullptr, mNamespace),
1384 mCacheId);
1385 }
1386
1387 private:
1388 const Namespace mNamespace;
1389 const StorageOpenArgs mArgs;
1390 CacheId mCacheId;
1391 };
1392
1393 // ----------------------------------------------------------------------------
1394
1395 class Manager::StorageDeleteAction final : public Manager::BaseAction {
1396 public:
StorageDeleteAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace,const StorageDeleteArgs & aArgs)1397 StorageDeleteAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1398 Namespace aNamespace, const StorageDeleteArgs& aArgs)
1399 : BaseAction(std::move(aManager), aListenerId),
1400 mNamespace(aNamespace),
1401 mArgs(aArgs),
1402 mCacheDeleted(false),
1403 mCacheId(INVALID_CACHE_ID) {}
1404
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1405 virtual nsresult RunSyncWithDBOnTarget(
1406 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1407 mozIStorageConnection* aConn) override {
1408 mozStorageTransaction trans(aConn, false,
1409 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1410
1411 QM_TRY(MOZ_TO_RESULT(trans.Start()));
1412
1413 QM_TRY_INSPECT(const auto& maybeCacheId,
1414 db::StorageGetCacheId(*aConn, mNamespace, mArgs.key()));
1415
1416 if (maybeCacheId.isNothing()) {
1417 mCacheDeleted = false;
1418 return NS_OK;
1419 }
1420 mCacheId = maybeCacheId.ref();
1421
1422 // Don't delete the removing padding size here, we'll delete it on
1423 // DeleteOrphanedCacheAction.
1424 QM_TRY(
1425 MOZ_TO_RESULT(db::StorageForgetCache(*aConn, mNamespace, mArgs.key())));
1426
1427 QM_TRY(MOZ_TO_RESULT(trans.Commit()));
1428
1429 mCacheDeleted = true;
1430 return NS_OK;
1431 }
1432
Complete(Listener * aListener,ErrorResult && aRv)1433 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1434 if (mCacheDeleted) {
1435 // If content is referencing this cache, mark it orphaned to be
1436 // deleted later.
1437 if (!mManager->SetCacheIdOrphanedIfRefed(mCacheId)) {
1438 // no outstanding references, delete immediately
1439 const auto pinnedContext =
1440 SafeRefPtr{mManager->mContext, AcquireStrongRefFromRawPtr{}};
1441
1442 if (pinnedContext->IsCanceled()) {
1443 pinnedContext->NoteOrphanedData();
1444 } else {
1445 pinnedContext->CancelForCacheId(mCacheId);
1446 pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
1447 mManager.clonePtr(), mCacheId));
1448 }
1449 }
1450 }
1451
1452 aListener->OnOpComplete(std::move(aRv), StorageDeleteResult(mCacheDeleted));
1453 }
1454
1455 private:
1456 const Namespace mNamespace;
1457 const StorageDeleteArgs mArgs;
1458 bool mCacheDeleted;
1459 CacheId mCacheId;
1460 };
1461
1462 // ----------------------------------------------------------------------------
1463
1464 class Manager::StorageKeysAction final : public Manager::BaseAction {
1465 public:
StorageKeysAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,Namespace aNamespace)1466 StorageKeysAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1467 Namespace aNamespace)
1468 : BaseAction(std::move(aManager), aListenerId), mNamespace(aNamespace) {}
1469
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1470 virtual nsresult RunSyncWithDBOnTarget(
1471 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1472 mozIStorageConnection* aConn) override {
1473 QM_TRY_UNWRAP(mKeys, db::StorageGetKeys(*aConn, mNamespace));
1474
1475 return NS_OK;
1476 }
1477
Complete(Listener * aListener,ErrorResult && aRv)1478 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1479 if (aRv.Failed()) {
1480 mKeys.Clear();
1481 }
1482 aListener->OnOpComplete(std::move(aRv), StorageKeysResult(mKeys));
1483 }
1484
1485 private:
1486 const Namespace mNamespace;
1487 nsTArray<nsString> mKeys;
1488 };
1489
1490 // ----------------------------------------------------------------------------
1491
1492 class Manager::OpenStreamAction final : public Manager::BaseAction {
1493 public:
OpenStreamAction(SafeRefPtr<Manager> aManager,ListenerId aListenerId,InputStreamResolver && aResolver,const nsID & aBodyId)1494 OpenStreamAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId,
1495 InputStreamResolver&& aResolver, const nsID& aBodyId)
1496 : BaseAction(std::move(aManager), aListenerId),
1497 mResolver(std::move(aResolver)),
1498 mBodyId(aBodyId) {}
1499
RunSyncWithDBOnTarget(const CacheDirectoryMetadata & aDirectoryMetadata,nsIFile * aDBDir,mozIStorageConnection * aConn)1500 virtual nsresult RunSyncWithDBOnTarget(
1501 const CacheDirectoryMetadata& aDirectoryMetadata, nsIFile* aDBDir,
1502 mozIStorageConnection* aConn) override {
1503 MOZ_DIAGNOSTIC_ASSERT(aDBDir);
1504
1505 QM_TRY_UNWRAP(mBodyStream, BodyOpen(aDirectoryMetadata, *aDBDir, mBodyId));
1506
1507 return NS_OK;
1508 }
1509
Complete(Listener * aListener,ErrorResult && aRv)1510 virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
1511 if (aRv.Failed()) {
1512 // Ignore the reason for fail and just pass a null input stream to let it
1513 // fail.
1514 aRv.SuppressException();
1515 mResolver(nullptr);
1516 } else {
1517 mResolver(std::move(mBodyStream));
1518 }
1519
1520 mResolver = nullptr;
1521 }
1522
1523 private:
1524 InputStreamResolver mResolver;
1525 const nsID mBodyId;
1526 nsCOMPtr<nsIInputStream> mBodyStream;
1527 };
1528
1529 // ----------------------------------------------------------------------------
1530
1531 // static
1532 Manager::ListenerId Manager::sNextListenerId = 0;
1533
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult)1534 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1535 const CacheOpResult& aResult) {
1536 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID, Nothing());
1537 }
1538
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult,CacheId aOpenedCacheId)1539 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1540 const CacheOpResult& aResult,
1541 CacheId aOpenedCacheId) {
1542 OnOpComplete(std::move(aRv), aResult, aOpenedCacheId, Nothing());
1543 }
1544
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult,const SavedResponse & aSavedResponse,StreamList & aStreamList)1545 void Manager::Listener::OnOpComplete(ErrorResult&& aRv,
1546 const CacheOpResult& aResult,
1547 const SavedResponse& aSavedResponse,
1548 StreamList& aStreamList) {
1549 AutoTArray<SavedResponse, 1> responseList;
1550 responseList.AppendElement(aSavedResponse);
1551 OnOpComplete(
1552 std::move(aRv), aResult, INVALID_CACHE_ID,
1553 Some(StreamInfo{responseList, nsTArray<SavedRequest>(), aStreamList}));
1554 }
1555
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult,const nsTArray<SavedResponse> & aSavedResponseList,StreamList & aStreamList)1556 void Manager::Listener::OnOpComplete(
1557 ErrorResult&& aRv, const CacheOpResult& aResult,
1558 const nsTArray<SavedResponse>& aSavedResponseList,
1559 StreamList& aStreamList) {
1560 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
1561 Some(StreamInfo{aSavedResponseList, nsTArray<SavedRequest>(),
1562 aStreamList}));
1563 }
1564
OnOpComplete(ErrorResult && aRv,const CacheOpResult & aResult,const nsTArray<SavedRequest> & aSavedRequestList,StreamList & aStreamList)1565 void Manager::Listener::OnOpComplete(
1566 ErrorResult&& aRv, const CacheOpResult& aResult,
1567 const nsTArray<SavedRequest>& aSavedRequestList, StreamList& aStreamList) {
1568 OnOpComplete(std::move(aRv), aResult, INVALID_CACHE_ID,
1569 Some(StreamInfo{nsTArray<SavedResponse>(), aSavedRequestList,
1570 aStreamList}));
1571 }
1572
1573 // static
AcquireCreateIfNonExistent(const SafeRefPtr<ManagerId> & aManagerId)1574 Result<SafeRefPtr<Manager>, nsresult> Manager::AcquireCreateIfNonExistent(
1575 const SafeRefPtr<ManagerId>& aManagerId) {
1576 mozilla::ipc::AssertIsOnBackgroundThread();
1577 return Factory::AcquireCreateIfNonExistent(aManagerId);
1578 }
1579
1580 // static
InitiateShutdown()1581 void Manager::InitiateShutdown() {
1582 mozilla::ipc::AssertIsOnBackgroundThread();
1583
1584 Factory::ShutdownAll();
1585 }
1586
1587 // static
IsShutdownAllComplete()1588 bool Manager::IsShutdownAllComplete() {
1589 mozilla::ipc::AssertIsOnBackgroundThread();
1590
1591 return Factory::IsShutdownAllComplete();
1592 }
1593
1594 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId)1595 void Manager::RecordMayNotDeleteCSCP(int32_t aCacheStreamControlParentId) {
1596 Factory::RecordMayNotDeleteCSCP(aCacheStreamControlParentId);
1597 }
1598
RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId)1599 void Manager::RecordHaveDeletedCSCP(int32_t aCacheStreamControlParentId) {
1600 Factory::RecordHaveDeletedCSCP(aCacheStreamControlParentId);
1601 }
1602 #endif
1603
1604 // static
GetShutdownStatus()1605 nsCString Manager::GetShutdownStatus() {
1606 mozilla::ipc::AssertIsOnBackgroundThread();
1607
1608 return Factory::GetShutdownStatus();
1609 }
1610
1611 // static
Abort(const Client::DirectoryLockIdTable & aDirectoryLockIds)1612 void Manager::Abort(const Client::DirectoryLockIdTable& aDirectoryLockIds) {
1613 mozilla::ipc::AssertIsOnBackgroundThread();
1614
1615 Factory::Abort(aDirectoryLockIds);
1616 }
1617
1618 // static
AbortAll()1619 void Manager::AbortAll() {
1620 mozilla::ipc::AssertIsOnBackgroundThread();
1621
1622 Factory::AbortAll();
1623 }
1624
RemoveListener(Listener * aListener)1625 void Manager::RemoveListener(Listener* aListener) {
1626 NS_ASSERT_OWNINGTHREAD(Manager);
1627 // There may not be a listener here in the case where an actor is killed
1628 // before it can perform any actual async requests on Manager.
1629 mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
1630 MOZ_ASSERT(
1631 !mListeners.Contains(aListener, ListenerEntryListenerComparator()));
1632 MaybeAllowContextToClose();
1633 }
1634
RemoveContext(Context & aContext)1635 void Manager::RemoveContext(Context& aContext) {
1636 NS_ASSERT_OWNINGTHREAD(Manager);
1637 MOZ_DIAGNOSTIC_ASSERT(mContext);
1638 MOZ_DIAGNOSTIC_ASSERT(mContext == &aContext);
1639
1640 // Whether the Context destruction was triggered from the Manager going
1641 // idle or the underlying storage being invalidated, we should know we
1642 // are closing before the Context is destroyed.
1643 MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
1644
1645 // Before forgetting the Context, check to see if we have any outstanding
1646 // cache or body objects waiting for deletion. If so, note that we've
1647 // orphaned data so it will be cleaned up on the next open.
1648 if (std::any_of(
1649 mCacheIdRefs.cbegin(), mCacheIdRefs.cend(),
1650 [](const auto& cacheIdRef) { return cacheIdRef.mOrphaned; }) ||
1651 std::any_of(mBodyIdRefs.cbegin(), mBodyIdRefs.cend(),
1652 [](const auto& bodyIdRef) { return bodyIdRef.mOrphaned; })) {
1653 aContext.NoteOrphanedData();
1654 }
1655
1656 mContext = nullptr;
1657
1658 // Once the context is gone, we can immediately remove ourself from the
1659 // Factory list. We don't need to block shutdown by staying in the list
1660 // any more.
1661 Factory::Remove(*this);
1662 }
1663
NoteClosing()1664 void Manager::NoteClosing() {
1665 NS_ASSERT_OWNINGTHREAD(Manager);
1666 // This can be called more than once legitimately through different paths.
1667 mState = Closing;
1668 }
1669
GetState() const1670 Manager::State Manager::GetState() const {
1671 NS_ASSERT_OWNINGTHREAD(Manager);
1672 return mState;
1673 }
1674
AddRefCacheId(CacheId aCacheId)1675 void Manager::AddRefCacheId(CacheId aCacheId) {
1676 NS_ASSERT_OWNINGTHREAD(Manager);
1677
1678 const auto end = mCacheIdRefs.end();
1679 const auto foundIt =
1680 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
1681 if (foundIt != end) {
1682 foundIt->mCount += 1;
1683 return;
1684 }
1685
1686 mCacheIdRefs.AppendElement(CacheIdRefCounter{aCacheId, 1, false});
1687 }
1688
ReleaseCacheId(CacheId aCacheId)1689 void Manager::ReleaseCacheId(CacheId aCacheId) {
1690 NS_ASSERT_OWNINGTHREAD(Manager);
1691
1692 const auto end = mCacheIdRefs.end();
1693 const auto foundIt =
1694 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
1695 if (foundIt != end) {
1696 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1697 const uint32_t oldRef = foundIt->mCount;
1698 #endif
1699 foundIt->mCount -= 1;
1700 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
1701 if (foundIt->mCount == 0) {
1702 const bool orphaned = foundIt->mOrphaned;
1703 mCacheIdRefs.RemoveElementAt(foundIt);
1704 const auto pinnedContext =
1705 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1706 // If the context is already gone, then orphan flag should have been
1707 // set in RemoveContext().
1708 if (orphaned && pinnedContext) {
1709 if (pinnedContext->IsCanceled()) {
1710 pinnedContext->NoteOrphanedData();
1711 } else {
1712 pinnedContext->CancelForCacheId(aCacheId);
1713 pinnedContext->Dispatch(MakeSafeRefPtr<DeleteOrphanedCacheAction>(
1714 SafeRefPtrFromThis(), aCacheId));
1715 }
1716 }
1717 }
1718 MaybeAllowContextToClose();
1719 return;
1720 }
1721
1722 MOZ_ASSERT_UNREACHABLE("Attempt to release CacheId that is not referenced!");
1723 }
1724
AddRefBodyId(const nsID & aBodyId)1725 void Manager::AddRefBodyId(const nsID& aBodyId) {
1726 NS_ASSERT_OWNINGTHREAD(Manager);
1727
1728 const auto end = mBodyIdRefs.end();
1729 const auto foundIt =
1730 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
1731 if (foundIt != end) {
1732 foundIt->mCount += 1;
1733 return;
1734 }
1735
1736 mBodyIdRefs.AppendElement(BodyIdRefCounter{aBodyId, 1, false});
1737 }
1738
ReleaseBodyId(const nsID & aBodyId)1739 void Manager::ReleaseBodyId(const nsID& aBodyId) {
1740 NS_ASSERT_OWNINGTHREAD(Manager);
1741
1742 const auto end = mBodyIdRefs.end();
1743 const auto foundIt =
1744 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
1745 if (foundIt != end) {
1746 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
1747 const uint32_t oldRef = foundIt->mCount;
1748 #endif
1749 foundIt->mCount -= 1;
1750 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount < oldRef);
1751 if (foundIt->mCount < 1) {
1752 const bool orphaned = foundIt->mOrphaned;
1753 mBodyIdRefs.RemoveElementAt(foundIt);
1754 const auto pinnedContext =
1755 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1756 // If the context is already gone, then orphan flag should have been
1757 // set in RemoveContext().
1758 if (orphaned && pinnedContext) {
1759 if (pinnedContext->IsCanceled()) {
1760 pinnedContext->NoteOrphanedData();
1761 } else {
1762 pinnedContext->Dispatch(
1763 MakeSafeRefPtr<DeleteOrphanedBodyAction>(aBodyId));
1764 }
1765 }
1766 }
1767 MaybeAllowContextToClose();
1768 return;
1769 }
1770
1771 MOZ_ASSERT_UNREACHABLE("Attempt to release BodyId that is not referenced!");
1772 }
1773
GetManagerId() const1774 const ManagerId& Manager::GetManagerId() const { return *mManagerId; }
1775
AddStreamList(StreamList & aStreamList)1776 void Manager::AddStreamList(StreamList& aStreamList) {
1777 NS_ASSERT_OWNINGTHREAD(Manager);
1778 mStreamLists.AppendElement(WrapNotNullUnchecked(&aStreamList));
1779 }
1780
RemoveStreamList(StreamList & aStreamList)1781 void Manager::RemoveStreamList(StreamList& aStreamList) {
1782 NS_ASSERT_OWNINGTHREAD(Manager);
1783 mStreamLists.RemoveElement(&aStreamList);
1784 }
1785
ExecuteCacheOp(Listener * aListener,CacheId aCacheId,const CacheOpArgs & aOpArgs)1786 void Manager::ExecuteCacheOp(Listener* aListener, CacheId aCacheId,
1787 const CacheOpArgs& aOpArgs) {
1788 NS_ASSERT_OWNINGTHREAD(Manager);
1789 MOZ_DIAGNOSTIC_ASSERT(aListener);
1790 MOZ_DIAGNOSTIC_ASSERT(aOpArgs.type() != CacheOpArgs::TCachePutAllArgs);
1791
1792 if (NS_WARN_IF(mState == Closing)) {
1793 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
1794 return;
1795 }
1796
1797 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1798 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1799
1800 auto action = [this, aListener, aCacheId, &aOpArgs,
1801 &pinnedContext]() -> SafeRefPtr<Action> {
1802 const ListenerId listenerId = SaveListener(aListener);
1803
1804 if (CacheOpArgs::TCacheDeleteArgs == aOpArgs.type()) {
1805 return MakeSafeRefPtr<CacheDeleteAction>(SafeRefPtrFromThis(), listenerId,
1806 aCacheId,
1807 aOpArgs.get_CacheDeleteArgs());
1808 }
1809
1810 auto streamList = MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
1811 pinnedContext.clonePtr());
1812
1813 switch (aOpArgs.type()) {
1814 case CacheOpArgs::TCacheMatchArgs:
1815 return MakeSafeRefPtr<CacheMatchAction>(
1816 SafeRefPtrFromThis(), listenerId, aCacheId,
1817 aOpArgs.get_CacheMatchArgs(), std::move(streamList));
1818 case CacheOpArgs::TCacheMatchAllArgs:
1819 return MakeSafeRefPtr<CacheMatchAllAction>(
1820 SafeRefPtrFromThis(), listenerId, aCacheId,
1821 aOpArgs.get_CacheMatchAllArgs(), std::move(streamList));
1822 case CacheOpArgs::TCacheKeysArgs:
1823 return MakeSafeRefPtr<CacheKeysAction>(
1824 SafeRefPtrFromThis(), listenerId, aCacheId,
1825 aOpArgs.get_CacheKeysArgs(), std::move(streamList));
1826 default:
1827 MOZ_CRASH("Unknown Cache operation!");
1828 }
1829 }();
1830
1831 pinnedContext->Dispatch(std::move(action));
1832 }
1833
ExecuteStorageOp(Listener * aListener,Namespace aNamespace,const CacheOpArgs & aOpArgs)1834 void Manager::ExecuteStorageOp(Listener* aListener, Namespace aNamespace,
1835 const CacheOpArgs& aOpArgs) {
1836 NS_ASSERT_OWNINGTHREAD(Manager);
1837 MOZ_DIAGNOSTIC_ASSERT(aListener);
1838
1839 if (NS_WARN_IF(mState == Closing)) {
1840 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), void_t());
1841 return;
1842 }
1843
1844 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1845 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1846
1847 auto action = [this, aListener, aNamespace, &aOpArgs,
1848 &pinnedContext]() -> SafeRefPtr<Action> {
1849 const ListenerId listenerId = SaveListener(aListener);
1850
1851 switch (aOpArgs.type()) {
1852 case CacheOpArgs::TStorageMatchArgs:
1853 return MakeSafeRefPtr<StorageMatchAction>(
1854 SafeRefPtrFromThis(), listenerId, aNamespace,
1855 aOpArgs.get_StorageMatchArgs(),
1856 MakeSafeRefPtr<StreamList>(SafeRefPtrFromThis(),
1857 pinnedContext.clonePtr()));
1858 case CacheOpArgs::TStorageHasArgs:
1859 return MakeSafeRefPtr<StorageHasAction>(SafeRefPtrFromThis(),
1860 listenerId, aNamespace,
1861 aOpArgs.get_StorageHasArgs());
1862 case CacheOpArgs::TStorageOpenArgs:
1863 return MakeSafeRefPtr<StorageOpenAction>(SafeRefPtrFromThis(),
1864 listenerId, aNamespace,
1865 aOpArgs.get_StorageOpenArgs());
1866 case CacheOpArgs::TStorageDeleteArgs:
1867 return MakeSafeRefPtr<StorageDeleteAction>(
1868 SafeRefPtrFromThis(), listenerId, aNamespace,
1869 aOpArgs.get_StorageDeleteArgs());
1870 case CacheOpArgs::TStorageKeysArgs:
1871 return MakeSafeRefPtr<StorageKeysAction>(SafeRefPtrFromThis(),
1872 listenerId, aNamespace);
1873 default:
1874 MOZ_CRASH("Unknown CacheStorage operation!");
1875 }
1876 }();
1877
1878 pinnedContext->Dispatch(std::move(action));
1879 }
1880
ExecuteOpenStream(Listener * aListener,InputStreamResolver && aResolver,const nsID & aBodyId)1881 void Manager::ExecuteOpenStream(Listener* aListener,
1882 InputStreamResolver&& aResolver,
1883 const nsID& aBodyId) {
1884 NS_ASSERT_OWNINGTHREAD(Manager);
1885 MOZ_DIAGNOSTIC_ASSERT(aListener);
1886 MOZ_DIAGNOSTIC_ASSERT(aResolver);
1887
1888 if (NS_WARN_IF(mState == Closing)) {
1889 aResolver(nullptr);
1890 return;
1891 }
1892
1893 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1894 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1895
1896 // We save the listener simply to track the existence of the caller here.
1897 // Our returned value will really be passed to the resolver when the
1898 // operation completes. In the future we should remove the Listener
1899 // mechanism in favor of std::function or MozPromise.
1900 ListenerId listenerId = SaveListener(aListener);
1901
1902 pinnedContext->Dispatch(MakeSafeRefPtr<OpenStreamAction>(
1903 SafeRefPtrFromThis(), listenerId, std::move(aResolver), aBodyId));
1904 }
1905
ExecutePutAll(Listener * aListener,CacheId aCacheId,const nsTArray<CacheRequestResponse> & aPutList,const nsTArray<nsCOMPtr<nsIInputStream>> & aRequestStreamList,const nsTArray<nsCOMPtr<nsIInputStream>> & aResponseStreamList)1906 void Manager::ExecutePutAll(
1907 Listener* aListener, CacheId aCacheId,
1908 const nsTArray<CacheRequestResponse>& aPutList,
1909 const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
1910 const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList) {
1911 NS_ASSERT_OWNINGTHREAD(Manager);
1912 MOZ_DIAGNOSTIC_ASSERT(aListener);
1913
1914 if (NS_WARN_IF(mState == Closing)) {
1915 aListener->OnOpComplete(ErrorResult(NS_ERROR_FAILURE), CachePutAllResult());
1916 return;
1917 }
1918
1919 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1920 MOZ_DIAGNOSTIC_ASSERT(!pinnedContext->IsCanceled());
1921
1922 ListenerId listenerId = SaveListener(aListener);
1923 pinnedContext->Dispatch(MakeSafeRefPtr<CachePutAllAction>(
1924 SafeRefPtrFromThis(), listenerId, aCacheId, aPutList, aRequestStreamList,
1925 aResponseStreamList));
1926 }
1927
Manager(SafeRefPtr<ManagerId> aManagerId,nsIThread * aIOThread,const ConstructorGuard &)1928 Manager::Manager(SafeRefPtr<ManagerId> aManagerId, nsIThread* aIOThread,
1929 const ConstructorGuard&)
1930 : mManagerId(std::move(aManagerId)),
1931 mIOThread(aIOThread),
1932 mContext(nullptr),
1933 mShuttingDown(false),
1934 mState(Open) {
1935 MOZ_DIAGNOSTIC_ASSERT(mManagerId);
1936 MOZ_DIAGNOSTIC_ASSERT(mIOThread);
1937 }
1938
~Manager()1939 Manager::~Manager() {
1940 NS_ASSERT_OWNINGTHREAD(Manager);
1941 MOZ_DIAGNOSTIC_ASSERT(mState == Closing);
1942 MOZ_DIAGNOSTIC_ASSERT(!mContext);
1943
1944 nsCOMPtr<nsIThread> ioThread;
1945 mIOThread.swap(ioThread);
1946
1947 // Don't spin the event loop in the destructor waiting for the thread to
1948 // shutdown. Defer this to the main thread, instead.
1949 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewRunnableMethod(
1950 "nsIThread::AsyncShutdown", ioThread, &nsIThread::AsyncShutdown)));
1951 }
1952
Init(Maybe<Manager &> aOldManager)1953 void Manager::Init(Maybe<Manager&> aOldManager) {
1954 NS_ASSERT_OWNINGTHREAD(Manager);
1955
1956 // Create the context immediately. Since there can at most be one Context
1957 // per Manager now, this lets us cleanly call Factory::Remove() once the
1958 // Context goes away.
1959 SafeRefPtr<Context> ref = Context::Create(
1960 SafeRefPtrFromThis(), mIOThread->SerialEventTarget(),
1961 MakeSafeRefPtr<SetupAction>(),
1962 aOldManager ? SomeRef(*aOldManager->mContext) : Nothing());
1963 mContext = ref.unsafeGetRawPtr();
1964 }
1965
Shutdown()1966 void Manager::Shutdown() {
1967 NS_ASSERT_OWNINGTHREAD(Manager);
1968
1969 // Ignore duplicate attempts to shutdown. This can occur when we start
1970 // a browser initiated shutdown and then run ~Manager() which also
1971 // calls Shutdown().
1972 if (mShuttingDown) {
1973 return;
1974 }
1975
1976 mShuttingDown = true;
1977
1978 // Note that we are closing to prevent any new requests from coming in and
1979 // creating a new Context. We must ensure all Contexts and IO operations are
1980 // complete before shutdown proceeds.
1981 NoteClosing();
1982
1983 // If there is a context, then cancel and only note that we are done after
1984 // its cleaned up.
1985 if (mContext) {
1986 const auto pinnedContext =
1987 SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
1988 pinnedContext->CancelAll();
1989 return;
1990 }
1991 }
1992
MaybeDirectoryLockRef() const1993 Maybe<DirectoryLock&> Manager::MaybeDirectoryLockRef() const {
1994 NS_ASSERT_OWNINGTHREAD(Manager);
1995 MOZ_DIAGNOSTIC_ASSERT(mContext);
1996
1997 return mContext->MaybeDirectoryLockRef();
1998 }
1999
Abort()2000 void Manager::Abort() {
2001 NS_ASSERT_OWNINGTHREAD(Manager);
2002 MOZ_DIAGNOSTIC_ASSERT(mContext);
2003
2004 // Note that we are closing to prevent any new requests from coming in and
2005 // creating a new Context. We must ensure all Contexts and IO operations are
2006 // complete before origin clear proceeds.
2007 NoteClosing();
2008
2009 // Cancel and only note that we are done after the context is cleaned up.
2010 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2011 pinnedContext->CancelAll();
2012 }
2013
SaveListener(Listener * aListener)2014 Manager::ListenerId Manager::SaveListener(Listener* aListener) {
2015 NS_ASSERT_OWNINGTHREAD(Manager);
2016
2017 // Once a Listener is added, we keep a reference to it until its
2018 // removed. Since the same Listener might make multiple requests,
2019 // ensure we only have a single reference in our list.
2020 ListenerList::index_type index =
2021 mListeners.IndexOf(aListener, 0, ListenerEntryListenerComparator());
2022 if (index != ListenerList::NoIndex) {
2023 return mListeners[index].mId;
2024 }
2025
2026 ListenerId id = sNextListenerId;
2027 sNextListenerId += 1;
2028
2029 mListeners.AppendElement(ListenerEntry(id, aListener));
2030 return id;
2031 }
2032
GetListener(ListenerId aListenerId) const2033 Manager::Listener* Manager::GetListener(ListenerId aListenerId) const {
2034 NS_ASSERT_OWNINGTHREAD(Manager);
2035 ListenerList::index_type index =
2036 mListeners.IndexOf(aListenerId, 0, ListenerEntryIdComparator());
2037 if (index != ListenerList::NoIndex) {
2038 return mListeners[index].mListener;
2039 }
2040
2041 // This can legitimately happen if the actor is deleted while a request is
2042 // in process. For example, the child process OOMs.
2043 return nullptr;
2044 }
2045
SetCacheIdOrphanedIfRefed(CacheId aCacheId)2046 bool Manager::SetCacheIdOrphanedIfRefed(CacheId aCacheId) {
2047 NS_ASSERT_OWNINGTHREAD(Manager);
2048
2049 const auto end = mCacheIdRefs.end();
2050 const auto foundIt =
2051 std::find_if(mCacheIdRefs.begin(), end, MatchByCacheId(aCacheId));
2052 if (foundIt != end) {
2053 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
2054 MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
2055 foundIt->mOrphaned = true;
2056 return true;
2057 }
2058
2059 return false;
2060 }
2061
2062 // TODO: provide way to set body non-orphaned if its added back to a cache (bug
2063 // 1110479)
2064
SetBodyIdOrphanedIfRefed(const nsID & aBodyId)2065 bool Manager::SetBodyIdOrphanedIfRefed(const nsID& aBodyId) {
2066 NS_ASSERT_OWNINGTHREAD(Manager);
2067
2068 const auto end = mBodyIdRefs.end();
2069 const auto foundIt =
2070 std::find_if(mBodyIdRefs.begin(), end, MatchByBodyId(aBodyId));
2071 if (foundIt != end) {
2072 MOZ_DIAGNOSTIC_ASSERT(foundIt->mCount > 0);
2073 MOZ_DIAGNOSTIC_ASSERT(!foundIt->mOrphaned);
2074 foundIt->mOrphaned = true;
2075 return true;
2076 }
2077
2078 return false;
2079 }
2080
NoteOrphanedBodyIdList(const nsTArray<nsID> & aDeletedBodyIdList)2081 void Manager::NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList) {
2082 NS_ASSERT_OWNINGTHREAD(Manager);
2083
2084 // XXX TransformIfIntoNewArray might be generalized to allow specifying the
2085 // type of nsTArray to create, so that it can create an AutoTArray as well; an
2086 // TransformIf (without AbortOnErr) might be added, which could be used here.
2087 DeleteOrphanedBodyAction::DeletedBodyIdList deleteNowList;
2088 deleteNowList.SetCapacity(aDeletedBodyIdList.Length());
2089
2090 std::copy_if(aDeletedBodyIdList.cbegin(), aDeletedBodyIdList.cend(),
2091 MakeBackInserter(deleteNowList),
2092 [this](const auto& deletedBodyId) {
2093 return !SetBodyIdOrphanedIfRefed(deletedBodyId);
2094 });
2095
2096 // TODO: note that we need to check these bodies for staleness on startup (bug
2097 // 1110446)
2098 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2099 if (!deleteNowList.IsEmpty() && pinnedContext &&
2100 !pinnedContext->IsCanceled()) {
2101 pinnedContext->Dispatch(
2102 MakeSafeRefPtr<DeleteOrphanedBodyAction>(std::move(deleteNowList)));
2103 }
2104 }
2105
MaybeAllowContextToClose()2106 void Manager::MaybeAllowContextToClose() {
2107 NS_ASSERT_OWNINGTHREAD(Manager);
2108
2109 // If we have an active context, but we have no more users of the Manager,
2110 // then let it shut itself down. We must wait for all possible users of
2111 // Cache state information to complete before doing this. Once we allow
2112 // the Context to close we may not reliably get notified of storage
2113 // invalidation.
2114 const auto pinnedContext = SafeRefPtr{mContext, AcquireStrongRefFromRawPtr{}};
2115 if (pinnedContext && mListeners.IsEmpty() && mCacheIdRefs.IsEmpty() &&
2116 mBodyIdRefs.IsEmpty()) {
2117 // Mark this Manager as invalid so that it won't get used again. We don't
2118 // want to start any new operations once we allow the Context to close since
2119 // it may race with the underlying storage getting invalidated.
2120 NoteClosing();
2121
2122 pinnedContext->AllowToClose();
2123 }
2124 }
2125
2126 } // namespace mozilla::dom::cache
2127