forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1066 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1066 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/dom/cache/Context.h"
 | |
| 
 | |
| #include "CacheCommon.h"
 | |
| 
 | |
| #include "mozilla/AutoRestore.h"
 | |
| #include "mozilla/dom/SafeRefPtr.h"
 | |
| #include "mozilla/dom/cache/Action.h"
 | |
| #include "mozilla/dom/cache/FileUtils.h"
 | |
| #include "mozilla/dom/cache/Manager.h"
 | |
| #include "mozilla/dom/cache/ManagerId.h"
 | |
| #include "mozilla/dom/quota/DirectoryLock.h"
 | |
| #include "mozilla/dom/quota/QuotaManager.h"
 | |
| #include "mozilla/dom/quota/ResultExtensions.h"
 | |
| #include "mozIStorageConnection.h"
 | |
| #include "nsIPrincipal.h"
 | |
| #include "nsIRunnable.h"
 | |
| #include "nsIThread.h"
 | |
| #include "nsThreadUtils.h"
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| using mozilla::dom::cache::Action;
 | |
| using mozilla::dom::cache::CacheDirectoryMetadata;
 | |
| 
 | |
| class NullAction final : public Action {
 | |
|  public:
 | |
|   NullAction() = default;
 | |
| 
 | |
|   virtual void RunOnTarget(mozilla::SafeRefPtr<Resolver> aResolver,
 | |
|                            const mozilla::Maybe<CacheDirectoryMetadata>&,
 | |
|                            Data*) override {
 | |
|     // Resolve success immediately.  This Action does no actual work.
 | |
|     MOZ_DIAGNOSTIC_ASSERT(aResolver);
 | |
|     aResolver->Resolve(NS_OK);
 | |
|   }
 | |
| };
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| namespace mozilla::dom::cache {
 | |
| 
 | |
| using mozilla::dom::quota::AssertIsOnIOThread;
 | |
| using mozilla::dom::quota::DirectoryLock;
 | |
| using mozilla::dom::quota::OpenDirectoryListener;
 | |
| using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
 | |
| using mozilla::dom::quota::PersistenceType;
 | |
| using mozilla::dom::quota::QuotaManager;
 | |
| 
 | |
| class Context::Data final : public Action::Data {
 | |
|  public:
 | |
|   explicit Data(nsISerialEventTarget* aTarget) : mTarget(aTarget) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mTarget);
 | |
|   }
 | |
| 
 | |
|   virtual mozIStorageConnection* GetConnection() const override {
 | |
|     MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
|     return mConnection;
 | |
|   }
 | |
| 
 | |
|   virtual void SetConnection(mozIStorageConnection* aConn) override {
 | |
|     MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mConnection);
 | |
|     mConnection = aConn;
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mConnection);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~Data() {
 | |
|     // We could proxy release our data here, but instead just assert.  The
 | |
|     // Context code should guarantee that we are destroyed on the target
 | |
|     // thread once the connection is initialized.  If we're not, then
 | |
|     // QuotaManager might race and try to clear the origin out from under us.
 | |
|     MOZ_ASSERT_IF(mConnection, mTarget->IsOnCurrentThread());
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsISerialEventTarget> mTarget;
 | |
|   nsCOMPtr<mozIStorageConnection> mConnection;
 | |
| 
 | |
|   // Threadsafe counting because we're created on the PBackground thread
 | |
|   // and destroyed on the target IO thread.
 | |
|   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Context::Data)
 | |
| };
 | |
| 
 | |
| // Executed to perform the complicated dance of steps necessary to initialize
 | |
| // the QuotaManager.  This must be performed for each origin before any disk
 | |
| // IO occurrs.
 | |
| class Context::QuotaInitRunnable final : public nsIRunnable,
 | |
|                                          public OpenDirectoryListener {
 | |
|  public:
 | |
|   QuotaInitRunnable(SafeRefPtr<Context> aContext, SafeRefPtr<Manager> aManager,
 | |
|                     Data* aData, nsISerialEventTarget* aTarget,
 | |
|                     SafeRefPtr<Action> aInitAction)
 | |
|       : mContext(std::move(aContext)),
 | |
|         mThreadsafeHandle(mContext->CreateThreadsafeHandle()),
 | |
|         mManager(std::move(aManager)),
 | |
|         mData(aData),
 | |
|         mTarget(aTarget),
 | |
|         mInitAction(std::move(aInitAction)),
 | |
|         mInitiatingEventTarget(GetCurrentSerialEventTarget()),
 | |
|         mResult(NS_OK),
 | |
|         mState(STATE_INIT),
 | |
|         mCanceled(false) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mContext);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mManager);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mData);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mTarget);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mInitiatingEventTarget);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mInitAction);
 | |
|   }
 | |
| 
 | |
|   Maybe<DirectoryLock&> MaybeDirectoryLockRef() const {
 | |
|     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
 | |
| 
 | |
|     return ToMaybeRef(mDirectoryLock.get());
 | |
|   }
 | |
| 
 | |
|   nsresult Dispatch() {
 | |
|     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == STATE_INIT);
 | |
| 
 | |
|     mState = STATE_GET_INFO;
 | |
|     nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       mState = STATE_COMPLETE;
 | |
|       Clear();
 | |
|     }
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   void Cancel() {
 | |
|     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
 | |
|     mCanceled = true;
 | |
|     mInitAction->CancelOnInitiatingThread();
 | |
|   }
 | |
| 
 | |
|   // OpenDirectoryListener methods
 | |
|   virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
 | |
| 
 | |
|   virtual void DirectoryLockFailed() override;
 | |
| 
 | |
|  private:
 | |
|   class SyncResolver final : public Action::Resolver {
 | |
|    public:
 | |
|     SyncResolver() : mResolved(false), mResult(NS_OK) {}
 | |
| 
 | |
|     virtual void Resolve(nsresult aRv) override {
 | |
|       MOZ_DIAGNOSTIC_ASSERT(!mResolved);
 | |
|       mResolved = true;
 | |
|       mResult = aRv;
 | |
|     };
 | |
| 
 | |
|     bool Resolved() const { return mResolved; }
 | |
|     nsresult Result() const { return mResult; }
 | |
| 
 | |
|    private:
 | |
|     ~SyncResolver() = default;
 | |
| 
 | |
|     bool mResolved;
 | |
|     nsresult mResult;
 | |
| 
 | |
|     NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver,
 | |
|                                override)
 | |
|   };
 | |
| 
 | |
|   ~QuotaInitRunnable() {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mContext);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mInitAction);
 | |
|   }
 | |
| 
 | |
|   enum State {
 | |
|     STATE_INIT,
 | |
|     STATE_GET_INFO,
 | |
|     STATE_CREATE_QUOTA_MANAGER,
 | |
|     STATE_WAIT_FOR_DIRECTORY_LOCK,
 | |
|     STATE_ENSURE_ORIGIN_INITIALIZED,
 | |
|     STATE_RUN_ON_TARGET,
 | |
|     STATE_RUNNING,
 | |
|     STATE_COMPLETING,
 | |
|     STATE_COMPLETE
 | |
|   };
 | |
| 
 | |
|   void Complete(nsresult aResult) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING || NS_FAILED(aResult));
 | |
| 
 | |
|     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mResult));
 | |
|     mResult = aResult;
 | |
| 
 | |
|     mState = STATE_COMPLETING;
 | |
|     MOZ_ALWAYS_SUCCEEDS(
 | |
|         mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
 | |
|   }
 | |
| 
 | |
|   void Clear() {
 | |
|     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mContext);
 | |
|     mContext = nullptr;
 | |
|     mManager = nullptr;
 | |
|     mInitAction = nullptr;
 | |
|   }
 | |
| 
 | |
|   SafeRefPtr<Context> mContext;
 | |
|   SafeRefPtr<ThreadsafeHandle> mThreadsafeHandle;
 | |
|   SafeRefPtr<Manager> mManager;
 | |
|   RefPtr<Data> mData;
 | |
|   nsCOMPtr<nsISerialEventTarget> mTarget;
 | |
|   SafeRefPtr<Action> mInitAction;
 | |
|   nsCOMPtr<nsIEventTarget> mInitiatingEventTarget;
 | |
|   nsresult mResult;
 | |
|   Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
 | |
|   RefPtr<DirectoryLock> mDirectoryLock;
 | |
|   State mState;
 | |
|   Atomic<bool> mCanceled;
 | |
| 
 | |
|  public:
 | |
|   NS_DECL_THREADSAFE_ISUPPORTS
 | |
|   NS_DECL_NSIRUNNABLE
 | |
| };
 | |
| 
 | |
| void Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock* aLock) {
 | |
|   NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aLock);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
 | |
| 
 | |
|   mDirectoryLock = aLock;
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mDirectoryLock->Id() >= 0);
 | |
|   mDirectoryMetadata->mDirectoryLockId = mDirectoryLock->Id();
 | |
| 
 | |
|   if (mCanceled) {
 | |
|     Complete(NS_ERROR_ABORT);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   QuotaManager* qm = QuotaManager::Get();
 | |
|   MOZ_DIAGNOSTIC_ASSERT(qm);
 | |
| 
 | |
|   mState = STATE_ENSURE_ORIGIN_INITIALIZED;
 | |
|   nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     Complete(rv);
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Context::QuotaInitRunnable::DirectoryLockFailed() {
 | |
|   NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
 | |
| 
 | |
|   NS_WARNING("Failed to acquire a directory lock!");
 | |
| 
 | |
|   Complete(NS_ERROR_FAILURE);
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable);
 | |
| 
 | |
| // The QuotaManager init state machine is represented in the following diagram:
 | |
| //
 | |
| //    +---------------+
 | |
| //    |     Start     |      Resolve(error)
 | |
| //    | (Orig Thread) +---------------------+
 | |
| //    +-------+-------+                     |
 | |
| //            |                             |
 | |
| // +----------v-----------+                 |
 | |
| // |       GetInfo        |  Resolve(error) |
 | |
| // |    (Main Thread)     +-----------------+
 | |
| // +----------+-----------+                 |
 | |
| //            |                             |
 | |
| // +----------v-----------+                 |
 | |
| // |  CreateQuotaManager  |  Resolve(error) |
 | |
| // |    (Orig Thread)     +-----------------+
 | |
| // +----------+-----------+                 |
 | |
| //            |                             |
 | |
| // +----------v-----------+                 |
 | |
| // | WaitForDirectoryLock |  Resolve(error) |
 | |
| // |    (Orig Thread)     +-----------------+
 | |
| // +----------+-----------+                 |
 | |
| //            |                             |
 | |
| // +----------v------------+                |
 | |
| // |EnsureOriginInitialized| Resolve(error) |
 | |
| // |   (Quota IO Thread)   +----------------+
 | |
| // +----------+------------+                |
 | |
| //            |                             |
 | |
| // +----------v------------+                |
 | |
| // |     RunOnTarget       | Resolve(error) |
 | |
| // |   (Target Thread)     +----------------+
 | |
| // +----------+------------+                |
 | |
| //            |                             |
 | |
| //  +---------v---------+            +------v------+
 | |
| //  |      Running      |            |  Completing |
 | |
| //  | (Target Thread)   +------------>(Orig Thread)|
 | |
| //  +-------------------+            +------+------+
 | |
| //                                          |
 | |
| //                                    +-----v----+
 | |
| //                                    | Complete |
 | |
| //                                    +----------+
 | |
| //
 | |
| // The initialization process proceeds through the main states.  If an error
 | |
| // occurs, then we transition to Completing state back on the original thread.
 | |
| NS_IMETHODIMP
 | |
| Context::QuotaInitRunnable::Run() {
 | |
|   // May run on different threads depending on the state.  See individual
 | |
|   // state cases for thread assertions.
 | |
| 
 | |
|   SafeRefPtr<SyncResolver> resolver = MakeSafeRefPtr<SyncResolver>();
 | |
| 
 | |
|   switch (mState) {
 | |
|     // -----------------------------------
 | |
|     case STATE_GET_INFO: {
 | |
|       MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|       auto res = [this]() -> Result<Ok, nsresult> {
 | |
|         if (mCanceled) {
 | |
|           return Err(NS_ERROR_ABORT);
 | |
|         }
 | |
| 
 | |
|         nsCOMPtr<nsIPrincipal> principal = mManager->GetManagerId().Principal();
 | |
| 
 | |
|         QM_TRY_UNWRAP(auto principalMetadata,
 | |
|                       QuotaManager::GetInfoFromPrincipal(principal));
 | |
| 
 | |
|         mDirectoryMetadata.emplace(std::move(principalMetadata));
 | |
| 
 | |
|         mState = STATE_CREATE_QUOTA_MANAGER;
 | |
| 
 | |
|         MOZ_ALWAYS_SUCCEEDS(
 | |
|             mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
 | |
| 
 | |
|         return Ok{};
 | |
|       }();
 | |
| 
 | |
|       if (res.isErr()) {
 | |
|         resolver->Resolve(res.inspectErr());
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     // ----------------------------------
 | |
|     case STATE_CREATE_QUOTA_MANAGER: {
 | |
|       NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
 | |
| 
 | |
|       if (mCanceled || QuotaManager::IsShuttingDown()) {
 | |
|         resolver->Resolve(NS_ERROR_ABORT);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       QM_TRY(QuotaManager::EnsureCreated(), QM_PROPAGATE,
 | |
|              [&resolver](const auto rv) { resolver->Resolve(rv); });
 | |
| 
 | |
|       MOZ_DIAGNOSTIC_ASSERT(QuotaManager::Get());
 | |
| 
 | |
|       // Open directory
 | |
|       RefPtr<DirectoryLock> directoryLock =
 | |
|           QuotaManager::Get()->CreateDirectoryLock(PERSISTENCE_TYPE_DEFAULT,
 | |
|                                                    *mDirectoryMetadata,
 | |
|                                                    quota::Client::DOMCACHE,
 | |
|                                                    /* aExclusive */ false);
 | |
| 
 | |
|       // DirectoryLock::Acquire() will hold a reference to us as a listener. We
 | |
|       // will then get DirectoryLockAcquired() on the owning thread when it is
 | |
|       // safe to access our storage directory.
 | |
|       mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
 | |
|       directoryLock->Acquire(this);
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     // ----------------------------------
 | |
|     case STATE_ENSURE_ORIGIN_INITIALIZED: {
 | |
|       AssertIsOnIOThread();
 | |
| 
 | |
|       auto res = [this]() -> Result<Ok, nsresult> {
 | |
|         if (mCanceled) {
 | |
|           return Err(NS_ERROR_ABORT);
 | |
|         }
 | |
| 
 | |
|         QuotaManager* quotaManager = QuotaManager::Get();
 | |
|         MOZ_DIAGNOSTIC_ASSERT(quotaManager);
 | |
| 
 | |
|         QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized()));
 | |
| 
 | |
|         QM_TRY(
 | |
|             MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized()));
 | |
| 
 | |
|         QM_TRY_UNWRAP(mDirectoryMetadata->mDir,
 | |
|                       quotaManager
 | |
|                           ->EnsureTemporaryOriginIsInitialized(
 | |
|                               PERSISTENCE_TYPE_DEFAULT, *mDirectoryMetadata)
 | |
|                           .map([](const auto& res) { return res.first; }));
 | |
| 
 | |
|         mState = STATE_RUN_ON_TARGET;
 | |
| 
 | |
|         MOZ_ALWAYS_SUCCEEDS(
 | |
|             mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
 | |
| 
 | |
|         return Ok{};
 | |
|       }();
 | |
| 
 | |
|       if (res.isErr()) {
 | |
|         resolver->Resolve(res.inspectErr());
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     // -------------------
 | |
|     case STATE_RUN_ON_TARGET: {
 | |
|       MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
| 
 | |
|       mState = STATE_RUNNING;
 | |
| 
 | |
|       // Execute the provided initialization Action.  The Action must Resolve()
 | |
|       // before returning.
 | |
|       mInitAction->RunOnTarget(resolver.clonePtr(), mDirectoryMetadata, mData);
 | |
|       MOZ_DIAGNOSTIC_ASSERT(resolver->Resolved());
 | |
| 
 | |
|       mData = nullptr;
 | |
| 
 | |
|       // If the database was opened, then we should always succeed when creating
 | |
|       // the marker file.  If it wasn't opened successfully, then no need to
 | |
|       // create a marker file anyway.
 | |
|       if (NS_SUCCEEDED(resolver->Result())) {
 | |
|         MOZ_ALWAYS_SUCCEEDS(CreateMarkerFile(*mDirectoryMetadata));
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     // -------------------
 | |
|     case STATE_COMPLETING: {
 | |
|       NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
 | |
|       mInitAction->CompleteOnInitiatingThread(mResult);
 | |
|       mContext->OnQuotaInit(mResult, mDirectoryMetadata,
 | |
|                             mDirectoryLock.forget());
 | |
|       mState = STATE_COMPLETE;
 | |
| 
 | |
|       // Explicitly cleanup here as the destructor could fire on any of
 | |
|       // the threads we have bounced through.
 | |
|       Clear();
 | |
|       break;
 | |
|     }
 | |
|     // -----
 | |
|     case STATE_WAIT_FOR_DIRECTORY_LOCK:
 | |
|     default: {
 | |
|       MOZ_CRASH("unexpected state in QuotaInitRunnable");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (resolver->Resolved()) {
 | |
|     Complete(resolver->Result());
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // Runnable wrapper around Action objects dispatched on the Context.  This
 | |
| // runnable executes the Action on the appropriate threads while the Context
 | |
| // is initialized.
 | |
| class Context::ActionRunnable final : public nsIRunnable,
 | |
|                                       public Action::Resolver,
 | |
|                                       public Context::Activity {
 | |
|  public:
 | |
|   ActionRunnable(SafeRefPtr<Context> aContext, Data* aData,
 | |
|                  nsISerialEventTarget* aTarget, SafeRefPtr<Action> aAction,
 | |
|                  const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata)
 | |
|       : mContext(std::move(aContext)),
 | |
|         mData(aData),
 | |
|         mTarget(aTarget),
 | |
|         mAction(std::move(aAction)),
 | |
|         mDirectoryMetadata(aDirectoryMetadata),
 | |
|         mInitiatingThread(GetCurrentEventTarget()),
 | |
|         mState(STATE_INIT),
 | |
|         mResult(NS_OK),
 | |
|         mExecutingRunOnTarget(false) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mContext);
 | |
|     // mData may be nullptr
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mTarget);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mAction);
 | |
|     // mDirectoryMetadata.mDir may be nullptr if QuotaInitRunnable failed
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mInitiatingThread);
 | |
|   }
 | |
| 
 | |
|   nsresult Dispatch() {
 | |
|     NS_ASSERT_OWNINGTHREAD(ActionRunnable);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == STATE_INIT);
 | |
| 
 | |
|     mState = STATE_RUN_ON_TARGET;
 | |
|     nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       mState = STATE_COMPLETE;
 | |
|       Clear();
 | |
|     }
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   virtual bool MatchesCacheId(CacheId aCacheId) const override {
 | |
|     NS_ASSERT_OWNINGTHREAD(ActionRunnable);
 | |
|     return mAction->MatchesCacheId(aCacheId);
 | |
|   }
 | |
| 
 | |
|   virtual void Cancel() override {
 | |
|     NS_ASSERT_OWNINGTHREAD(ActionRunnable);
 | |
|     mAction->CancelOnInitiatingThread();
 | |
|   }
 | |
| 
 | |
|   virtual void Resolve(nsresult aRv) override {
 | |
|     MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == STATE_RUNNING);
 | |
| 
 | |
|     mResult = aRv;
 | |
| 
 | |
|     // We ultimately must complete on the initiating thread, but bounce through
 | |
|     // the current thread again to ensure that we don't destroy objects and
 | |
|     // state out from under the currently running action's stack.
 | |
|     mState = STATE_RESOLVING;
 | |
| 
 | |
|     // If we were resolved synchronously within Action::RunOnTarget() then we
 | |
|     // can avoid a thread bounce and just resolve once RunOnTarget() returns.
 | |
|     // The Run() method will handle this by looking at mState after
 | |
|     // RunOnTarget() returns.
 | |
|     if (mExecutingRunOnTarget) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Otherwise we are in an asynchronous resolve.  And must perform a thread
 | |
|     // bounce to run on the target thread again.
 | |
|     MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~ActionRunnable() {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mState == STATE_COMPLETE);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mContext);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mAction);
 | |
|   }
 | |
| 
 | |
|   void Clear() {
 | |
|     NS_ASSERT_OWNINGTHREAD(ActionRunnable);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mContext);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(mAction);
 | |
|     mContext->RemoveActivity(*this);
 | |
|     mContext = nullptr;
 | |
|     mAction = nullptr;
 | |
|   }
 | |
| 
 | |
|   enum State {
 | |
|     STATE_INIT,
 | |
|     STATE_RUN_ON_TARGET,
 | |
|     STATE_RUNNING,
 | |
|     STATE_RESOLVING,
 | |
|     STATE_COMPLETING,
 | |
|     STATE_COMPLETE
 | |
|   };
 | |
| 
 | |
|   SafeRefPtr<Context> mContext;
 | |
|   RefPtr<Data> mData;
 | |
|   nsCOMPtr<nsISerialEventTarget> mTarget;
 | |
|   SafeRefPtr<Action> mAction;
 | |
|   const Maybe<CacheDirectoryMetadata> mDirectoryMetadata;
 | |
|   nsCOMPtr<nsIEventTarget> mInitiatingThread;
 | |
|   State mState;
 | |
|   nsresult mResult;
 | |
| 
 | |
|   // Only accessible on target thread;
 | |
|   bool mExecutingRunOnTarget;
 | |
| 
 | |
|  public:
 | |
|   NS_DECL_THREADSAFE_ISUPPORTS
 | |
|   NS_DECL_NSIRUNNABLE
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable, nsIRunnable);
 | |
| 
 | |
| // The ActionRunnable has a simpler state machine.  It basically needs to run
 | |
| // the action on the target thread and then complete on the original thread.
 | |
| //
 | |
| //   +-------------+
 | |
| //   |    Start    |
 | |
| //   |(Orig Thread)|
 | |
| //   +-----+-------+
 | |
| //         |
 | |
| // +-------v---------+
 | |
| // |  RunOnTarget    |
 | |
| // |Target IO Thread)+---+ Resolve()
 | |
| // +-------+---------+   |
 | |
| //         |             |
 | |
| // +-------v----------+  |
 | |
| // |     Running      |  |
 | |
| // |(Target IO Thread)|  |
 | |
| // +------------------+  |
 | |
| //         | Resolve()   |
 | |
| // +-------v----------+  |
 | |
| // |     Resolving    <--+                   +-------------+
 | |
| // |                  |                      |  Completing |
 | |
| // |(Target IO Thread)+---------------------->(Orig Thread)|
 | |
| // +------------------+                      +-------+-----+
 | |
| //                                                   |
 | |
| //                                                   |
 | |
| //                                              +----v---+
 | |
| //                                              |Complete|
 | |
| //                                              +--------+
 | |
| //
 | |
| // Its important to note that synchronous actions will effectively Resolve()
 | |
| // out of the Running state immediately.  Asynchronous Actions may remain
 | |
| // in the Running state for some time, but normally the ActionRunnable itself
 | |
| // does not see any execution there.  Its all handled internal to the Action.
 | |
| NS_IMETHODIMP
 | |
| Context::ActionRunnable::Run() {
 | |
|   switch (mState) {
 | |
|     // ----------------------
 | |
|     case STATE_RUN_ON_TARGET: {
 | |
|       MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
|       MOZ_DIAGNOSTIC_ASSERT(!mExecutingRunOnTarget);
 | |
| 
 | |
|       // Note that we are calling RunOnTarget().  This lets us detect
 | |
|       // if Resolve() is called synchronously.
 | |
|       AutoRestore<bool> executingRunOnTarget(mExecutingRunOnTarget);
 | |
|       mExecutingRunOnTarget = true;
 | |
| 
 | |
|       mState = STATE_RUNNING;
 | |
|       mAction->RunOnTarget(SafeRefPtrFromThis(), mDirectoryMetadata, mData);
 | |
| 
 | |
|       mData = nullptr;
 | |
| 
 | |
|       // Resolve was called synchronously from RunOnTarget().  We can
 | |
|       // immediately move to completing now since we are sure RunOnTarget()
 | |
|       // completed.
 | |
|       if (mState == STATE_RESOLVING) {
 | |
|         // Use recursion instead of switch case fall-through...  Seems slightly
 | |
|         // easier to understand.
 | |
|         Run();
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     // -----------------
 | |
|     case STATE_RESOLVING: {
 | |
|       MOZ_ASSERT(mTarget->IsOnCurrentThread());
 | |
|       // The call to Action::RunOnTarget() must have returned now if we
 | |
|       // are running on the target thread again.  We may now proceed
 | |
|       // with completion.
 | |
|       mState = STATE_COMPLETING;
 | |
|       // Shutdown must be delayed until all Contexts are destroyed.  Crash
 | |
|       // for this invariant violation.
 | |
|       MOZ_ALWAYS_SUCCEEDS(
 | |
|           mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
 | |
|       break;
 | |
|     }
 | |
|     // -------------------
 | |
|     case STATE_COMPLETING: {
 | |
|       NS_ASSERT_OWNINGTHREAD(ActionRunnable);
 | |
|       mAction->CompleteOnInitiatingThread(mResult);
 | |
|       mState = STATE_COMPLETE;
 | |
|       // Explicitly cleanup here as the destructor could fire on any of
 | |
|       // the threads we have bounced through.
 | |
|       Clear();
 | |
|       break;
 | |
|     }
 | |
|     // -----------------
 | |
|     default: {
 | |
|       MOZ_CRASH("unexpected state in ActionRunnable");
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void Context::ThreadsafeHandle::AllowToClose() {
 | |
|   if (mOwningEventTarget->IsOnCurrentThread()) {
 | |
|     AllowToCloseOnOwningThread();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Dispatch is guaranteed to succeed here because we block shutdown until
 | |
|   // all Contexts have been destroyed.
 | |
|   nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
 | |
|       "dom::cache::Context::ThreadsafeHandle::AllowToCloseOnOwningThread", this,
 | |
|       &ThreadsafeHandle::AllowToCloseOnOwningThread);
 | |
|   MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(runnable.forget(),
 | |
|                                                    nsIThread::DISPATCH_NORMAL));
 | |
| }
 | |
| 
 | |
| void Context::ThreadsafeHandle::InvalidateAndAllowToClose() {
 | |
|   if (mOwningEventTarget->IsOnCurrentThread()) {
 | |
|     InvalidateAndAllowToCloseOnOwningThread();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Dispatch is guaranteed to succeed here because we block shutdown until
 | |
|   // all Contexts have been destroyed.
 | |
|   nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod(
 | |
|       "dom::cache::Context::ThreadsafeHandle::"
 | |
|       "InvalidateAndAllowToCloseOnOwningThread",
 | |
|       this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
 | |
|   MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(runnable.forget(),
 | |
|                                                    nsIThread::DISPATCH_NORMAL));
 | |
| }
 | |
| 
 | |
| Context::ThreadsafeHandle::ThreadsafeHandle(SafeRefPtr<Context> aContext)
 | |
|     : mStrongRef(std::move(aContext)),
 | |
|       mWeakRef(mStrongRef.unsafeGetRawPtr()),
 | |
|       mOwningEventTarget(GetCurrentSerialEventTarget()) {}
 | |
| 
 | |
| Context::ThreadsafeHandle::~ThreadsafeHandle() {
 | |
|   // Normally we only touch mStrongRef on the owning thread.  This is safe,
 | |
|   // however, because when we do use mStrongRef on the owning thread we are
 | |
|   // always holding a strong ref to the ThreadsafeHandle via the owning
 | |
|   // runnable.  So we cannot run the ThreadsafeHandle destructor simultaneously.
 | |
|   if (!mStrongRef || mOwningEventTarget->IsOnCurrentThread()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Dispatch in NS_ProxyRelease is guaranteed to succeed here because we block
 | |
|   // shutdown until all Contexts have been destroyed. Therefore it is ok to have
 | |
|   // MOZ_ALWAYS_SUCCEED here.
 | |
|   MOZ_ALWAYS_SUCCEEDS(NS_ProxyRelease("Context::ThreadsafeHandle::mStrongRef",
 | |
|                                       mOwningEventTarget, mStrongRef.forget()));
 | |
| }
 | |
| 
 | |
| void Context::ThreadsafeHandle::AllowToCloseOnOwningThread() {
 | |
|   MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
 | |
| 
 | |
|   // A Context "closes" when its ref count drops to zero.  Dropping this
 | |
|   // strong ref is necessary, but not sufficient for the close to occur.
 | |
|   // Any outstanding IO will continue and keep the Context alive.  Once
 | |
|   // the Context is idle, it will be destroyed.
 | |
| 
 | |
|   // First, tell the context to flush any target thread shared data.  This
 | |
|   // data must be released on the target thread prior to running the Context
 | |
|   // destructor.  This will schedule an Action which ensures that the
 | |
|   // ~Context() is not immediately executed when we drop the strong ref.
 | |
|   if (mStrongRef) {
 | |
|     mStrongRef->DoomTargetData();
 | |
|   }
 | |
| 
 | |
|   // Now drop our strong ref and let Context finish running any outstanding
 | |
|   // Actions.
 | |
|   mStrongRef = nullptr;
 | |
| }
 | |
| 
 | |
| void Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread() {
 | |
|   MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
 | |
|   // Cancel the Context through the weak reference.  This means we can
 | |
|   // allow the Context to close by dropping the strong ref, but then
 | |
|   // still cancel ongoing IO if necessary.
 | |
|   if (mWeakRef) {
 | |
|     mWeakRef->Invalidate();
 | |
|   }
 | |
|   // We should synchronously have AllowToCloseOnOwningThread called when
 | |
|   // the Context is canceled.
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mStrongRef);
 | |
| }
 | |
| 
 | |
| void Context::ThreadsafeHandle::ContextDestroyed(Context& aContext) {
 | |
|   MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mStrongRef);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mWeakRef);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mWeakRef == &aContext);
 | |
|   mWeakRef = nullptr;
 | |
| }
 | |
| 
 | |
| // static
 | |
| SafeRefPtr<Context> Context::Create(SafeRefPtr<Manager> aManager,
 | |
|                                     nsISerialEventTarget* aTarget,
 | |
|                                     SafeRefPtr<Action> aInitAction,
 | |
|                                     Maybe<Context&> aOldContext) {
 | |
|   auto context = MakeSafeRefPtr<Context>(std::move(aManager), aTarget,
 | |
|                                          std::move(aInitAction));
 | |
|   context->Init(aOldContext);
 | |
|   return context;
 | |
| }
 | |
| 
 | |
| Context::Context(SafeRefPtr<Manager> aManager, nsISerialEventTarget* aTarget,
 | |
|                  SafeRefPtr<Action> aInitAction)
 | |
|     : mManager(std::move(aManager)),
 | |
|       mTarget(aTarget),
 | |
|       mData(new Data(aTarget)),
 | |
|       mState(STATE_CONTEXT_PREINIT),
 | |
|       mOrphanedData(false),
 | |
|       mInitAction(std::move(aInitAction)) {
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mManager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mTarget);
 | |
| }
 | |
| 
 | |
| void Context::Dispatch(SafeRefPtr<Action> aAction) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aAction);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState != STATE_CONTEXT_CANCELED);
 | |
| 
 | |
|   if (mState == STATE_CONTEXT_CANCELED) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mState == STATE_CONTEXT_INIT || mState == STATE_CONTEXT_PREINIT) {
 | |
|     PendingAction* pending = mPendingActions.AppendElement();
 | |
|     pending->mAction = std::move(aAction);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_READY);
 | |
|   DispatchAction(std::move(aAction));
 | |
| }
 | |
| 
 | |
| Maybe<DirectoryLock&> Context::MaybeDirectoryLockRef() const {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
| 
 | |
|   if (mState == STATE_CONTEXT_PREINIT) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
 | |
| 
 | |
|     return Nothing();
 | |
|   }
 | |
| 
 | |
|   if (mState == STATE_CONTEXT_INIT) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
 | |
| 
 | |
|     return mInitRunnable->MaybeDirectoryLockRef();
 | |
|   }
 | |
| 
 | |
|   return ToMaybeRef(mDirectoryLock.get());
 | |
| }
 | |
| 
 | |
| void Context::CancelAll() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
| 
 | |
|   // In PREINIT state we have not dispatch the init action yet.  Just
 | |
|   // forget it.
 | |
|   if (mState == STATE_CONTEXT_PREINIT) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
 | |
|     mInitAction = nullptr;
 | |
| 
 | |
|     // In INIT state we have dispatched the runnable, but not received the
 | |
|     // async completion yet.  Cancel the runnable, but don't forget about it
 | |
|     // until we get OnQuotaInit() callback.
 | |
|   } else if (mState == STATE_CONTEXT_INIT) {
 | |
|     mInitRunnable->Cancel();
 | |
|   }
 | |
| 
 | |
|   mState = STATE_CONTEXT_CANCELED;
 | |
|   mPendingActions.Clear();
 | |
|   for (const auto& activity : mActivityList.ForwardRange()) {
 | |
|     activity->Cancel();
 | |
|   }
 | |
|   AllowToClose();
 | |
| }
 | |
| 
 | |
| bool Context::IsCanceled() const {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   return mState == STATE_CONTEXT_CANCELED;
 | |
| }
 | |
| 
 | |
| void Context::Invalidate() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   mManager->NoteClosing();
 | |
|   CancelAll();
 | |
| }
 | |
| 
 | |
| void Context::AllowToClose() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   if (mThreadsafeHandle) {
 | |
|     mThreadsafeHandle->AllowToClose();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Context::CancelForCacheId(CacheId aCacheId) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
| 
 | |
|   // Remove matching pending actions
 | |
|   mPendingActions.RemoveElementsBy([aCacheId](const auto& pendingAction) {
 | |
|     return pendingAction.mAction->MatchesCacheId(aCacheId);
 | |
|   });
 | |
| 
 | |
|   // Cancel activities and let them remove themselves
 | |
|   for (const auto& activity : mActivityList.ForwardRange()) {
 | |
|     if (activity->MatchesCacheId(aCacheId)) {
 | |
|       activity->Cancel();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| Context::~Context() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mManager);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mData);
 | |
| 
 | |
|   if (mThreadsafeHandle) {
 | |
|     mThreadsafeHandle->ContextDestroyed(*this);
 | |
|   }
 | |
| 
 | |
|   // Note, this may set the mOrphanedData flag.
 | |
|   mManager->RemoveContext(*this);
 | |
| 
 | |
|   if (mDirectoryMetadata && mDirectoryMetadata->mDir && !mOrphanedData) {
 | |
|     MOZ_ALWAYS_SUCCEEDS(DeleteMarkerFile(*mDirectoryMetadata));
 | |
|   }
 | |
| 
 | |
|   if (mNextContext) {
 | |
|     mNextContext->Start();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Context::Init(Maybe<Context&> aOldContext) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
| 
 | |
|   if (aOldContext) {
 | |
|     aOldContext->SetNextContext(SafeRefPtrFromThis());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Start();
 | |
| }
 | |
| 
 | |
| void Context::Start() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
| 
 | |
|   // Previous context closing delayed our start, but then we were canceled.
 | |
|   // In this case, just do nothing here.
 | |
|   if (mState == STATE_CONTEXT_CANCELED) {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
 | |
|     MOZ_DIAGNOSTIC_ASSERT(!mInitAction);
 | |
|     // If we can't initialize the quota subsystem we will never be able to
 | |
|     // clear our shared data object via the target IO thread.  Instead just
 | |
|     // clear it here to maintain the invariant that the shared data is
 | |
|     // cleared before Context destruction.
 | |
|     mData = nullptr;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_PREINIT);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
 | |
| 
 | |
|   mInitRunnable =
 | |
|       new QuotaInitRunnable(SafeRefPtrFromThis(), mManager.clonePtr(), mData,
 | |
|                             mTarget, std::move(mInitAction));
 | |
|   mState = STATE_CONTEXT_INIT;
 | |
| 
 | |
|   nsresult rv = mInitRunnable->Dispatch();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     // Shutdown must be delayed until all Contexts are destroyed.  Shutdown
 | |
|     // must also prevent any new Contexts from being constructed.  Crash
 | |
|     // for this invariant violation.
 | |
|     MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Context::DispatchAction(SafeRefPtr<Action> aAction, bool aDoomData) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
| 
 | |
|   auto runnable =
 | |
|       MakeSafeRefPtr<ActionRunnable>(SafeRefPtrFromThis(), mData, mTarget,
 | |
|                                      std::move(aAction), mDirectoryMetadata);
 | |
| 
 | |
|   if (aDoomData) {
 | |
|     mData = nullptr;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = runnable->Dispatch();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     // Shutdown must be delayed until all Contexts are destroyed.  Crash
 | |
|     // for this invariant violation.
 | |
|     MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
 | |
|   }
 | |
|   AddActivity(*runnable);
 | |
| }
 | |
| 
 | |
| void Context::OnQuotaInit(
 | |
|     nsresult aRv, const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
 | |
|     already_AddRefed<DirectoryLock> aDirectoryLock) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mInitRunnable);
 | |
|   mInitRunnable = nullptr;
 | |
| 
 | |
|   if (aDirectoryMetadata) {
 | |
|     mDirectoryMetadata.emplace(*aDirectoryMetadata);
 | |
|   }
 | |
| 
 | |
|   // Always save the directory lock to ensure QuotaManager does not shutdown
 | |
|   // before the Context has gone away.
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
 | |
|   mDirectoryLock = aDirectoryLock;
 | |
| 
 | |
|   // If we opening the context failed, but we were not explicitly canceled,
 | |
|   // still treat the entire context as canceled.  We don't want to allow
 | |
|   // new actions to be dispatched.  We also cannot leave the context in
 | |
|   // the INIT state after failing to open.
 | |
|   if (NS_FAILED(aRv)) {
 | |
|     mState = STATE_CONTEXT_CANCELED;
 | |
|   }
 | |
| 
 | |
|   if (mState == STATE_CONTEXT_CANCELED) {
 | |
|     for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
 | |
|       mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
 | |
|     }
 | |
|     mPendingActions.Clear();
 | |
|     mThreadsafeHandle->AllowToClose();
 | |
|     // Context will destruct after return here and last ref is released.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_INIT);
 | |
|   mState = STATE_CONTEXT_READY;
 | |
| 
 | |
|   for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
 | |
|     DispatchAction(std::move(mPendingActions[i].mAction));
 | |
|   }
 | |
|   mPendingActions.Clear();
 | |
| }
 | |
| 
 | |
| void Context::AddActivity(Activity& aActivity) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   MOZ_ASSERT(!mActivityList.Contains(&aActivity));
 | |
|   mActivityList.AppendElement(WrapNotNullUnchecked(&aActivity));
 | |
| }
 | |
| 
 | |
| void Context::RemoveActivity(Activity& aActivity) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   MOZ_ALWAYS_TRUE(mActivityList.RemoveElement(&aActivity));
 | |
|   MOZ_ASSERT(!mActivityList.Contains(&aActivity));
 | |
| }
 | |
| 
 | |
| void Context::NoteOrphanedData() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   // This may be called more than once
 | |
|   mOrphanedData = true;
 | |
| }
 | |
| 
 | |
| SafeRefPtr<Context::ThreadsafeHandle> Context::CreateThreadsafeHandle() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   if (!mThreadsafeHandle) {
 | |
|     mThreadsafeHandle = MakeSafeRefPtr<ThreadsafeHandle>(SafeRefPtrFromThis());
 | |
|   }
 | |
|   return mThreadsafeHandle.clonePtr();
 | |
| }
 | |
| 
 | |
| void Context::SetNextContext(SafeRefPtr<Context> aNextContext) {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(aNextContext);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mNextContext);
 | |
|   mNextContext = std::move(aNextContext);
 | |
| }
 | |
| 
 | |
| void Context::DoomTargetData() {
 | |
|   NS_ASSERT_OWNINGTHREAD(Context);
 | |
|   MOZ_DIAGNOSTIC_ASSERT(mData);
 | |
| 
 | |
|   // We are about to drop our reference to the Data.  We need to ensure that
 | |
|   // the ~Context() destructor does not run until contents of Data have been
 | |
|   // released on the Target thread.
 | |
| 
 | |
|   // Dispatch a no-op Action.  This will hold the Context alive through a
 | |
|   // roundtrip to the target thread and back to the owning thread.  The
 | |
|   // ref to the Data object is cleared on the owning thread after creating
 | |
|   // the ActionRunnable, but before dispatching it.
 | |
|   DispatchAction(MakeSafeRefPtr<NullAction>(), true /* doomed data */);
 | |
| 
 | |
|   MOZ_DIAGNOSTIC_ASSERT(!mData);
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom::cache
 | 
