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/Context.h"
8
9
#include "mozilla/AutoRestore.h"
10
#include "mozilla/dom/cache/Action.h"
11
#include "mozilla/dom/cache/FileUtils.h"
12
#include "mozilla/dom/cache/Manager.h"
13
#include "mozilla/dom/cache/ManagerId.h"
14
#include "mozilla/dom/quota/QuotaManager.h"
15
#include "mozIStorageConnection.h"
16
#include "nsIPrincipal.h"
17
#include "nsIRunnable.h"
18
#include "nsThreadUtils.h"
19
20
namespace {
21
22
using mozilla::dom::cache::Action;
23
using mozilla::dom::cache::QuotaInfo;
24
25
class NullAction final : public Action {
26
public:
27
NullAction() {}
28
29
virtual void RunOnTarget(Resolver* aResolver, const QuotaInfo&,
30
Data*) override {
31
// Resolve success immediately. This Action does no actual work.
32
MOZ_DIAGNOSTIC_ASSERT(aResolver);
33
aResolver->Resolve(NS_OK);
34
}
35
};
36
37
} // namespace
38
39
namespace mozilla {
40
namespace dom {
41
namespace cache {
42
43
using mozilla::dom::quota::AssertIsOnIOThread;
44
using mozilla::dom::quota::OpenDirectoryListener;
45
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
46
using mozilla::dom::quota::PersistenceType;
47
using mozilla::dom::quota::QuotaManager;
48
49
class Context::Data final : public Action::Data {
50
public:
51
explicit Data(nsISerialEventTarget* aTarget) : mTarget(aTarget) {
52
MOZ_DIAGNOSTIC_ASSERT(mTarget);
53
}
54
55
virtual mozIStorageConnection* GetConnection() const override {
56
MOZ_ASSERT(mTarget->IsOnCurrentThread());
57
return mConnection;
58
}
59
60
virtual void SetConnection(mozIStorageConnection* aConn) override {
61
MOZ_ASSERT(mTarget->IsOnCurrentThread());
62
MOZ_DIAGNOSTIC_ASSERT(!mConnection);
63
mConnection = aConn;
64
MOZ_DIAGNOSTIC_ASSERT(mConnection);
65
}
66
67
private:
68
~Data() {
69
// We could proxy release our data here, but instead just assert. The
70
// Context code should guarantee that we are destroyed on the target
71
// thread once the connection is initialized. If we're not, then
72
// QuotaManager might race and try to clear the origin out from under us.
73
MOZ_ASSERT_IF(mConnection, mTarget->IsOnCurrentThread());
74
}
75
76
nsCOMPtr<nsISerialEventTarget> mTarget;
77
nsCOMPtr<mozIStorageConnection> mConnection;
78
79
// Threadsafe counting because we're created on the PBackground thread
80
// and destroyed on the target IO thread.
81
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Context::Data)
82
};
83
84
// Executed to perform the complicated dance of steps necessary to initialize
85
// the QuotaManager. This must be performed for each origin before any disk
86
// IO occurrs.
87
class Context::QuotaInitRunnable final : public nsIRunnable,
88
public OpenDirectoryListener {
89
public:
90
QuotaInitRunnable(Context* aContext, Manager* aManager, Data* aData,
91
nsISerialEventTarget* aTarget, Action* aInitAction)
92
: mContext(aContext),
93
mThreadsafeHandle(aContext->CreateThreadsafeHandle()),
94
mManager(aManager),
95
mData(aData),
96
mTarget(aTarget),
97
mInitAction(aInitAction),
98
mInitiatingEventTarget(GetCurrentThreadEventTarget()),
99
mResult(NS_OK),
100
mState(STATE_INIT),
101
mCanceled(false) {
102
MOZ_DIAGNOSTIC_ASSERT(mContext);
103
MOZ_DIAGNOSTIC_ASSERT(mManager);
104
MOZ_DIAGNOSTIC_ASSERT(mData);
105
MOZ_DIAGNOSTIC_ASSERT(mTarget);
106
MOZ_DIAGNOSTIC_ASSERT(mInitiatingEventTarget);
107
MOZ_DIAGNOSTIC_ASSERT(mInitAction);
108
}
109
110
nsresult Dispatch() {
111
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
112
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_INIT);
113
114
mState = STATE_GET_INFO;
115
nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
116
if (NS_WARN_IF(NS_FAILED(rv))) {
117
mState = STATE_COMPLETE;
118
Clear();
119
}
120
return rv;
121
}
122
123
void Cancel() {
124
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
125
MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
126
mCanceled = true;
127
mInitAction->CancelOnInitiatingThread();
128
}
129
130
void OpenDirectory();
131
132
// OpenDirectoryListener methods
133
virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
134
135
virtual void DirectoryLockFailed() override;
136
137
private:
138
class SyncResolver final : public Action::Resolver {
139
public:
140
SyncResolver() : mResolved(false), mResult(NS_OK) {}
141
142
virtual void Resolve(nsresult aRv) override {
143
MOZ_DIAGNOSTIC_ASSERT(!mResolved);
144
mResolved = true;
145
mResult = aRv;
146
};
147
148
bool Resolved() const { return mResolved; }
149
nsresult Result() const { return mResult; }
150
151
private:
152
~SyncResolver() {}
153
154
bool mResolved;
155
nsresult mResult;
156
157
NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver,
158
override)
159
};
160
161
~QuotaInitRunnable() {
162
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE);
163
MOZ_DIAGNOSTIC_ASSERT(!mContext);
164
MOZ_DIAGNOSTIC_ASSERT(!mInitAction);
165
}
166
167
enum State {
168
STATE_INIT,
169
STATE_GET_INFO,
170
STATE_CREATE_QUOTA_MANAGER,
171
STATE_OPEN_DIRECTORY,
172
STATE_WAIT_FOR_DIRECTORY_LOCK,
173
STATE_ENSURE_ORIGIN_INITIALIZED,
174
STATE_RUN_ON_TARGET,
175
STATE_RUNNING,
176
STATE_COMPLETING,
177
STATE_COMPLETE
178
};
179
180
void Complete(nsresult aResult) {
181
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING || NS_FAILED(aResult));
182
183
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mResult));
184
mResult = aResult;
185
186
mState = STATE_COMPLETING;
187
MOZ_ALWAYS_SUCCEEDS(
188
mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
189
}
190
191
void Clear() {
192
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
193
MOZ_DIAGNOSTIC_ASSERT(mContext);
194
mContext = nullptr;
195
mManager = nullptr;
196
mInitAction = nullptr;
197
}
198
199
RefPtr<Context> mContext;
200
RefPtr<ThreadsafeHandle> mThreadsafeHandle;
201
RefPtr<Manager> mManager;
202
RefPtr<Data> mData;
203
nsCOMPtr<nsISerialEventTarget> mTarget;
204
RefPtr<Action> mInitAction;
205
nsCOMPtr<nsIEventTarget> mInitiatingEventTarget;
206
nsresult mResult;
207
QuotaInfo mQuotaInfo;
208
RefPtr<DirectoryLock> mDirectoryLock;
209
State mState;
210
Atomic<bool> mCanceled;
211
212
public:
213
NS_DECL_THREADSAFE_ISUPPORTS
214
NS_DECL_NSIRUNNABLE
215
};
216
217
void Context::QuotaInitRunnable::OpenDirectory() {
218
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
219
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CREATE_QUOTA_MANAGER ||
220
mState == STATE_OPEN_DIRECTORY);
221
MOZ_DIAGNOSTIC_ASSERT(QuotaManager::Get());
222
223
// QuotaManager::OpenDirectory() will hold a reference to us as
224
// a listener. We will then get DirectoryLockAcquired() on the owning
225
// thread when it is safe to access our storage directory.
226
mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
227
QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
228
mQuotaInfo.mGroup, mQuotaInfo.mOrigin,
229
quota::Client::DOMCACHE,
230
/* aExclusive */ false, this);
231
}
232
233
void Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock* aLock) {
234
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
235
MOZ_DIAGNOSTIC_ASSERT(aLock);
236
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
237
MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
238
239
mDirectoryLock = aLock;
240
241
MOZ_DIAGNOSTIC_ASSERT(mDirectoryLock->Id() >= 0);
242
mQuotaInfo.mDirectoryLockId = mDirectoryLock->Id();
243
244
if (mCanceled) {
245
Complete(NS_ERROR_ABORT);
246
return;
247
}
248
249
QuotaManager* qm = QuotaManager::Get();
250
MOZ_DIAGNOSTIC_ASSERT(qm);
251
252
mState = STATE_ENSURE_ORIGIN_INITIALIZED;
253
nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
254
if (NS_WARN_IF(NS_FAILED(rv))) {
255
Complete(rv);
256
return;
257
}
258
}
259
260
void Context::QuotaInitRunnable::DirectoryLockFailed() {
261
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
262
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
263
MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
264
265
NS_WARNING("Failed to acquire a directory lock!");
266
267
Complete(NS_ERROR_FAILURE);
268
}
269
270
NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable);
271
272
// The QuotaManager init state machine is represented in the following diagram:
273
//
274
// +---------------+
275
// | Start | Resolve(error)
276
// | (Orig Thread) +---------------------+
277
// +-------+-------+ |
278
// | |
279
// +----------v-----------+ |
280
// | GetInfo | Resolve(error) |
281
// | (Main Thread) +-----------------+
282
// +----------+-----------+ |
283
// | |
284
// +----------v-----------+ |
285
// | CreateQuotaManager | Resolve(error) |
286
// | (Orig Thread) +-----------------+
287
// +----------+-----------+ |
288
// | |
289
// +----------v-----------+ |
290
// | OpenDirectory | Resolve(error) |
291
// | (Orig Thread) +-----------------+
292
// +----------+-----------+ |
293
// | |
294
// +----------v-----------+ |
295
// | WaitForDirectoryLock | Resolve(error) |
296
// | (Orig Thread) +-----------------+
297
// +----------+-----------+ |
298
// | |
299
// +----------v------------+ |
300
// |EnsureOriginInitialized| Resolve(error) |
301
// | (Quota IO Thread) +----------------+
302
// +----------+------------+ |
303
// | |
304
// +----------v------------+ |
305
// | RunOnTarget | Resolve(error) |
306
// | (Target Thread) +----------------+
307
// +----------+------------+ |
308
// | |
309
// +---------v---------+ +------v------+
310
// | Running | | Completing |
311
// | (Target Thread) +------------>(Orig Thread)|
312
// +-------------------+ +------+------+
313
// |
314
// +-----v----+
315
// | Complete |
316
// +----------+
317
//
318
// The initialization process proceeds through the main states. If an error
319
// occurs, then we transition to Completing state back on the original thread.
320
NS_IMETHODIMP
321
Context::QuotaInitRunnable::Run() {
322
// May run on different threads depending on the state. See individual
323
// state cases for thread assertions.
324
325
RefPtr<SyncResolver> resolver = new SyncResolver();
326
327
switch (mState) {
328
// -----------------------------------
329
case STATE_GET_INFO: {
330
MOZ_ASSERT(NS_IsMainThread());
331
332
if (mCanceled) {
333
resolver->Resolve(NS_ERROR_ABORT);
334
break;
335
}
336
337
RefPtr<ManagerId> managerId = mManager->GetManagerId();
338
nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
339
nsresult rv = QuotaManager::GetInfoFromPrincipal(
340
principal, &mQuotaInfo.mSuffix, &mQuotaInfo.mGroup,
341
&mQuotaInfo.mOrigin);
342
if (NS_WARN_IF(NS_FAILED(rv))) {
343
resolver->Resolve(rv);
344
break;
345
}
346
347
mState = STATE_CREATE_QUOTA_MANAGER;
348
MOZ_ALWAYS_SUCCEEDS(
349
mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
350
break;
351
}
352
// ----------------------------------
353
case STATE_CREATE_QUOTA_MANAGER: {
354
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
355
356
if (mCanceled || QuotaManager::IsShuttingDown()) {
357
resolver->Resolve(NS_ERROR_ABORT);
358
break;
359
}
360
361
if (QuotaManager::Get()) {
362
OpenDirectory();
363
return NS_OK;
364
}
365
366
mState = STATE_OPEN_DIRECTORY;
367
QuotaManager::GetOrCreate(this);
368
break;
369
}
370
// ----------------------------------
371
case STATE_OPEN_DIRECTORY: {
372
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
373
374
if (NS_WARN_IF(!QuotaManager::Get())) {
375
resolver->Resolve(NS_ERROR_FAILURE);
376
break;
377
}
378
379
OpenDirectory();
380
break;
381
}
382
// ----------------------------------
383
case STATE_ENSURE_ORIGIN_INITIALIZED: {
384
AssertIsOnIOThread();
385
386
if (mCanceled) {
387
resolver->Resolve(NS_ERROR_ABORT);
388
break;
389
}
390
391
QuotaManager* qm = QuotaManager::Get();
392
MOZ_DIAGNOSTIC_ASSERT(qm);
393
nsresult rv = qm->EnsureStorageAndOriginIsInitialized(
394
PERSISTENCE_TYPE_DEFAULT, mQuotaInfo.mSuffix, mQuotaInfo.mGroup,
395
mQuotaInfo.mOrigin, quota::Client::DOMCACHE,
396
getter_AddRefs(mQuotaInfo.mDir));
397
if (NS_FAILED(rv)) {
398
resolver->Resolve(rv);
399
break;
400
}
401
402
mState = STATE_RUN_ON_TARGET;
403
404
MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
405
break;
406
}
407
// -------------------
408
case STATE_RUN_ON_TARGET: {
409
MOZ_ASSERT(mTarget->IsOnCurrentThread());
410
411
mState = STATE_RUNNING;
412
413
// Execute the provided initialization Action. The Action must Resolve()
414
// before returning.
415
mInitAction->RunOnTarget(resolver, mQuotaInfo, mData);
416
MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved());
417
418
mData = nullptr;
419
420
// If the database was opened, then we should always succeed when creating
421
// the marker file. If it wasn't opened successfully, then no need to
422
// create a marker file anyway.
423
if (NS_SUCCEEDED(resolver->Result())) {
424
MOZ_ALWAYS_SUCCEEDS(CreateMarkerFile(mQuotaInfo));
425
}
426
427
break;
428
}
429
// -------------------
430
case STATE_COMPLETING: {
431
NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
432
mInitAction->CompleteOnInitiatingThread(mResult);
433
mContext->OnQuotaInit(mResult, mQuotaInfo, mDirectoryLock.forget());
434
mState = STATE_COMPLETE;
435
436
// Explicitly cleanup here as the destructor could fire on any of
437
// the threads we have bounced through.
438
Clear();
439
break;
440
}
441
// -----
442
case STATE_WAIT_FOR_DIRECTORY_LOCK:
443
default: {
444
MOZ_CRASH("unexpected state in QuotaInitRunnable");
445
}
446
}
447
448
if (resolver->Resolved()) {
449
Complete(resolver->Result());
450
}
451
452
return NS_OK;
453
}
454
455
// Runnable wrapper around Action objects dispatched on the Context. This
456
// runnable executes the Action on the appropriate threads while the Context
457
// is initialized.
458
class Context::ActionRunnable final : public nsIRunnable,
459
public Action::Resolver,
460
public Context::Activity {
461
public:
462
ActionRunnable(Context* aContext, Data* aData, nsISerialEventTarget* aTarget,
463
Action* aAction, const QuotaInfo& aQuotaInfo)
464
: mContext(aContext),
465
mData(aData),
466
mTarget(aTarget),
467
mAction(aAction),
468
mQuotaInfo(aQuotaInfo),
469
mInitiatingThread(GetCurrentThreadEventTarget()),
470
mState(STATE_INIT),
471
mResult(NS_OK),
472
mExecutingRunOnTarget(false) {
473
MOZ_DIAGNOSTIC_ASSERT(mContext);
474
// mData may be nullptr
475
MOZ_DIAGNOSTIC_ASSERT(mTarget);
476
MOZ_DIAGNOSTIC_ASSERT(mAction);
477
// mQuotaInfo.mDir may be nullptr if QuotaInitRunnable failed
478
MOZ_DIAGNOSTIC_ASSERT(mInitiatingThread);
479
}
480
481
nsresult Dispatch() {
482
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
483
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_INIT);
484
485
mState = STATE_RUN_ON_TARGET;
486
nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
487
if (NS_WARN_IF(NS_FAILED(rv))) {
488
mState = STATE_COMPLETE;
489
Clear();
490
}
491
return rv;
492
}
493
494
virtual bool MatchesCacheId(CacheId aCacheId) const override {
495
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
496
return mAction->MatchesCacheId(aCacheId);
497
}
498
499
virtual void Cancel() override {
500
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
501
mAction->CancelOnInitiatingThread();
502
}
503
504
virtual void Resolve(nsresult aRv) override {
505
MOZ_ASSERT(mTarget->IsOnCurrentThread());
506
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING);
507
508
mResult = aRv;
509
510
// We ultimately must complete on the initiating thread, but bounce through
511
// the current thread again to ensure that we don't destroy objects and
512
// state out from under the currently running action's stack.
513
mState = STATE_RESOLVING;
514
515
// If we were resolved synchronously within Action::RunOnTarget() then we
516
// can avoid a thread bounce and just resolve once RunOnTarget() returns.
517
// The Run() method will handle this by looking at mState after
518
// RunOnTarget() returns.
519
if (mExecutingRunOnTarget) {
520
return;
521
}
522
523
// Otherwise we are in an asynchronous resolve. And must perform a thread
524
// bounce to run on the target thread again.
525
MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
526
}
527
528
private:
529
~ActionRunnable() {
530
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE);
531
MOZ_DIAGNOSTIC_ASSERT(!mContext);
532
MOZ_DIAGNOSTIC_ASSERT(!mAction);
533
}
534
535
void Clear() {
536
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
537
MOZ_DIAGNOSTIC_ASSERT(mContext);
538
MOZ_DIAGNOSTIC_ASSERT(mAction);
539
mContext->RemoveActivity(this);
540
mContext = nullptr;
541
mAction = nullptr;
542
}
543
544
enum State {
545
STATE_INIT,
546
STATE_RUN_ON_TARGET,
547
STATE_RUNNING,
548
STATE_RESOLVING,
549
STATE_COMPLETING,
550
STATE_COMPLETE
551
};
552
553
RefPtr<Context> mContext;
554
RefPtr<Data> mData;
555
nsCOMPtr<nsISerialEventTarget> mTarget;
556
RefPtr<Action> mAction;
557
const QuotaInfo mQuotaInfo;
558
nsCOMPtr<nsIEventTarget> mInitiatingThread;
559
State mState;
560
nsresult mResult;
561
562
// Only accessible on target thread;
563
bool mExecutingRunOnTarget;
564
565
public:
566
NS_DECL_THREADSAFE_ISUPPORTS
567
NS_DECL_NSIRUNNABLE
568
};
569
570
NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable, nsIRunnable);
571
572
// The ActionRunnable has a simpler state machine. It basically needs to run
573
// the action on the target thread and then complete on the original thread.
574
//
575
// +-------------+
576
// | Start |
577
// |(Orig Thread)|
578
// +-----+-------+
579
// |
580
// +-------v---------+
581
// | RunOnTarget |
582
// |Target IO Thread)+---+ Resolve()
583
// +-------+---------+ |
584
// | |
585
// +-------v----------+ |
586
// | Running | |
587
// |(Target IO Thread)| |
588
// +------------------+ |
589
// | Resolve() |
590
// +-------v----------+ |
591
// | Resolving <--+ +-------------+
592
// | | | Completing |
593
// |(Target IO Thread)+---------------------->(Orig Thread)|
594
// +------------------+ +-------+-----+
595
// |
596
// |
597
// +----v---+
598
// |Complete|
599
// +--------+
600
//
601
// Its important to note that synchronous actions will effectively Resolve()
602
// out of the Running state immediately. Asynchronous Actions may remain
603
// in the Running state for some time, but normally the ActionRunnable itself
604
// does not see any execution there. Its all handled internal to the Action.
605
NS_IMETHODIMP
606
Context::ActionRunnable::Run() {
607
switch (mState) {
608
// ----------------------
609
case STATE_RUN_ON_TARGET: {
610
MOZ_ASSERT(mTarget->IsOnCurrentThread());
611
MOZ_DIAGNOSTIC_ASSERT(!mExecutingRunOnTarget);
612
613
// Note that we are calling RunOnTarget(). This lets us detect
614
// if Resolve() is called synchronously.
615
AutoRestore<bool> executingRunOnTarget(mExecutingRunOnTarget);
616
mExecutingRunOnTarget = true;
617
618
mState = STATE_RUNNING;
619
mAction->RunOnTarget(this, mQuotaInfo, mData);
620
621
mData = nullptr;
622
623
// Resolve was called synchronously from RunOnTarget(). We can
624
// immediately move to completing now since we are sure RunOnTarget()
625
// completed.
626
if (mState == STATE_RESOLVING) {
627
// Use recursion instead of switch case fall-through... Seems slightly
628
// easier to understand.
629
Run();
630
}
631
632
break;
633
}
634
// -----------------
635
case STATE_RESOLVING: {
636
MOZ_ASSERT(mTarget->IsOnCurrentThread());
637
// The call to Action::RunOnTarget() must have returned now if we
638
// are running on the target thread again. We may now proceed
639
// with completion.
640
mState = STATE_COMPLETING;
641
// Shutdown must be delayed until all Contexts are destroyed. Crash
642
// for this invariant violation.
643
MOZ_ALWAYS_SUCCEEDS(
644
mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
645
break;
646
}
647
// -------------------
648
case STATE_COMPLETING: {
649
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
650
mAction->CompleteOnInitiatingThread(mResult);
651
mState = STATE_COMPLETE;
652
// Explicitly cleanup here as the destructor could fire on any of
653
// the threads we have bounced through.
654
Clear();
655
break;
656
}
657
// -----------------
658
default: {
659
MOZ_CRASH("unexpected state in ActionRunnable");
660
break;
661
}
662
}
663
return NS_OK;
664
}
665
666
void Context::ThreadsafeHandle::AllowToClose() {
667
if (mOwningEventTarget->IsOnCurrentThread()) {
668
AllowToCloseOnOwningThread();
669
return;
670
}
671
672
// Dispatch is guaranteed to succeed here because we block shutdown until
673
// all Contexts have been destroyed.
674
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
675
"dom::cache::Context::ThreadsafeHandle::AllowToCloseOnOwningThread", this,
676
&ThreadsafeHandle::AllowToCloseOnOwningThread);
677
MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(runnable.forget(),
678
nsIThread::DISPATCH_NORMAL));
679
}
680
681
void Context::ThreadsafeHandle::InvalidateAndAllowToClose() {
682
if (mOwningEventTarget->IsOnCurrentThread()) {
683
InvalidateAndAllowToCloseOnOwningThread();
684
return;
685
}
686
687
// Dispatch is guaranteed to succeed here because we block shutdown until
688
// all Contexts have been destroyed.
689
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
690
"dom::cache::Context::ThreadsafeHandle::"
691
"InvalidateAndAllowToCloseOnOwningThread",
692
this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
693
MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(runnable.forget(),
694
nsIThread::DISPATCH_NORMAL));
695
}
696
697
Context::ThreadsafeHandle::ThreadsafeHandle(Context* aContext)
698
: mStrongRef(aContext),
699
mWeakRef(aContext),
700
mOwningEventTarget(GetCurrentThreadSerialEventTarget()) {}
701
702
Context::ThreadsafeHandle::~ThreadsafeHandle() {
703
// Normally we only touch mStrongRef on the owning thread. This is safe,
704
// however, because when we do use mStrongRef on the owning thread we are
705
// always holding a strong ref to the ThreadsafeHandle via the owning
706
// runnable. So we cannot run the ThreadsafeHandle destructor simultaneously.
707
if (!mStrongRef || mOwningEventTarget->IsOnCurrentThread()) {
708
return;
709
}
710
711
// Dispatch is guaranteed to succeed here because we block shutdown until
712
// all Contexts have been destroyed.
713
NS_ProxyRelease("Context::ThreadsafeHandle::mStrongRef", mOwningEventTarget,
714
mStrongRef.forget());
715
}
716
717
void Context::ThreadsafeHandle::AllowToCloseOnOwningThread() {
718
MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
719
720
// A Context "closes" when its ref count drops to zero. Dropping this
721
// strong ref is necessary, but not sufficient for the close to occur.
722
// Any outstanding IO will continue and keep the Context alive. Once
723
// the Context is idle, it will be destroyed.
724
725
// First, tell the context to flush any target thread shared data. This
726
// data must be released on the target thread prior to running the Context
727
// destructor. This will schedule an Action which ensures that the
728
// ~Context() is not immediately executed when we drop the strong ref.
729
if (mStrongRef) {
730
mStrongRef->DoomTargetData();
731
}
732
733
// Now drop our strong ref and let Context finish running any outstanding
734
// Actions.
735
mStrongRef = nullptr;
736
}
737
738
void Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread() {
739
MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
740
// Cancel the Context through the weak reference. This means we can
741
// allow the Context to close by dropping the strong ref, but then
742
// still cancel ongoing IO if necessary.
743
if (mWeakRef) {
744
mWeakRef->Invalidate();
745
}
746
// We should synchronously have AllowToCloseOnOwningThread called when
747
// the Context is canceled.
748
MOZ_DIAGNOSTIC_ASSERT(!mStrongRef);
749
}
750
751
void Context::ThreadsafeHandle::ContextDestroyed(Context* aContext) {
752
MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
753
MOZ_DIAGNOSTIC_ASSERT(!mStrongRef);
754
MOZ_DIAGNOSTIC_ASSERT(mWeakRef);
755
MOZ_DIAGNOSTIC_ASSERT(mWeakRef == aContext);
756
mWeakRef = nullptr;
757
}
758
759
// static
760
already_AddRefed<Context> Context::Create(Manager* aManager,
761
nsISerialEventTarget* aTarget,
762
Action* aInitAction,
763
Context* aOldContext) {
764
RefPtr<Context> context = new Context(aManager, aTarget, aInitAction);
765
context->Init(aOldContext);
766
return context.forget();
767
}
768
769
Context::Context(Manager* aManager, nsISerialEventTarget* aTarget,
770
Action* aInitAction)
771
: mManager(aManager),
772
mTarget(aTarget),
773
mData(new Data(aTarget)),
774
mState(STATE_CONTEXT_PREINIT),
775
mOrphanedData(false),
776
mInitAction(aInitAction) {
777
MOZ_DIAGNOSTIC_ASSERT(mManager);
778
MOZ_DIAGNOSTIC_ASSERT(mTarget);
779
}
780
781
void Context::Dispatch(Action* aAction) {
782
NS_ASSERT_OWNINGTHREAD(Context);
783
MOZ_DIAGNOSTIC_ASSERT(aAction);
784
785
MOZ_DIAGNOSTIC_ASSERT(mState != STATE_CONTEXT_CANCELED);
786
if (mState == STATE_CONTEXT_CANCELED) {
787
return;
788
} else if (mState == STATE_CONTEXT_INIT || mState == STATE_CONTEXT_PREINIT) {
789
PendingAction* pending = mPendingActions.AppendElement();
790
pending->mAction = aAction;
791
return;
792
}
793
794
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_READY);
795
DispatchAction(aAction);
796
}
797
798
void Context::CancelAll() {
799
NS_ASSERT_OWNINGTHREAD(Context);
800
801
// In PREINIT state we have not dispatch the init action yet. Just
802
// forget it.
803
if (mState == STATE_CONTEXT_PREINIT) {
804
MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
805
mInitAction = nullptr;
806
807
// In INIT state we have dispatched the runnable, but not received the
808
// async completion yet. Cancel the runnable, but don't forget about it
809
// until we get OnQuotaInit() callback.
810
} else if (mState == STATE_CONTEXT_INIT) {
811
mInitRunnable->Cancel();
812
}
813
814
mState = STATE_CONTEXT_CANCELED;
815
mPendingActions.Clear();
816
{
817
ActivityList::ForwardIterator iter(mActivityList);
818
while (iter.HasMore()) {
819
iter.GetNext()->Cancel();
820
}
821
}
822
AllowToClose();
823
}
824
825
bool Context::IsCanceled() const {
826
NS_ASSERT_OWNINGTHREAD(Context);
827
return mState == STATE_CONTEXT_CANCELED;
828
}
829
830
void Context::Invalidate() {
831
NS_ASSERT_OWNINGTHREAD(Context);
832
mManager->NoteClosing();
833
CancelAll();
834
}
835
836
void Context::AllowToClose() {
837
NS_ASSERT_OWNINGTHREAD(Context);
838
if (mThreadsafeHandle) {
839
mThreadsafeHandle->AllowToClose();
840
}
841
}
842
843
void Context::CancelForCacheId(CacheId aCacheId) {
844
NS_ASSERT_OWNINGTHREAD(Context);
845
846
// Remove matching pending actions
847
for (int32_t i = mPendingActions.Length() - 1; i >= 0; --i) {
848
if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) {
849
mPendingActions.RemoveElementAt(i);
850
}
851
}
852
853
// Cancel activities and let them remove themselves
854
ActivityList::ForwardIterator iter(mActivityList);
855
while (iter.HasMore()) {
856
Activity* activity = iter.GetNext();
857
if (activity->MatchesCacheId(aCacheId)) {
858
activity->Cancel();
859
}
860
}
861
}
862
863
Context::~Context() {
864
NS_ASSERT_OWNINGTHREAD(Context);
865
MOZ_DIAGNOSTIC_ASSERT(mManager);
866
MOZ_DIAGNOSTIC_ASSERT(!mData);
867
868
if (mThreadsafeHandle) {
869
mThreadsafeHandle->ContextDestroyed(this);
870
}
871
872
// Note, this may set the mOrphanedData flag.
873
mManager->RemoveContext(this);
874
875
if (mQuotaInfo.mDir && !mOrphanedData) {
876
MOZ_ALWAYS_SUCCEEDS(DeleteMarkerFile(mQuotaInfo));
877
}
878
879
if (mNextContext) {
880
mNextContext->Start();
881
}
882
}
883
884
void Context::Init(Context* aOldContext) {
885
NS_ASSERT_OWNINGTHREAD(Context);
886
887
if (aOldContext) {
888
aOldContext->SetNextContext(this);
889
return;
890
}
891
892
Start();
893
}
894
895
void Context::Start() {
896
NS_ASSERT_OWNINGTHREAD(Context);
897
898
// Previous context closing delayed our start, but then we were canceled.
899
// In this case, just do nothing here.
900
if (mState == STATE_CONTEXT_CANCELED) {
901
MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
902
MOZ_DIAGNOSTIC_ASSERT(!mInitAction);
903
// If we can't initialize the quota subsystem we will never be able to
904
// clear our shared data object via the target IO thread. Instead just
905
// clear it here to maintain the invariant that the shared data is
906
// cleared before Context destruction.
907
mData = nullptr;
908
return;
909
}
910
911
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_PREINIT);
912
MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
913
914
mInitRunnable =
915
new QuotaInitRunnable(this, mManager, mData, mTarget, mInitAction);
916
mInitAction = nullptr;
917
918
mState = STATE_CONTEXT_INIT;
919
920
nsresult rv = mInitRunnable->Dispatch();
921
if (NS_FAILED(rv)) {
922
// Shutdown must be delayed until all Contexts are destroyed. Shutdown
923
// must also prevent any new Contexts from being constructed. Crash
924
// for this invariant violation.
925
MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
926
}
927
}
928
929
void Context::DispatchAction(Action* aAction, bool aDoomData) {
930
NS_ASSERT_OWNINGTHREAD(Context);
931
932
RefPtr<ActionRunnable> runnable =
933
new ActionRunnable(this, mData, mTarget, aAction, mQuotaInfo);
934
935
if (aDoomData) {
936
mData = nullptr;
937
}
938
939
nsresult rv = runnable->Dispatch();
940
if (NS_FAILED(rv)) {
941
// Shutdown must be delayed until all Contexts are destroyed. Crash
942
// for this invariant violation.
943
MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
944
}
945
AddActivity(runnable);
946
}
947
948
void Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
949
already_AddRefed<DirectoryLock> aDirectoryLock) {
950
NS_ASSERT_OWNINGTHREAD(Context);
951
952
MOZ_DIAGNOSTIC_ASSERT(mInitRunnable);
953
mInitRunnable = nullptr;
954
955
mQuotaInfo = aQuotaInfo;
956
957
// Always save the directory lock to ensure QuotaManager does not shutdown
958
// before the Context has gone away.
959
MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
960
mDirectoryLock = aDirectoryLock;
961
962
// If we opening the context failed, but we were not explicitly canceled,
963
// still treat the entire context as canceled. We don't want to allow
964
// new actions to be dispatched. We also cannot leave the context in
965
// the INIT state after failing to open.
966
if (NS_FAILED(aRv)) {
967
mState = STATE_CONTEXT_CANCELED;
968
}
969
970
if (mState == STATE_CONTEXT_CANCELED) {
971
for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
972
mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
973
}
974
mPendingActions.Clear();
975
mThreadsafeHandle->AllowToClose();
976
// Context will destruct after return here and last ref is released.
977
return;
978
}
979
980
MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_INIT);
981
mState = STATE_CONTEXT_READY;
982
983
for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
984
DispatchAction(mPendingActions[i].mAction);
985
}
986
mPendingActions.Clear();
987
}
988
989
void Context::AddActivity(Activity* aActivity) {
990
NS_ASSERT_OWNINGTHREAD(Context);
991
MOZ_DIAGNOSTIC_ASSERT(aActivity);
992
MOZ_ASSERT(!mActivityList.Contains(aActivity));
993
mActivityList.AppendElement(aActivity);
994
}
995
996
void Context::RemoveActivity(Activity* aActivity) {
997
NS_ASSERT_OWNINGTHREAD(Context);
998
MOZ_DIAGNOSTIC_ASSERT(aActivity);
999
MOZ_ALWAYS_TRUE(mActivityList.RemoveElement(aActivity));
1000
MOZ_ASSERT(!mActivityList.Contains(aActivity));
1001
}
1002
1003
void Context::NoteOrphanedData() {
1004
NS_ASSERT_OWNINGTHREAD(Context);
1005
// This may be called more than once
1006
mOrphanedData = true;
1007
}
1008
1009
already_AddRefed<Context::ThreadsafeHandle> Context::CreateThreadsafeHandle() {
1010
NS_ASSERT_OWNINGTHREAD(Context);
1011
if (!mThreadsafeHandle) {
1012
mThreadsafeHandle = new ThreadsafeHandle(this);
1013
}
1014
RefPtr<ThreadsafeHandle> ref = mThreadsafeHandle;
1015
return ref.forget();
1016
}
1017
1018
void Context::SetNextContext(Context* aNextContext) {
1019
NS_ASSERT_OWNINGTHREAD(Context);
1020
MOZ_DIAGNOSTIC_ASSERT(aNextContext);
1021
MOZ_DIAGNOSTIC_ASSERT(!mNextContext);
1022
mNextContext = aNextContext;
1023
}
1024
1025
void Context::DoomTargetData() {
1026
NS_ASSERT_OWNINGTHREAD(Context);
1027
MOZ_DIAGNOSTIC_ASSERT(mData);
1028
1029
// We are about to drop our reference to the Data. We need to ensure that
1030
// the ~Context() destructor does not run until contents of Data have been
1031
// released on the Target thread.
1032
1033
// Dispatch a no-op Action. This will hold the Context alive through a
1034
// roundtrip to the target thread and back to the owning thread. The
1035
// ref to the Data object is cleared on the owning thread after creating
1036
// the ActionRunnable, but before dispatching it.
1037
RefPtr<Action> action = new NullAction();
1038
DispatchAction(action, true /* doomed data */);
1039
1040
MOZ_DIAGNOSTIC_ASSERT(!mData);
1041
}
1042
1043
} // namespace cache
1044
} // namespace dom
1045
} // namespace mozilla