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