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