Coverage Report

Created: 2018-09-25 14:53

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