123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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
|