summaryrefslogtreecommitdiffstats
path: root/dom/cache/Context.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/cache/Context.cpp')
-rw-r--r--dom/cache/Context.cpp1149
1 files changed, 1149 insertions, 0 deletions
diff --git a/dom/cache/Context.cpp b/dom/cache/Context.cpp
new file mode 100644
index 000000000..db66ae90e
--- /dev/null
+++ b/dom/cache/Context.cpp
@@ -0,0 +1,1149 @@
+/* -*- 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 "mozilla/AutoRestore.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/QuotaManager.h"
+#include "mozIStorageConnection.h"
+#include "nsIFile.h"
+#include "nsIPrincipal.h"
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+
+namespace {
+
+using mozilla::dom::cache::Action;
+using mozilla::dom::cache::QuotaInfo;
+
+class NullAction final : public Action
+{
+public:
+ NullAction()
+ {
+ }
+
+ virtual void
+ RunOnTarget(Resolver* aResolver, const QuotaInfo&, Data*) override
+ {
+ // Resolve success immediately. This Action does no actual work.
+ MOZ_DIAGNOSTIC_ASSERT(aResolver);
+ aResolver->Resolve(NS_OK);
+ }
+};
+
+} // namespace
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::dom::quota::AssertIsOnIOThread;
+using mozilla::dom::quota::OpenDirectoryListener;
+using mozilla::dom::quota::QuotaManager;
+using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
+using mozilla::dom::quota::PersistenceType;
+
+class Context::Data final : public Action::Data
+{
+public:
+ explicit Data(nsIThread* aTarget)
+ : mTarget(aTarget)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget);
+ }
+
+ virtual mozIStorageConnection*
+ GetConnection() const override
+ {
+ MOZ_ASSERT(mTarget == NS_GetCurrentThread());
+ return mConnection;
+ }
+
+ virtual void
+ SetConnection(mozIStorageConnection* aConn) override
+ {
+ MOZ_ASSERT(mTarget == NS_GetCurrentThread());
+ 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 == NS_GetCurrentThread());
+ }
+
+ nsCOMPtr<nsIThread> 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(Context* aContext,
+ Manager* aManager,
+ Data* aData,
+ nsIThread* aTarget,
+ Action* aInitAction)
+ : mContext(aContext)
+ , mThreadsafeHandle(aContext->CreateThreadsafeHandle())
+ , mManager(aManager)
+ , mData(aData)
+ , mTarget(aTarget)
+ , mInitAction(aInitAction)
+ , mInitiatingThread(NS_GetCurrentThread())
+ , 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(mInitiatingThread);
+ MOZ_DIAGNOSTIC_ASSERT(mInitAction);
+ }
+
+ 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();
+ }
+
+ void OpenDirectory();
+
+ // 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() { }
+
+ 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_OPEN_DIRECTORY,
+ 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(
+ mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
+ }
+
+ void Clear()
+ {
+ NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+ MOZ_DIAGNOSTIC_ASSERT(mContext);
+ mContext = nullptr;
+ mManager = nullptr;
+ mInitAction = nullptr;
+ }
+
+ RefPtr<Context> mContext;
+ RefPtr<ThreadsafeHandle> mThreadsafeHandle;
+ RefPtr<Manager> mManager;
+ RefPtr<Data> mData;
+ nsCOMPtr<nsIThread> mTarget;
+ RefPtr<Action> mInitAction;
+ nsCOMPtr<nsIThread> mInitiatingThread;
+ nsresult mResult;
+ QuotaInfo mQuotaInfo;
+ RefPtr<DirectoryLock> mDirectoryLock;
+ State mState;
+ Atomic<bool> mCanceled;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+};
+
+void
+Context::QuotaInitRunnable::OpenDirectory()
+{
+ NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+ MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CREATE_QUOTA_MANAGER ||
+ mState == STATE_OPEN_DIRECTORY);
+ MOZ_DIAGNOSTIC_ASSERT(QuotaManager::Get());
+
+ // QuotaManager::OpenDirectory() 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;
+ QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
+ mQuotaInfo.mGroup,
+ mQuotaInfo.mOrigin,
+ mQuotaInfo.mIsApp,
+ quota::Client::DOMCACHE,
+ /* aExclusive */ false,
+ this);
+}
+
+void
+Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+ NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+ MOZ_DIAGNOSTIC_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
+ MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
+
+ mDirectoryLock = aLock;
+
+ 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-----------+ |
+// | OpenDirectory | 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.
+
+ RefPtr<SyncResolver> resolver = new SyncResolver();
+
+ switch(mState) {
+ // -----------------------------------
+ case STATE_GET_INFO:
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCanceled) {
+ resolver->Resolve(NS_ERROR_ABORT);
+ break;
+ }
+
+ RefPtr<ManagerId> managerId = mManager->GetManagerId();
+ nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
+ nsresult rv = QuotaManager::GetInfoFromPrincipal(principal,
+ &mQuotaInfo.mSuffix,
+ &mQuotaInfo.mGroup,
+ &mQuotaInfo.mOrigin,
+ &mQuotaInfo.mIsApp);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ resolver->Resolve(rv);
+ break;
+ }
+
+ mState = STATE_CREATE_QUOTA_MANAGER;
+ MOZ_ALWAYS_SUCCEEDS(
+ mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL));
+ break;
+ }
+ // ----------------------------------
+ case STATE_CREATE_QUOTA_MANAGER:
+ {
+ NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+
+ if (mCanceled || QuotaManager::IsShuttingDown()) {
+ resolver->Resolve(NS_ERROR_ABORT);
+ break;
+ }
+
+ if (QuotaManager::Get()) {
+ OpenDirectory();
+ return NS_OK;
+ }
+
+ mState = STATE_OPEN_DIRECTORY;
+ QuotaManager::GetOrCreate(this);
+ break;
+ }
+ // ----------------------------------
+ case STATE_OPEN_DIRECTORY:
+ {
+ NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+
+ if (NS_WARN_IF(!QuotaManager::Get())) {
+ resolver->Resolve(NS_ERROR_FAILURE);
+ break;
+ }
+
+ OpenDirectory();
+ break;
+ }
+ // ----------------------------------
+ case STATE_ENSURE_ORIGIN_INITIALIZED:
+ {
+ AssertIsOnIOThread();
+
+ if (mCanceled) {
+ resolver->Resolve(NS_ERROR_ABORT);
+ break;
+ }
+
+ QuotaManager* qm = QuotaManager::Get();
+ MOZ_DIAGNOSTIC_ASSERT(qm);
+ nsresult rv = qm->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
+ mQuotaInfo.mSuffix,
+ mQuotaInfo.mGroup,
+ mQuotaInfo.mOrigin,
+ mQuotaInfo.mIsApp,
+ getter_AddRefs(mQuotaInfo.mDir));
+ if (NS_FAILED(rv)) {
+ resolver->Resolve(rv);
+ break;
+ }
+
+ mState = STATE_RUN_ON_TARGET;
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
+ break;
+ }
+ // -------------------
+ case STATE_RUN_ON_TARGET:
+ {
+ MOZ_ASSERT(NS_GetCurrentThread() == mTarget);
+
+ mState = STATE_RUNNING;
+
+ // Execute the provided initialization Action. The Action must Resolve()
+ // before returning.
+ mInitAction->RunOnTarget(resolver, mQuotaInfo, 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(mQuotaInfo));
+ }
+
+ break;
+ }
+ // -------------------
+ case STATE_COMPLETING:
+ {
+ NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+ mInitAction->CompleteOnInitiatingThread(mResult);
+ mContext->OnQuotaInit(mResult, mQuotaInfo, 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(Context* aContext, Data* aData, nsIEventTarget* aTarget,
+ Action* aAction, const QuotaInfo& aQuotaInfo)
+ : mContext(aContext)
+ , mData(aData)
+ , mTarget(aTarget)
+ , mAction(aAction)
+ , mQuotaInfo(aQuotaInfo)
+ , mInitiatingThread(NS_GetCurrentThread())
+ , mState(STATE_INIT)
+ , mResult(NS_OK)
+ , mExecutingRunOnTarget(false)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(mContext);
+ // mData may be nullptr
+ MOZ_DIAGNOSTIC_ASSERT(mTarget);
+ MOZ_DIAGNOSTIC_ASSERT(mAction);
+ // mQuotaInfo.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 == NS_GetCurrentThread());
+ 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
+ };
+
+ RefPtr<Context> mContext;
+ RefPtr<Data> mData;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ RefPtr<Action> mAction;
+ const QuotaInfo mQuotaInfo;
+ nsCOMPtr<nsIThread> 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(NS_GetCurrentThread() == mTarget);
+ 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(this, mQuotaInfo, 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(NS_GetCurrentThread() == mTarget);
+ // 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 (mOwningThread == NS_GetCurrentThread()) {
+ AllowToCloseOnOwningThread();
+ return;
+ }
+
+ // Dispatch is guaranteed to succeed here because we block shutdown until
+ // all Contexts have been destroyed.
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod(this, &ThreadsafeHandle::AllowToCloseOnOwningThread);
+ MOZ_ALWAYS_SUCCEEDS(
+ mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL));
+}
+
+void
+Context::ThreadsafeHandle::InvalidateAndAllowToClose()
+{
+ if (mOwningThread == NS_GetCurrentThread()) {
+ InvalidateAndAllowToCloseOnOwningThread();
+ return;
+ }
+
+ // Dispatch is guaranteed to succeed here because we block shutdown until
+ // all Contexts have been destroyed.
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod(this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
+ MOZ_ALWAYS_SUCCEEDS(
+ mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL));
+}
+
+Context::ThreadsafeHandle::ThreadsafeHandle(Context* aContext)
+ : mStrongRef(aContext)
+ , mWeakRef(aContext)
+ , mOwningThread(NS_GetCurrentThread())
+{
+}
+
+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 || mOwningThread == NS_GetCurrentThread()) {
+ return;
+ }
+
+ // Dispatch is guaranteed to succeed here because we block shutdown until
+ // all Contexts have been destroyed.
+ NS_ProxyRelease(mOwningThread, mStrongRef.forget());
+}
+
+void
+Context::ThreadsafeHandle::AllowToCloseOnOwningThread()
+{
+ MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
+
+ // 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(mOwningThread == NS_GetCurrentThread());
+ // 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(mOwningThread == NS_GetCurrentThread());
+ MOZ_DIAGNOSTIC_ASSERT(!mStrongRef);
+ MOZ_DIAGNOSTIC_ASSERT(mWeakRef);
+ MOZ_DIAGNOSTIC_ASSERT(mWeakRef == aContext);
+ mWeakRef = nullptr;
+}
+
+// static
+already_AddRefed<Context>
+Context::Create(Manager* aManager, nsIThread* aTarget,
+ Action* aInitAction, Context* aOldContext)
+{
+ RefPtr<Context> context = new Context(aManager, aTarget, aInitAction);
+ context->Init(aOldContext);
+ return context.forget();
+}
+
+Context::Context(Manager* aManager, nsIThread* aTarget, Action* aInitAction)
+ : mManager(aManager)
+ , mTarget(aTarget)
+ , mData(new Data(aTarget))
+ , mState(STATE_CONTEXT_PREINIT)
+ , mOrphanedData(false)
+ , mInitAction(aInitAction)
+{
+ MOZ_DIAGNOSTIC_ASSERT(mManager);
+ MOZ_DIAGNOSTIC_ASSERT(mTarget);
+}
+
+void
+Context::Dispatch(Action* aAction)
+{
+ NS_ASSERT_OWNINGTHREAD(Context);
+ MOZ_DIAGNOSTIC_ASSERT(aAction);
+
+ MOZ_DIAGNOSTIC_ASSERT(mState != STATE_CONTEXT_CANCELED);
+ if (mState == STATE_CONTEXT_CANCELED) {
+ return;
+ } else if (mState == STATE_CONTEXT_INIT ||
+ mState == STATE_CONTEXT_PREINIT) {
+ PendingAction* pending = mPendingActions.AppendElement();
+ pending->mAction = aAction;
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_READY);
+ DispatchAction(aAction);
+}
+
+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();
+ {
+ ActivityList::ForwardIterator iter(mActivityList);
+ while (iter.HasMore()) {
+ iter.GetNext()->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
+ for (int32_t i = mPendingActions.Length() - 1; i >= 0; --i) {
+ if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) {
+ mPendingActions.RemoveElementAt(i);
+ }
+ }
+
+ // Cancel activities and let them remove themselves
+ ActivityList::ForwardIterator iter(mActivityList);
+ while (iter.HasMore()) {
+ Activity* activity = iter.GetNext();
+ 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 (mQuotaInfo.mDir && !mOrphanedData) {
+ MOZ_ALWAYS_SUCCEEDS(DeleteMarkerFile(mQuotaInfo));
+ }
+
+ if (mNextContext) {
+ mNextContext->Start();
+ }
+}
+
+void
+Context::Init(Context* aOldContext)
+{
+ NS_ASSERT_OWNINGTHREAD(Context);
+
+ if (aOldContext) {
+ aOldContext->SetNextContext(this);
+ 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);
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mState == STATE_CONTEXT_PREINIT);
+ MOZ_DIAGNOSTIC_ASSERT(!mInitRunnable);
+
+ mInitRunnable = new QuotaInitRunnable(this, mManager, mData, mTarget,
+ mInitAction);
+ mInitAction = nullptr;
+
+ 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(Action* aAction, bool aDoomData)
+{
+ NS_ASSERT_OWNINGTHREAD(Context);
+
+ RefPtr<ActionRunnable> runnable =
+ new ActionRunnable(this, mData, mTarget, aAction, mQuotaInfo);
+
+ 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 QuotaInfo& aQuotaInfo,
+ already_AddRefed<DirectoryLock> aDirectoryLock)
+{
+ NS_ASSERT_OWNINGTHREAD(Context);
+
+ MOZ_DIAGNOSTIC_ASSERT(mInitRunnable);
+ mInitRunnable = nullptr;
+
+ mQuotaInfo = aQuotaInfo;
+
+ // 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(mPendingActions[i].mAction);
+ }
+ mPendingActions.Clear();
+}
+
+void
+Context::AddActivity(Activity* aActivity)
+{
+ NS_ASSERT_OWNINGTHREAD(Context);
+ MOZ_DIAGNOSTIC_ASSERT(aActivity);
+ MOZ_ASSERT(!mActivityList.Contains(aActivity));
+ mActivityList.AppendElement(aActivity);
+}
+
+void
+Context::RemoveActivity(Activity* aActivity)
+{
+ NS_ASSERT_OWNINGTHREAD(Context);
+ MOZ_DIAGNOSTIC_ASSERT(aActivity);
+ 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;
+}
+
+already_AddRefed<Context::ThreadsafeHandle>
+Context::CreateThreadsafeHandle()
+{
+ NS_ASSERT_OWNINGTHREAD(Context);
+ if (!mThreadsafeHandle) {
+ mThreadsafeHandle = new ThreadsafeHandle(this);
+ }
+ RefPtr<ThreadsafeHandle> ref = mThreadsafeHandle;
+ return ref.forget();
+}
+
+void
+Context::SetNextContext(Context* aNextContext)
+{
+ NS_ASSERT_OWNINGTHREAD(Context);
+ MOZ_DIAGNOSTIC_ASSERT(aNextContext);
+ MOZ_DIAGNOSTIC_ASSERT(!mNextContext);
+ mNextContext = 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.
+ RefPtr<Action> action = new NullAction();
+ DispatchAction(action, true /* doomed data */);
+
+ MOZ_DIAGNOSTIC_ASSERT(!mData);
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla