mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 17:25:36 +00:00
aefa2aa5da
For the Quatum DOM project, it's better to work in terms of event targets than threads. This patch converts DOM code to operate on event targets rather than threads, when possible. MozReview-Commit-ID: 5FgvpKadUA2
1152 lines
32 KiB
C++
1152 lines
32 KiB
C++
/* -*- 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(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<nsISerialEventTarget> 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,
|
|
nsISerialEventTarget* aTarget,
|
|
Action* aInitAction)
|
|
: mContext(aContext)
|
|
, mThreadsafeHandle(aContext->CreateThreadsafeHandle())
|
|
, mManager(aManager)
|
|
, mData(aData)
|
|
, mTarget(aTarget)
|
|
, mInitAction(aInitAction)
|
|
, mInitiatingEventTarget(GetCurrentThreadEventTarget())
|
|
, 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);
|
|
}
|
|
|
|
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(
|
|
mInitiatingEventTarget->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<nsISerialEventTarget> mTarget;
|
|
RefPtr<Action> mInitAction;
|
|
nsCOMPtr<nsIEventTarget> mInitiatingEventTarget;
|
|
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,
|
|
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);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
resolver->Resolve(rv);
|
|
break;
|
|
}
|
|
|
|
mState = STATE_CREATE_QUOTA_MANAGER;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mInitiatingEventTarget->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,
|
|
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(mTarget->IsOnCurrentThread());
|
|
|
|
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, nsISerialEventTarget* aTarget,
|
|
Action* aAction, const QuotaInfo& aQuotaInfo)
|
|
: mContext(aContext)
|
|
, mData(aData)
|
|
, mTarget(aTarget)
|
|
, mAction(aAction)
|
|
, mQuotaInfo(aQuotaInfo)
|
|
, mInitiatingThread(GetCurrentThreadEventTarget())
|
|
, 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->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
|
|
};
|
|
|
|
RefPtr<Context> mContext;
|
|
RefPtr<Data> mData;
|
|
nsCOMPtr<nsISerialEventTarget> mTarget;
|
|
RefPtr<Action> mAction;
|
|
const QuotaInfo mQuotaInfo;
|
|
nsCOMPtr<nsIEventTarget> 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<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(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(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(this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mOwningEventTarget->Dispatch(runnable.forget(), nsIThread::DISPATCH_NORMAL));
|
|
}
|
|
|
|
Context::ThreadsafeHandle::ThreadsafeHandle(Context* aContext)
|
|
: mStrongRef(aContext)
|
|
, mWeakRef(aContext)
|
|
, mOwningEventTarget(GetCurrentThreadSerialEventTarget())
|
|
{
|
|
}
|
|
|
|
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 is guaranteed to succeed here because we block shutdown until
|
|
// all Contexts have been destroyed.
|
|
NS_ProxyRelease(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
|
|
already_AddRefed<Context>
|
|
Context::Create(Manager* aManager, nsISerialEventTarget* aTarget,
|
|
Action* aInitAction, Context* aOldContext)
|
|
{
|
|
RefPtr<Context> context = new Context(aManager, aTarget, aInitAction);
|
|
context->Init(aOldContext);
|
|
return context.forget();
|
|
}
|
|
|
|
Context::Context(Manager* aManager, nsISerialEventTarget* 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<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
|