/* -*- 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/. */
using mozilla::dom::cache::Action; using mozilla::dom::cache::CacheDirectoryMetadata;
class NullAction final : public Action { public:
NullAction() = default;
virtualvoid RunOnTarget(mozilla::SafeRefPtr<Resolver> aResolver, const mozilla::Maybe<CacheDirectoryMetadata>&, Data*, const mozilla::Maybe<mozilla::dom::cache::CipherKey>& /* aMaybeCipherKey */) 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::ClientDirectoryLock; using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; using mozilla::dom::quota::PersistenceType; using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::SleepIfEnabled;
class Context::Data final : public Action::Data { public: explicit Data(nsISerialEventTarget* aTarget) : mTarget(aTarget) {
MOZ_DIAGNOSTIC_ASSERT(mTarget);
}
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());
}
// 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:
QuotaInitRunnable(SafeRefPtr<Context> aContext, SafeRefPtr<Manager> aManager,
Data* aData, nsISerialEventTarget* aTarget,
SafeRefPtr<Action> aInitAction)
: mContext(std::move(aContext)),
mThreadsafeHandle(mContext->CreateThreadsafeHandle()),
mManager(std::move(aManager)),
mData(aData),
mTarget(aTarget),
mInitAction(std::move(aInitAction)),
mInitiatingEventTarget(GetCurrentSerialEventTarget()),
mResult(NS_OK),
mState(STATE_INIT),
mCanceled(false) {
MOZ_DIAGNOSTIC_ASSERT(mContext);
MOZ_DIAGNOSTIC_ASSERT(mManager);
MOZ_DIAGNOSTIC_ASSERT(mData);
MOZ_DIAGNOSTIC_ASSERT(mTarget);
MOZ_DIAGNOSTIC_ASSERT(mInitiatingEventTarget);
MOZ_DIAGNOSTIC_ASSERT(mInitAction);
}
// 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);
// Explicitly cleanup here as the destructor could fire on any of // the threads we have bounced through.
Clear(); break;
} // ----- case STATE_WAIT_FOR_DIRECTORY_LOCK: default: {
MOZ_CRASH("unexpected state in QuotaInitRunnable");
}
}
if (resolver->Resolved()) {
Complete(resolver->Result());
}
return NS_OK;
}
// Runnable wrapper around Action objects dispatched on the Context. This // runnable executes the Action on the appropriate threads while the Context // is initialized. class Context::ActionRunnable final : public nsIRunnable, public Action::Resolver, public Context::Activity { public:
ActionRunnable(SafeRefPtr<Context> aContext, Data* aData,
nsISerialEventTarget* aTarget, SafeRefPtr<Action> aAction, const Maybe<CacheDirectoryMetadata>& aDirectoryMetadata,
RefPtr<CipherKeyManager> aCipherKeyManager)
: mContext(std::move(aContext)),
mData(aData),
mTarget(aTarget),
mAction(std::move(aAction)),
mDirectoryMetadata(aDirectoryMetadata),
mCipherKeyManager(std::move(aCipherKeyManager)),
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);
}
// 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));
}
void DoStringify(nsACString& aData) override {
aData.Append("ActionRunnable ("_ns + // "State:"_ns + IntToCString(mState) + kStringifyDelimiter + // "Action:"_ns + IntToCString(static_cast<bool>(mAction)) +
kStringifyDelimiter + // TODO: We might want to have Action::Stringify, too. // "Context:"_ns + IntToCString(static_cast<bool>(mContext)) +
kStringifyDelimiter + // We do not print out mContext as we most probably were called // by its Stringify. ")"_ns);
}
// The ActionRunnable has a simpler state machine. It basically needs to run // the action on the target thread and then complete on the original thread. // // +-------------+ // | Start | // |(Orig Thread)| // +-----+-------+ // | // +-------v---------+ // | RunOnTarget | // |Target IO Thread)+---+ Resolve() // +-------+---------+ | // | | // +-------v----------+ | // | Running | | // |(Target IO Thread)| | // +------------------+ | // | Resolve() | // +-------v----------+ | // | Resolving <--+ +-------------+ // | | | Completing | // |(Target IO Thread)+---------------------->(Orig Thread)| // +------------------+ +-------+-----+ // | // | // +----v---+ // |Complete| // +--------+ // // Its important to note that synchronous actions will effectively Resolve() // out of the Running state immediately. Asynchronous Actions may remain // in the Running state for some time, but normally the ActionRunnable itself // does not see any execution there. Its all handled internal to the Action.
NS_IMETHODIMP
Context::ActionRunnable::Run() { switch (mState) { // ---------------------- case STATE_RUN_ON_TARGET: {
MOZ_ASSERT(mTarget->IsOnCurrentThread());
MOZ_DIAGNOSTIC_ASSERT(!mExecutingRunOnTarget);
// Note that we are calling RunOnTarget(). This lets us detect // if Resolve() is called synchronously.
AutoRestore<bool> executingRunOnTarget(mExecutingRunOnTarget);
mExecutingRunOnTarget = true;
// Resolve was called synchronously from RunOnTarget(). We can // immediately move to completing now since we are sure RunOnTarget() // completed. if (mState == STATE_RESOLVING) { // Use recursion instead of switch case fall-through... Seems slightly // easier to understand.
Run();
}
break;
} // ----------------- case STATE_RESOLVING: {
MOZ_ASSERT(mTarget->IsOnCurrentThread()); // The call to Action::RunOnTarget() must have returned now if we // are running on the target thread again. We may now proceed // with completion.
mState = STATE_COMPLETING; // Shutdown must be delayed until all Contexts are destroyed. Crash // for this invariant violation.
MOZ_ALWAYS_SUCCEEDS(
mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL)); break;
} // ------------------- case STATE_COMPLETING: {
NS_ASSERT_OWNINGTHREAD(ActionRunnable);
mAction->CompleteOnInitiatingThread(mResult);
mState = STATE_COMPLETE; // Explicitly cleanup here as the destructor could fire on any of // the threads we have bounced through.
Clear(); break;
} // ----------------- default: {
MOZ_CRASH("unexpected state in ActionRunnable"); break;
}
} return NS_OK;
}
void Context::ThreadsafeHandle::AllowToClose() { if (mOwningEventTarget->IsOnCurrentThread()) {
AllowToCloseOnOwningThread(); return;
}
// Dispatch is guaranteed to succeed here because we block shutdown until // all Contexts have been destroyed.
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( "dom::cache::Context::ThreadsafeHandle::AllowToCloseOnOwningThread", this,
&ThreadsafeHandle::AllowToCloseOnOwningThread);
MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(runnable.forget(),
nsIThread::DISPATCH_NORMAL));
}
void Context::ThreadsafeHandle::InvalidateAndAllowToClose() { if (mOwningEventTarget->IsOnCurrentThread()) {
InvalidateAndAllowToCloseOnOwningThread(); return;
}
// Dispatch is guaranteed to succeed here because we block shutdown until // all Contexts have been destroyed.
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( "dom::cache::Context::ThreadsafeHandle::" "InvalidateAndAllowToCloseOnOwningThread", this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(runnable.forget(),
nsIThread::DISPATCH_NORMAL));
}
Context::ThreadsafeHandle::~ThreadsafeHandle() { // 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()));
}
// 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);
}
// 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.
} elseif (mState == STATE_CONTEXT_INIT) {
mInitRunnable->Cancel();
}
mState = STATE_CONTEXT_CANCELED; // Allow completion of pending actions in Context::OnQuotaInit if (!mInitRunnable) {
mPendingActions.Clear();
} for (constauto& activity : mActivityList.ForwardRange()) {
activity->Cancel();
}
AllowToClose();
}
// Cancel activities and let them remove themselves for (constauto& activity : mActivityList.ForwardRange()) { if (activity->MatchesCacheId(aCacheId)) {
activity->Cancel();
}
}
}
// 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;
}
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.");
}
}
auto runnable = MakeSafeRefPtr<ActionRunnable>(
SafeRefPtrFromThis(), mData, mTarget, std::move(aAction),
mDirectoryMetadata, mCipherKeyManager);
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);
}
// Always save the directory lock to ensure QuotaManager does not shutdown // before the Context has gone away.
MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock);
mDirectoryLock = std::move(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) { if (NS_SUCCEEDED(aRv)) {
aRv = NS_ERROR_ABORT;
} 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;
}
// We could only assert below if quota initialization was a success which // is ensured by NS_FAILED(aRv) above
MOZ_DIAGNOSTIC_ASSERT(mDirectoryMetadata);
MOZ_DIAGNOSTIC_ASSERT(mDirectoryLock);
MOZ_DIAGNOSTIC_ASSERT(!mDirectoryLock->Invalidated());
MOZ_DIAGNOSTIC_ASSERT_IF(mDirectoryMetadata->mIsPrivate, mCipherKeyManager);
// We are about to drop our reference to the Data. We need to ensure that // the ~Context() destructor does not run until contents of Data have been // released on the Target thread.
// Dispatch a no-op Action. This will hold the Context alive through a // roundtrip to the target thread and back to the owning thread. The // ref to the Data object is cleared on the owning thread after creating // the ActionRunnable, but before dispatching it.
DispatchAction(MakeSafeRefPtr<NullAction>(), true/* doomed data */);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.