Source code

Revision control

Other Tools

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