Bug 1446161 - Asynchronously launch preallocated content processes using MozPromise. r=mccr8

There are several layers to this patch:

1. GeckoChildProcessHost now exposes a promise that's resolved when
the process handle is available (or rejected if launch failed), as a
nonblocking alternative to LaunchAndWaitForProcessHandle.

2. ContentParent builds on this with the private method
LaunchSubprocessAsync and the public method PreallocateProcessAsync;
synchronous launch continues to exist for the regular on-demand launch
path, for the time being.

3. PreallocatedProcessManager now uses async launch, and handles the new
"launch in progress" state appropriately.

Depends on D8942

Differential Revision: https://phabricator.services.mozilla.com/D8943

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jed Davis 2018-11-22 00:35:53 +00:00
parent 5379e8a375
commit dececcae11
8 changed files with 219 additions and 44 deletions

View File

@ -622,7 +622,7 @@ bool ContentParent::sEarlySandboxInit = false;
// PreallocateProcess is called by the PreallocatedProcessManager.
// ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
/*static*/ already_AddRefed<ContentParent>
/*static*/ RefPtr<ContentParent::LaunchPromise>
ContentParent::PreallocateProcess()
{
RefPtr<ContentParent> process =
@ -631,11 +631,7 @@ ContentParent::PreallocateProcess()
eNotRecordingOrReplaying,
/* aRecordingFile = */ EmptyString());
if (!process->LaunchSubprocess(PROCESS_PRIORITY_PREALLOC)) {
return nullptr;
}
return process.forget();
return process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC);
}
/*static*/ void
@ -900,7 +896,7 @@ ContentParent::GetNewOrUsedBrowserProcess(Element* aFrameElement,
// Create a new process from scratch.
RefPtr<ContentParent> p = new ContentParent(aOpener, aRemoteType, recordReplayState, recordingFile);
if (!p->LaunchSubprocess(aPriority)) {
if (!p->LaunchSubprocessSync(aPriority)) {
return nullptr;
}
@ -933,7 +929,7 @@ ContentParent::GetNewOrUsedJSPluginProcess(uint32_t aPluginID,
p = new ContentParent(aPluginID);
if (!p->LaunchSubprocess(aPriority)) {
if (!p->LaunchSubprocessSync(aPriority)) {
return nullptr;
}
@ -2199,14 +2195,27 @@ ContentParent::AppendSandboxParams(std::vector<std::string>& aArgs)
}
#endif // XP_MACOSX && MOZ_CONTENT_SANDBOX
bool
ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
void
ContentParent::LaunchSubprocessInternal(
ProcessPriority aInitialPriority,
mozilla::Variant<bool*, RefPtr<LaunchPromise>*>&& aRetval)
{
AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess", OTHER);
const bool isSync = aRetval.is<bool*>();
auto earlyReject = [aRetval, isSync]() {
if (isSync) {
*aRetval.as<bool*>() = false;
} else {
*aRetval.as<RefPtr<LaunchPromise>*>() = LaunchPromise::CreateAndReject(
GeckoChildProcessHost::LaunchError(), __func__);
}
};
if (!ContentProcessManager::GetSingleton()) {
// Shutdown has begun, we shouldn't spawn any more child processes.
return false;
earlyReject();
return;
}
std::vector<std::string> extraArgs;
@ -2231,12 +2240,14 @@ ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PR
if (!shm.Create(prefs.Length())) {
NS_ERROR("failed to create shared memory in the parent");
MarkAsDead();
return false;
earlyReject();
return;
}
if (!shm.Map(prefs.Length())) {
NS_ERROR("failed to map shared memory in the parent");
MarkAsDead();
return false;
earlyReject();
return;
}
// Copy the serialized prefs into the shared memory.
@ -2312,44 +2323,100 @@ ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PR
extraArgs.push_back(NS_ConvertUTF16toUTF8(mRecordingFile).get());
}
if (!mSubprocess->LaunchAndWaitForProcessHandle(extraArgs)) {
RefPtr<ContentParent> self(this);
auto reject = [self, this](GeckoChildProcessHost::LaunchError err) {
NS_ERROR("failed to launch child in the parent");
MarkAsDead();
return false;
}
return LaunchPromise::CreateAndReject(err, __func__);
};
// See also ActorDestroy.
mSelfRef = this;
base::ProcessId procId =
base::GetProcId(mSubprocess->GetChildProcessHandle());
Open(mSubprocess->GetChannel(), procId);
// Lifetime note: the GeckoChildProcessHost holds a strong reference
// to the launch promise, which takes ownership of these closures,
// which hold strong references to this ContentParent; the
// ContentParent then owns the GeckoChildProcessHost (and that
// ownership is not exposed to the cycle collector). Therefore,
// this all stays alive until the promise is resolved or rejected.
auto resolve = [self, this, aInitialPriority, isSync,
// Transfer ownership of RAII file descriptor/handle
// holders so that they won't be closed before the
// child can inherit them.
shm = std::move(shm),
prefMapHandle = std::move(prefMapHandle)
](base::ProcessHandle handle) {
AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess::resolve", OTHER);
base::ProcessId procId = base::GetProcId(handle);
Open(mSubprocess->GetChannel(), procId);
#ifdef MOZ_CODE_COVERAGE
Unused << SendShareCodeCoverageMutex(
CodeCoverageHandler::Get()->GetMutexHandle(procId));
Unused << SendShareCodeCoverageMutex(
CodeCoverageHandler::Get()->GetMutexHandle(procId));
#endif
InitInternal(aInitialPriority);
mIsAlive = true;
InitInternal(aInitialPriority);
ContentProcessManager::GetSingleton()->AddContentProcess(this);
ContentProcessManager::GetSingleton()->AddContentProcess(this);
mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
// Set a reply timeout for CPOWs.
SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
// Set a reply timeout for CPOWs.
SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
nsAutoString cpId;
cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
nsAutoString cpId;
cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
}
Init();
// Launch time telemetry will return in a later patch (bug 1474991).
Unused << isSync;
return LaunchPromise::CreateAndResolve(self, __func__);
};
if (isSync) {
bool ok = mSubprocess->LaunchAndWaitForProcessHandle(std::move(extraArgs));
if (ok) {
Unused << resolve(mSubprocess->GetChildProcessHandle());
} else {
Unused << reject(GeckoChildProcessHost::LaunchError{});
}
*aRetval.as<bool*>() = ok;
} else {
auto* retptr = aRetval.as<RefPtr<LaunchPromise>*>();
if (mSubprocess->AsyncLaunch(std::move(extraArgs))) {
RefPtr<GeckoChildProcessHost::HandlePromise> ready =
mSubprocess->WhenProcessHandleReady();
*retptr = ready->Then(GetCurrentThreadSerialEventTarget(), __func__,
std::move(resolve), std::move(reject));
} else {
*retptr = reject(GeckoChildProcessHost::LaunchError{});
}
}
}
Init();
/* static */ bool
ContentParent::LaunchSubprocessSync(hal::ProcessPriority aInitialPriority)
{
bool retval;
LaunchSubprocessInternal(aInitialPriority, mozilla::AsVariant(&retval));
return retval;
}
// Launch time telemetry will return in a later patch (bug 1474991).
return true;
/* static */ RefPtr<ContentParent::LaunchPromise>
ContentParent::LaunchSubprocessAsync(hal::ProcessPriority aInitialPriority)
{
RefPtr<LaunchPromise> retval;
LaunchSubprocessInternal(aInitialPriority, mozilla::AsVariant(&retval));
return retval;
}
ContentParent::ContentParent(ContentParent* aOpener,
@ -2370,7 +2437,7 @@ ContentParent::ContentParent(ContentParent* aOpener,
, mRemoteWorkerActors(0)
, mNumDestroyingTabs(0)
, mIsAvailable(true)
, mIsAlive(true)
, mIsAlive(false)
, mIsForBrowser(!mRemoteType.IsEmpty())
, mRecordReplayState(aRecordReplayState)
, mRecordingFile(aRecordingFile)

View File

@ -19,6 +19,7 @@
#include "mozilla/MemoryReportingProcess.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Variant.h"
#include "mozilla/UniquePtr.h"
#include "nsDataHashtable.h"
@ -131,10 +132,13 @@ public:
virtual bool IsContentParent() const override { return true; }
using LaunchError = GeckoChildProcessHost::LaunchError;
using LaunchPromise = GeckoChildProcessHost::LaunchPromise<RefPtr<ContentParent>>;
/**
* Create a subprocess suitable for use later as a content process.
*/
static already_AddRefed<ContentParent> PreallocateProcess();
static RefPtr<LaunchPromise> PreallocateProcess();
/**
* Start up the content-process machinery. This might include
@ -797,7 +801,20 @@ private:
// Launch the subprocess and associated initialization.
// Returns false if the process fails to start.
bool LaunchSubprocess(hal::ProcessPriority aInitialPriority = hal::PROCESS_PRIORITY_FOREGROUND);
// Deprecated in favor of LaunchSubprocessAsync.
bool LaunchSubprocessSync(hal::ProcessPriority aInitialPriority);
// Launch the subprocess and associated initialization;
// returns a promise and signals failure by rejecting.
// OS-level launching work is dispatched to another thread, but some
// initialization (creating IPDL actors, etc.; see Init()) is run on
// the main thread.
RefPtr<LaunchPromise> LaunchSubprocessAsync(hal::ProcessPriority aInitialPriority);
// Common implementation of LaunchSubprocess{Sync,Async}.
void LaunchSubprocessInternal(
hal::ProcessPriority aInitialPriority,
mozilla::Variant<bool*, RefPtr<LaunchPromise>*>&& aRetval);
// Common initialization after sub process launch.
void InitInternal(ProcessPriority aPriority);

View File

@ -48,7 +48,7 @@ private:
static mozilla::StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton;
PreallocatedProcessManagerImpl();
~PreallocatedProcessManagerImpl() {}
~PreallocatedProcessManagerImpl();
DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManagerImpl);
void Init();
@ -67,8 +67,13 @@ private:
bool mEnabled;
bool mShutdown;
bool mLaunchInProgress;
RefPtr<ContentParent> mPreallocatedProcess;
nsTHashtable<nsUint64HashKey> mBlockers;
bool IsEmpty() const {
return !mPreallocatedProcess && !mLaunchInProgress;
}
};
/* static */ StaticRefPtr<PreallocatedProcessManagerImpl>
@ -92,8 +97,16 @@ NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver)
PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl()
: mEnabled(false)
, mShutdown(false)
, mLaunchInProgress(false)
{}
PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl()
{
// This shouldn't happen, because the promise callbacks should
// hold strong references, but let't make absolutely sure:
MOZ_RELEASE_ASSERT(!mLaunchInProgress);
}
void
PreallocatedProcessManagerImpl::Init()
{
@ -179,6 +192,9 @@ PreallocatedProcessManagerImpl::Take()
bool
PreallocatedProcessManagerImpl::Provide(ContentParent* aParent)
{
// This will take the already-running process even if there's a
// launch in progress; if that process hasn't been taken by the
// time the launch completes, the new process will be shut down.
if (mEnabled && !mShutdown && !mPreallocatedProcess) {
mPreallocatedProcess = aParent;
}
@ -220,7 +236,7 @@ PreallocatedProcessManagerImpl::RemoveBlocker(ContentParent* aParent)
// it's possible for a short-lived process to be recycled through
// Provide() and Take() before reaching RecvFirstIdle.)
mBlockers.RemoveEntry(childID);
if (!mPreallocatedProcess && mBlockers.IsEmpty()) {
if (IsEmpty() && mBlockers.IsEmpty()) {
AllocateAfterDelay();
}
}
@ -230,7 +246,7 @@ PreallocatedProcessManagerImpl::CanAllocate()
{
return mEnabled &&
mBlockers.IsEmpty() &&
!mPreallocatedProcess &&
IsEmpty() &&
!mShutdown &&
!ContentParent::IsMaxProcessCountReached(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
}
@ -267,14 +283,31 @@ void
PreallocatedProcessManagerImpl::AllocateNow()
{
if (!CanAllocate()) {
if (mEnabled && !mShutdown && !mPreallocatedProcess && !mBlockers.IsEmpty()) {
if (mEnabled && !mShutdown && IsEmpty() && !mBlockers.IsEmpty()) {
// If it's too early to allocate a process let's retry later.
AllocateAfterDelay();
}
return;
}
mPreallocatedProcess = ContentParent::PreallocateProcess();
RefPtr<PreallocatedProcessManagerImpl> self(this);
mLaunchInProgress = true;
ContentParent::PreallocateProcess()
->Then(GetCurrentThreadSerialEventTarget(), __func__,
[self, this](const RefPtr<ContentParent>& process) {
mLaunchInProgress = false;
if (CanAllocate()) {
mPreallocatedProcess = process;
} else {
process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE);
}
},
[self, this](ContentParent::LaunchError err) {
mLaunchInProgress = false;
});
}
void

View File

@ -43,6 +43,9 @@ class SharedMemory {
SetHandle(init_handle, read_only);
}
// Move constructor; transfers ownership.
SharedMemory(SharedMemory&& other);
// Destructor. Will close any open files.
~SharedMemory();

View File

@ -31,6 +31,20 @@ SharedMemory::SharedMemory()
max_size_(0) {
}
SharedMemory::SharedMemory(SharedMemory&& other) {
if (this == &other) {
return;
}
mapped_file_ = other.mapped_file_;
memory_ = other.memory_;
read_only_ = other.read_only_;
max_size_ = other.max_size_;
other.mapped_file_ = -1;
other.memory_ = nullptr;
}
SharedMemory::~SharedMemory() {
Close();
}

View File

@ -64,6 +64,21 @@ SharedMemory::SharedMemory()
max_size_(0) {
}
SharedMemory::SharedMemory(SharedMemory&& other) {
if (this == &other) {
return;
}
mapped_file_ = other.mapped_file_;
memory_ = other.memory_;
read_only_ = other.read_only_;
max_size_ = other.max_size_;
external_section_ = other.external_section_;
other.mapped_file_ = nullptr;
other.memory_ = nullptr;
}
SharedMemory::~SharedMemory() {
external_section_ = true;
Close();

View File

@ -346,6 +346,9 @@ GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts)
MessageLoop* ioLoop = XRE_GetIOMessageLoop();
MOZ_ASSERT(mHandlePromise == nullptr);
mHandlePromise = new HandlePromise::Private(__func__);
// Currently this can't fail (see the MOZ_ALWAYS_SUCCEEDS in
// MessageLoop::PostTask_Helper), but in the future it possibly
// could, in which case this method could return false.
@ -494,6 +497,7 @@ GeckoChildProcessHost::RunPerformAsyncLaunch(std::vector<std::string> aExtraOpts
// If something failed let's set the error state and notify.
MonitorAutoLock lock(mMonitor);
mProcessState = PROCESS_ERROR;
mHandlePromise->Reject(LaunchError{}, __func__);
lock.Notify();
CHROMIUM_LOG(ERROR) << "Failed to launch " <<
XRE_ChildProcessTypeToString(mProcessType) << " subprocess";
@ -1125,6 +1129,7 @@ GeckoChildProcessHost::PerformAsyncLaunch(std::vector<std::string> aExtraOpts)
MonitorAutoLock lock(mMonitor);
mProcessState = PROCESS_CREATED;
mHandlePromise->Resolve(process, __func__);
lock.Notify();
mLaunchOptions = nullptr;
@ -1149,6 +1154,7 @@ GeckoChildProcessHost::OnChannelConnected(int32_t peer_pid)
MOZ_CRASH("can't open handle to child process");
}
MonitorAutoLock lock(mMonitor);
MOZ_DIAGNOSTIC_ASSERT(mProcessState == PROCESS_CREATED);
mProcessState = PROCESS_CONNECTED;
lock.Notify();
}
@ -1170,12 +1176,20 @@ GeckoChildProcessHost::OnChannelError()
// in the FIXME comment below.
MonitorAutoLock lock(mMonitor);
if (mProcessState < PROCESS_CONNECTED) {
MOZ_DIAGNOSTIC_ASSERT(mProcessState == PROCESS_CREATED);
mProcessState = PROCESS_ERROR;
lock.Notify();
}
// FIXME/bug 773925: save up this error for the next listener.
}
RefPtr<GeckoChildProcessHost::HandlePromise>
GeckoChildProcessHost::WhenProcessHandleReady()
{
MOZ_ASSERT(mHandlePromise != nullptr);
return mHandlePromise;
}
void
GeckoChildProcessHost::GetQueuedMessages(std::queue<IPC::Message>& queue)
{

View File

@ -15,6 +15,7 @@
#include "mozilla/DebugOnly.h"
#include "mozilla/ipc/FileDescriptor.h"
#include "mozilla/Monitor.h"
#include "mozilla/MozPromise.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/UniquePtr.h"
@ -47,7 +48,8 @@ public:
// Does not block. The IPC channel may not be initialized yet, and
// the child process may or may not have been created when this
// method returns.
// method returns. This GeckoChildProcessHost must not be destroyed
// while the launch is in progress.
bool AsyncLaunch(StringVector aExtraOpts=StringVector());
virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0);
@ -77,6 +79,15 @@ public:
virtual void OnChannelError() override;
virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override;
struct LaunchError {};
template <typename T>
using LaunchPromise = mozilla::MozPromise<T, LaunchError, /* excl: */ false>;
using HandlePromise = LaunchPromise<base::ProcessHandle>;
// Resolves to the process handle when it's available (see
// LaunchAndWaitForProcessHandle); use with AsyncLaunch.
RefPtr<HandlePromise> WhenProcessHandleReady();
virtual void InitializeChannel();
virtual bool CanShutdown() override { return true; }
@ -170,6 +181,7 @@ protected:
#if defined(OS_MACOSX)
task_t mChildTask;
#endif
RefPtr<HandlePromise::Private> mHandlePromise;
bool OpenPrivilegedHandle(base::ProcessId aPid);