/* -*- 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 mTarget; nsCOMPtr 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 mContext; RefPtr mThreadsafeHandle; RefPtr mManager; RefPtr mData; nsCOMPtr mTarget; RefPtr mInitAction; nsCOMPtr mInitiatingThread; nsresult mResult; QuotaInfo mQuotaInfo; RefPtr mDirectoryLock; State mState; Atomic 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 resolver = new SyncResolver(); switch(mState) { // ----------------------------------- case STATE_GET_INFO: { MOZ_ASSERT(NS_IsMainThread()); if (mCanceled) { resolver->Resolve(NS_ERROR_ABORT); break; } RefPtr managerId = mManager->GetManagerId(); nsCOMPtr 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 mContext; RefPtr mData; nsCOMPtr mTarget; RefPtr mAction; const QuotaInfo mQuotaInfo; nsCOMPtr 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 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 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 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::Create(Manager* aManager, nsIThread* aTarget, Action* aInitAction, Context* aOldContext) { RefPtr 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); // 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(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 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 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::CreateThreadsafeHandle() { NS_ASSERT_OWNINGTHREAD(Context); if (!mThreadsafeHandle) { mThreadsafeHandle = new ThreadsafeHandle(this); } RefPtr 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 = new NullAction(); DispatchAction(action, true /* doomed data */); MOZ_DIAGNOSTIC_ASSERT(!mData); } } // namespace cache } // namespace dom } // namespace mozilla